锦通 发表于 2025-3-23 05:33:09

【redis】事务详解,相关下令multi、exec、discard 与 watch 的原理

什么是事务

MySQL 事务:


[*]原子性:把多个操作,打包成一个团体
[*]一致性:事务执行之前和之后,数据都不能离谱(变化前后能对上号)
[*]持久性:事务中做出的修改都会存硬盘
[*]隔离性:事务并发执行,涉及到的一些问题
原子性

相比于 MySQL 来说,Redis 的事务就是个弟弟:


[*]原子性:Redis 的事务,到底有没有原子性?存在争议

[*]最原本的含义是把多个操作打包到一起,要么都执行,要么都不执行
[*]Redis 做到了上述的含义,但是 MySQL 这里的原子性走的更远

[*]MySQL 也是把多个操作打包到一起,要么全都执行成功,要么都不执行。假如事务中有操作执行失败的,就要进行回滚,把中央已经执行的操作,全部回退
[*]Redis 事务中的如干操作,要是有失败的,无所谓。


Redis 把多个操作打包到一起执行,已经可以称为是原子性了,只是 MySQL 标杆,提高了“原子性”门槛,这就使人们谈到原子性的时候,更多的想到的是 MySQL 如许带回滚的原子性
所以,一般说到 Redis 的事务有没有原子性,更多的倾向于没有(或者弱化的原子性)
一致性

Redis 没有束缚,也没有回滚机制,事务执行过程中假如某个修改操作出现失败,就可能引起不一致的情况


[*]所以 Redis 事务不具备一致性
持久性

reids 自己就是内存数据库,数据是存储在内存中的,固然 Redis 也有持久化机制(AOF),但这里的持久化机制和事务没有什么关系


[*]像 MySQL 那边,事务百分百有持久性,Redis 这边把持久化机制关了。这是不一样的
[*]所以 Redis 事务不具备持久性
隔离性

Redis 是一个单线程模子的服务器程序,全部的请求/事务都是“串行”执行的。而谈到隔离性,都是并发执行才会涉及到的


[*]所以 Redis 事务不涉及隔离性
上风

Redis 事务,主要的意义就是为了“打包”,制止其他客户端的下令,插队插到中央


[*]Redis 中实现事务,是引入了一个队列(每个客户端都有一个)
[*]开启事务的时候,此时客户端输入的下令,就会发给服务器,并且进入这个队列中(而不是立即执行)。当遇到了“执行事务”下令的时候,此时就会把队列中的这些任务都按次序依次执行

[*]“按次序依次执行”是在 Redis 主线程中完成的,主线程会把事务中的操作都执行完,再处理别的客户端

与 MySQL 对比

Redis 的事务为什么就计划的这么简单,而不计划成和 MySQL 一样强大呢?


[*]MySQL 的事务,在背后付出了很大的代价

[*]空间上,要耗费更多的空间来存储更多的数据(实现回滚,就要额外开发空间去存储必要的日记…)
[*]时间上,也要有更大的执行开销 (必要做更多额外的动作)

正是由于 MySQL 上述的问题,才有 Redis 上场的时机(简单高效的上风)
用处

什么时候必要用到 Redis 事务呢?


[*]假如我们必要把多个操作打包进行操作,使用事务是比力合适的
比如一个商品秒杀出售场景:
一个货品 A,进行秒杀出售,市场火爆。此时最重要的就是不能出现“超卖”的情况(超卖:放货 5000 台,卖出了 5001 台)
一个典型的程序写法:
获取仓库中剩余的商品个数
if(个数 > 0) {
        下单成功;
        个数--;
}


[*]假如不加上任何限定,就可能会存在"线程安全"问题
[*]所以我们得让 下单成功 和 个数-- 这两个操作是原子的

[*]以前在多线程中,是通过加锁的方式,来制止插队
[*]在 Redis 中,就直接使用事务即可

使用事务之后的写法:
开启事务
get count
if count > 0
        decr count
执行事务


[*]在 redis 吸收到下令的时候,不会立即执行,只会将其按次序放在队列中。当收到“执行事务”操作的时候,才会开始按次序执行下令https://i-blog.csdnimg.cn/img_convert/61ebdc39b841da5e63f5461bbff3800c.png
[*]第二个客户端的“执行事务”下令发过来之后,服务器才真正执行第二个事务中的内容。此时第一个事务执行下令已经运行过了,此时第二个事务 get 到的 count 就已经是第一个事务自减之后的效果了
这个场景中,没加锁,也能办理上述“超卖”问题
   redis 下令里能进行条件判断吗?


[*]redis 原生下令中确实没有这种条件判断定,但是 redis 支持 lua 脚本

[*]lua 是另外一种编程语言,特点是小巧,许多程序都可以内嵌 lua 语言,从而去执行其他的语言

[*]通过 lua 脚本,就能实现上述的“条件判断”,并且也和事务一样是打包批量执行的
[*]lua 脚本的实现方式,是 redis 事务的进阶版本
确实,redis 的事务的应用场景,没有 MySQL 的事务那么多(有点鸡肋的感觉)。redis 假如是按照集群模式摆设,就不支持事务
事务相关下令

开启事务——MULTI

(猫体,不是马体)
开启一个事务,执行成功返回 OK
https://i-blog.csdnimg.cn/img_convert/837d33d05340c2ad6957b65aaa422664.png


[*]此时只是在服务器的事务队列中,保存了上述请求,并没有真正执行下令
[*]此时假如另外开一个客户端,尝试查询这几个 key,是查询不到的
执行事务——EXEC

真正执行事务
https://i-blog.csdnimg.cn/img_convert/f89d4568042a563a5741cca4fe2b2bd9.png


[*]此时客户端才会真正把上述操作发给客户端,此时就可以获取到 key 的值了https://i-blog.csdnimg.cn/img_convert/ad346ed948d6a51346cb1ce4ea4748e3.png
放弃当前事务——DISCARD

放弃当前事务,此时直接清空事务队列,之前的操作都不会真正执行到https://i-blog.csdnimg.cn/img_convert/3901e86086ec8637466af5b9d7d6a406.png
https://i-blog.csdnimg.cn/img_convert/4e771450e0fb0a927aea643053585b5c.png


[*]前面输入的下令,都被抛弃了
当我们开启事务,并且给服务器发送若干下令之后,此时重启服务器,会怎么样?


[*]此时的效果就等同于 discard
[*]事务队列终归是内存中的结构,重启之后,天然是没有了
监控某个 key——WATCH

在执行事务的时候,假如某个事务中修改的值,被别的客户端修改了,此时就容易出现数据不一致的问题
作用场景

https://i-blog.csdnimg.cn/img_convert/8d748e8261ae9bfd2de09a0e91324884.png
从时间上来看,客户端 1 是先发送了 set key 222,客户端 2 是后发送了 set key 333


[*]由于客户端 1 中,得是 exec 执行了,才会真正执行 set key 222。这个操作变成了现实上更晚执行的操作,最终 key 的值就是 222
使用方法

在刚才的场景中,就可以使用 watch 下令来监控这个 key,看看这个 key 在事务的 multi 和 exec 之间,set key 之后,是否在外部被其他客户端修改了https://i-blog.csdnimg.cn/img_convert/31e2a9755ecefa9b1d4d655ffd798525.png


[*]被监控的 key 被修改之后,exec 之后返回值为 nil
实现原理

watch 的实现,类似与一个“乐观锁”


[*]乐观锁/悲观锁不是指某个具体的锁,而是指的是某一类锁的特性
[*]乐观锁:加锁之前,就有一个心理预期,接下来锁的辩论概率较低
[*]悲观锁:加锁之前,也有一个心理预期,接下来锁的辩论概率较高

[*]辩论:两个线程针对同一个锁加锁,一个能加锁成功,另一个就得阻塞等待

[*]锁辩论概率高低,接下来要做的工作是不一样的
   乐观锁在 https://yeeear.blog.csdn.net/article/details/141102212 这篇文章中有具体表明
redis 的 watch 就相称于是基于版本号如许的机制,来实现了“乐观锁”(就是 CAS 中的 ABA 问题的办理方法)
https://i-blog.csdnimg.cn/img_convert/6a3649866836025a028587604030b6db.png


[*]watch 必须搭配事务使用,并且必须在 multi 之前使用
[*]假如 watch 的版本号和 exec 的版本号

[*]一致:阐明当前 key 在事务开启到最终执行这个过程中,没有被别的客户端修改,于是才能真正进行设置
[*]不一致:阐明 key 在其他客户端中改过了,因此此处就直接抛弃事务中的操作,exec 返回 nil

所以,watch 本质上就是给 exec 加了一个判断条件
事务总结

Redis 的事务,要比 MySQL 的事务,简单许多

[*]原子性:Redis 的事务,并不支持回滚
[*]一致性:Redis 并不会保证事务执行前后的内容同一
[*]持久性:Redis 主要通过内存来存储数据
[*]隔离性:Redis 自身作为一个单线程的服务器模子,上面处理的请求本质上都是串行执行的
四个关于事务的下令:

[*]开启事务—— nulti
[*]执行事务—— exec
[*]放弃当前事务—— discard
[*]监控某个 key 是否被修改—— watch

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【redis】事务详解,相关下令multi、exec、discard 与 watch 的原理