redis的事务,以及watch的原理

说到redis的事务,大家头一个想到的肯定就是mysql的事务,那么大家还记得mysql事务的四个特性吗?分别就是原子性,一致性,持久性,隔离性,大概再说一下这四个特性的含义吧

原子性:值得是mysql可以把几个操作"打包"成一个事务的关系去执行,并且规定如果事务中但凡有一个指令执行错误,事务的所有指令都要"回滚",要不然就是全部指令都要执行成功,也就是mysql的事务指令要么全部执行成功,要不就全部都回滚,一个指令也不执行。

一致性:就是值mysql的事务指令执行完之后,mysql的数据不能离谱,要符合指令执行之后的结果,比如你只是插入一行数据,然而实际新增了100条数据。

持久性:指的是mysql的数据都是存在硬盘中的。

隔离性:指的是多个事务同时访问mysql的执行顺序。

redis事务的特性

"弱化"的原子性

为什么说是弱化的呢?是因为redis的事务中的多个指令都只管执行,不管执行的结果如何,即使有一个指令出错,redis说指令不过就执行不过吧,剩下的指令继续执行就可以了,已经执行过的也不会回滚了,在mysql中这个事务的所有指令都不能继续执行了,已经执行的都应该根据binlog的日志去恢复数据库的数据,所以说是"弱化"的原子性,而且redis也可以把几个指令打包成一个事务去执行,所以还是有一定的原子性,只是大家一说到原子性,首先想到的就是mysql的原子性,这个原子性太深入人心了,所以大家都认为mysql的原子性才是真正的原子性,而redis的事务虽然也能打包成一个事务,但是不能像mysql一样去保证回滚或者像mysql一样要不全部执行通过,要不全部不执行。有人可能就要问,那为什么redis不跟mysql做的一样呢?redis当然可以做得跟mysql一样,但是这不就让redis失去了自己的特点了吗?mysql既然能够做到回滚,就是增加了更多的开销去保证这一点,所以存储binlog需要硬盘空间吧,回滚需要时间吧,所以mysql是增加了很多额外的开销来保证这一点的,所以我们之所以选择redis就像因为他快,之所以选择mysql就是因为他适合用来存储数据,如果redis啥都做得跟mysql一样,redis就没有自己的有事了,那为什么不直接选择mysql呢?

不具有的"一致性"

redis的事务没有原子性,因为redis的事务中的所有指令,redis都不能保证执行成功,也不能保证一但有一个执行失败就回滚所有的指令,所以说redis不能保证数据的一致性,他不能保证指令执行之后的结果与预期一致,也不能保证失败之后,回滚操作,让数据和执行前一致,所以说redis的事务没有一致性。

不具有的"持久性"

前面两个特点说mysql不具有,好像还说的过去,但是为什么说redis还没有持久性呢?redis的数据不是可以持久化到硬盘吗?注意这里说的redis事务的特点,事务的特点,事务是在内存上指令的,作用的范围也是内存,跟持久化就没有关系了。

不具有"隔离性"

至于为什么说没有隔离性,是因为隔离是针对多个事务同时执行的情况下才会有的特点,但是redis是单线程的呀,是不会有多个事务同时执行的情况发生了。

以上就是redis的事务的几个特点,不具有也算是一个特点吧。

接下来就好好举例子来说一下redis的事务是怎么使用的,已经如何开启一个事务,执行一个事务,舍弃一个事务。

开启事务用指令 multi

我们用指令multi来开启一个指令,当我们输入multi然后回车,Redis返回一个ok,就说明我们这个客户端已经开始一个事务了,然后我们就可以去输入各种指令,但是每输入一个指令,redis并不会给我们返回执行的结果,实际也不会去执行,而是给我们返回一个QUEUED,表示这些指令虽然是发给服务器的,但是服务器会把这些执行先加入到服务器给我们这个客户端专门分配的一个队列(每一个客户端都会专门分配一个队列),而先不会去执行的,所以当我们输入一个set指令的,你去打开其他客户端去使用get获取相对应的值是获取不到的,那我们要如何让redis服务器去帮我们执行这些指令的?使用指令exec。

执行事务用指令 exec

当我们已经将这个事务的所有指令都输入完成之后,就得让redis去帮我们去执行这个事务中的所有指令,这时候我们输入指令exec,redis服务器受到这个指令之后,就会把我们这个事务的所有指令统一去指令,而且不允许其他指令来打断这个事务,直到这个事务的所有指令都执行完成,才回去执行别的指令,也就是说,但redis服务器在只能指令事务中的指令时候,会阻塞其他客户端发来的指令。在redis执行事务中的指令时,如果有的指令执行不通过,redis并不会管他,而是继续往后执行,并不会像mysql一样去回滚。

但是有一个点要注意的是,在开始执行之前,redis服务器回去检查事务中的所有指令,如果发现有语法上的错误,就不会执行这个事务中任何一个指令,相当与直接结束这个事务了。

舍弃事务用指令 discard

这个指令的用处主要就是,但我们不想继续去开启这个事务,或者说让redis服务器去执行这个事务的任何一个指令,我们就可以给服务器发送指令discard,告诉服务器我们这个事务不要了,丢弃了,redis服务器就不会去执行这个事务。

具体可以举一个例子来说一下这三个指令的含义

当我们要和朋友去吃烧烤的时候,由于我们先到了,我们就跟老板娘说我们先点一些菜,但是不着急烤,等朋友都来了,他们再点一个菜,然后再一起烤,这里我们先点的菜就像当于事务的前几个指令,redis服务器还不会真正执行这些指令,也就是老板还不去烤这些菜,直到我们的朋友到到齐了,他们又接着点了一些菜,点菜结束之后,我们就可以告诉老板娘,我们点菜完了,开始烤吧,这里就相当于输入执行exec,告诉服务器我们这个事务的指令都输入完了,所有的指令都打包成一个事务了,你现在开始执行吧。当然当你还没有告诉老板可以开始烤之前呢,是可以告诉老板你们不要了,也就是输入指令discard的。

接下来我们就开看一下具体的代码

1.这里就开看出来没有exec去执行事务之前,redis服务器并不会去执行事务的指令

2.只有执行指令exec,然服务器去执行执行事务,我们才能执行获取到相关的键值对

3、舍弃的事务中的指令是不会执行的,就像这里,事务中的指令是去添加一个键值对,右边两次获取分别是执行discard前后,发现都不能获取到key4的值,所以是可以说明这一点,舍弃的事务是不会去执行的。

4、如果有两个客户端,两个都先后开启事务而且都去修改一个键值对的值,那结果是怎么样的呢?执行顺序如下图

如果是以上的顺序,很明显,由于是事务B后执行,也就是事务B后修改,那么key1存的值肯定是"我是事务B",这个事务开始的先后没有关系,跟事务提交的顺序有关系,谁先被redis服务器处理,谁被redis服务器后处理有关系。

而且很明显的就是,redis即使有多个事务同时改变一个变量,并不是有线程安全问题,因为redis从始至终都是单线程的,单线程自然就不会有线程安全问题。但是像上面的这一种情况呢?但事务B要去修改key1的值的时候,其实key1的值被改变了,而且是跟事务B在事务set指令前的值和之后的值是不一样的,那我们有办法再出现这样的情况的话,然事务B的修改指令不在生效吗?也就是当我们开启事务之后,我们要保证我们要修改的键值对是只能被我们当前的是去修改的,然后应该被其他客户端修改了,我们就不再修改了吗?其实是有办法的,只需要我们在开启事务使用watch指令去监视我们要修改的键值对就可以了。

watch的原理

watch的机制类似于一个"乐观锁",这里的锁并不是特指哪一种锁,而是锁的一种特性,只要是实现了这个特性的锁,都可以是"乐观锁",与乐观锁相关的锁叫做"悲观锁",他们的不同的就在对于是"锁冲突"的预期不同,乐观锁:加锁之前,就预期这个锁发生冲突的概率比较低,就先不加锁;悲观锁:加锁之前就预期这个锁发生冲突的概率比较高,就先加锁。那锁冲突指的是什么呢?指的是两个事务或者线程都尝试对一个资源进行访问,而只有一个线程或者一个事务能拿到这个资源,另一个事务或者线程只能阻塞等待其他线程或者事务释放这个资源。

那watch的乐观锁是怎么一个原理呢?接下来我们先看一下指令

从上面的指令的执行顺序就可以看出来,watch确实是达到了一种乐观锁的效果,当前先监视这个key,如果我的事务是针对这个key进行修改的,而且在我提交事务之前,这个key对应的value已经改变了,那我当前的事务中对这个key的修改就不生效了。

原理就是当执行watch指令的时候,watch会给这个key一个version(版本号)并且记录当前的版本号,版本号可以说一个整数,后续任何一个客户端只要是对这个key进行修改,那么版本号就会增加,注意这里虽然说是增加,但是并没有说会增加多少,你可以增加1,也可以增加100,都可以,具体要看业务的实现。所以当别的客户端对watch这个key进行修改的时候,版本号就会增加,但执行exec指令的是时候,会先去判断当前的版本号和watch记录的版本号进行比较,如果不一样,那就说明这个key已经被修改了,那么事务的对于watch的修改就不执行了,也就是当前事务直接丢弃掉,相当于discard的效果。

这个说到相当于discard的效果,只要watch监视的key被其他客户端修改了,当前事务所有的指令都会被舍弃掉,即使你包含对其他的key的修改、获取的指令,所有的指令都会被舍弃掉。

相关推荐
风筝在晴天搁浅2 分钟前
设置一个带超时时间的LRU缓存
缓存
AI进化营-智能译站12 分钟前
ROS2 C++开发系列18-STL容器实战:deque缓存激光雷达数据|priority_queue调度任务
开发语言·c++·缓存·ai
xmjd msup18 分钟前
mysql的分区表
数据库·mysql
Lyyaoo.18 分钟前
【JAVA Spring面经】Spring 事务失效情况
java·数据库·spring
MeAT ITEM24 分钟前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
dovens28 分钟前
PostgreSQL 中进行数据导入和导出
大数据·数据库·postgresql
IOT.FIVE.NO.128 分钟前
claude code desktop cowork报错解决和记录Workspace..The isolated Linux environment ...
linux·服务器·数据库
Rick199336 分钟前
mysql 慢查询怎么快速定位
android·数据库·mysql
科技小花8 小时前
全球化深水区,数据治理成为企业出海 “核心竞争力”
大数据·数据库·人工智能·数据治理·数据中台·全球化
X56619 小时前
如何在 Laravel 中正确保存嵌套动态表单数据(主服务与子服务)
jvm·数据库·python