【Redis进阶】事务

1. Redis与MySQL的事务差别

相信一谈到事务,大家马上就能联想到MySQL的事务,其事务具有ACID四大特性,但是Redis的事务相比较于MySQL,那就是个"弟中弟",下面我们就来简单对比两者的事务特性:

  • 原子性:关于Redis事务是否具备"原子性"是存在争议的,Redis的原子性最原始的含义就是指"把一组操作打包在一起执行,要么全都执行;要么全都不执行"(但是Redis并没有回滚操作,即若中间有操作执行失败,Redis也会继续执行下去),但是对于MySQL来说,其提供的"原子性"特性保证这组操作要么全都执行成功,要么全都不执行(即MySQL事务带有回滚机制)
  • 一致性:Redis的事务不存在一致性,因为没有提供回滚机制,因此若中间操作执行失败,就可能存在数据不一致的情况
  • 持久性:Redis中的事务不存在持久性,尽管Redis含有持久化机制,但是这与事务特性无关
  • 隔离性:Redis中的事务不涉及到隔离性,因为Redis是一个单线程的服务器模型,所有的请求/事务都是串行执行的

2. Redis事务概述

目的:Redis中的事务最根本的目的就是将一组操作打包在一起执行,中间不允许有别的客户端插队执行
举一个形象生动的例子,我和我的朋友一起去吃烧烤,我一开始点了一堆五花肉、羊肉、牛肉、猪肉等菜品,但是我的朋友还没到,于是我让老板先存单不着急烤,当我的朋友到了以后他又点了一堆猪腰子、生蚝等菜品。此时我们告诉老板可以烤了(在这里我们前后两堆菜品一定是需要放在一起烤的,不会出现我们的菜品跟别的客人放一起端上来的情况)
实现方式:Redis服务器为每个客户端维护了一个队列(先进先出),当"事务开启"之后,客户端发送的命令就会加入到服务端对应的队列上而不是立即执行,如果遇到相应客户端的"执行事务"命令则按顺序将队列中的操作进行执行,并且根据原子性,只有处理完一个客户端的队列操作后才能执行下一个客户端的事务

💡 思考一下:为什么Redis中的事务如此简单,不设计的跟MySQL一样强大呢?

  1. 在空间上,需要额外的空间存储相应的更新操作(如undolog、redolog等等)
  2. 在时间上,回滚操作也需要额外的执行性能开销

而我们Redis是基于内存的数据库,追求性能!

应用场景:我们用到Redis中的事务操作往往在一些需要将一组操作放在一起执行的场景中,比如说秒杀场景中,提供下列伪代码,如果不加以任何限制让多个客户端并发执行,就可能存在超卖的问题!在Redis中我们就可以通过事务的方式来解决这种问题!

get count;
if (count > 0) {
    // 执行下单操作
    decr count;
}

我们借助事务操作就可以实现类似如下功能:

开启事务
get count;
if (count > 0) {
    // 执行下单操作
    decr count;
}
执行事务;

这样一来,当服务器接收到第二个客户端的"执行事务"命令时,第一个客户端的事务已经执行完毕,此时第二个客户端获取到的count就是正确的,也就避免了超卖问题。但是这里我们还需要想一想,Redis如何支持if条件判断语句呢?由于Redis支持原生Lua脚本,可以搭配Lua脚本进行if条件判断(感兴趣的小伙伴自行了解)

3. Redis事务操作

开启事务:MULTI
执行事务:EXEC
放弃当前事务:DISCARD
image.png
我们使用MULTI开启事务,并添加了两个key,但是并没有执行事务,此时如果使用第二个客户端进行访问,获取不到k1与k2的值:
image.png
但是当我们使用EXEC执行事务后,第二个客户端就可以进行访问了
image.png

温馨提示:如果在事务开启之后,突然断电,相当于执行了DISCARD丢弃当前事务操作!

4. watch监视原理

我们可以使用WATCH命令用来监视某个key是否在事务执行之前发生了变化:
image.png
在当前客户端上我们先使用watch k1监视k1键,此时第二个客户端执行了set k1 111此时当第一个客户端执行事务后,发现k1已经被改了于是返回nil,不执行set k1 222命令。
watch实现原理:类似于乐观锁,即预估当前发生锁冲突的概率不是很高,加锁操作的成本也比较低
watch中的乐观锁是基于 “版本号” 这样的机制实现的:

  1. 一开始客户端1执行watch k1时就会给k1分配一个版本号(递增的数字),例如为1,之后使用MULTI开始事务
  2. 这时客户端2执行SET k1 111修改了k1,此时版本号增长为2(由于客户端1此时还没有执行EXEC命令,因此由客户端2先执行)
  3. 此时客户端1执行EXEC命令,首先判断当时记录的k1的版本号是否与当前版本号一致,如果不一致说明有其余客户端更改,则返回nil,不执行事务中的内容;如果一致则继续执行

这个思想方法也与CAS的ABA问题解决方式相类似

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-21 19:34:03       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-21 19:34:03       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-21 19:34:03       45 阅读
  4. Python语言-面向对象

    2024-07-21 19:34:03       55 阅读

热门阅读

  1. 计算机视觉发展历程

    2024-07-21 19:34:03       17 阅读
  2. python中的fire和Linux shell中的参数传递

    2024-07-21 19:34:03       14 阅读
  3. Vue.js 首屏加载优化:实战与策略

    2024-07-21 19:34:03       13 阅读
  4. 《浔川 AI 五子棋 v5.0 上线倒计时》——浔川官方

    2024-07-21 19:34:03       18 阅读
  5. @JsonFormat注解的作用

    2024-07-21 19:34:03       13 阅读
  6. 云原生项目纪事系列 - 项目管理的鲜活事例

    2024-07-21 19:34:03       20 阅读
  7. Huawei、Cisco 路由中 RIP 协议 summary 的用法

    2024-07-21 19:34:03       17 阅读