[Redis] Redis事务
🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
- 1. Redis事务特性
- 2. Redis事务的意义
- 3. Redis事务实际操作
1. Redis事务特性
我们在之前学习MySQL和Spring的时候曾经接触过事务,我们知道,事务就是把好几个操作打包成为一个操作一起执行.这里的Redis的事务也是相同的道理.
我们在之前学习MySQL的事务的时候,我们知道MySQL的事务有4个特性,分别是原子性,一致性,持久性,隔离性.原子性就是把多个操作打包成为一个操作,要不都执行成功,要不都执行失败,一致性就是事务执行之前和执行之后数据都是合法的, 不可以出现某种中间的情况.持久性就是把数据保存在硬盘中,隔离性涉及到了事务的并发执行,隔离分为4个级别,分别是脏读,不可重复读,幻读,串行化.
但是Redis相比于MySQL的事务,就显得非常逊色.
- 首先原子性,Redis的事务,是否存在原子性,存在争议.Redis的事务虽然和MySQL一样,都是把多个操作打包为了一个操作.但是MySQL的事务在其中的操作出现一些异常的时候,数据都会回滚回去.要不全都执行成功,要不全部执行不成功.但是Redis中的事务如果有操作执行失败的时候,不具有回滚机制.也就是Redis的事务不具有要不都执行成功,要不都执行不成功的特性.Redis的这个原子性也就相当于MySQL原子性的一个半成品.
- 其次一致性,由于Redis没有约束,也没有回滚机制,如果事务执行过程中某个操作出现了失败,就可能引起数据不一致的情况.
- 不具备持久性,Redis本身就是内存数据库,数据是存在内存中的.虽然事务具有持久化的机制,但是这和事务没有关系.
- 最后不具备隔离性,Redis是一个单线程模型的服务程序,不存在并发的过程,所有的请求/事务都是串行化执行的.
2. Redis事务的意义
这么多特性Redis都不具备,那么Redis的事务到底有什么意义呢?
Redis的事务最主要的意义就是为了"打包",避免其他客户端的命令插队到中间.
Redis实现事务的时候引入了队列.每个客户端都有一个队列.开启事务的时候,此时客户端输入的命令就会发个服务器并且进入了这个队列中,而不是立即执行.当遇到"执行事务"的命令的时候,此时就会把队列中的这些任务都按照顺序依次执行.执行这些事务的时候是在Redis主线程中完成的.主线程会把事务中的操作都执行完,再处理别的客户端.
虽然Redis事务的功能没有MySQL强大,但是MySQL的事务为了完成这些强大的功能,也付出了不小的代价,空间上,需要花费更多的空间来存储更多的数据.时间上,也需要有更大的执行开销.
正是因为MySQL的事务有上述的问题,才有了Redis上场的机会.
比如下面的场景:
我们需要在购物网站上秒杀一个东西,就有可能出现超卖的情况,就是商家放了5000个商品,下单成功了5001个就是超卖.为了避免这样的情况,我们在多线程的章节,曾经通过上锁的方式来避免这样的情况.如果我们引入了Redis的话,我们可以直接使用事务即可解决.
比如两个客户端都在临界情况下下了单.
当客户端1开启一个事务的时候,事务1就会被放入到Redis的事务队列中,当客户端2也开启了一个事务的时候,事务2也会被放到队列中.事务1收到执行事务的命令被执行之后,count–,在事务2执行的时候,count>0的条件就不满足了,所以事务2就会执行失败.
上面的案例中,虽然Redis不具有if判断条件这样编程语言的功能.但是Redis可以引入lua脚本.通过lua脚本的方式就可以实现上述条件的判断.
3. Redis事务实际操作
- 开启事务
multi
. - 执行事务
exec
- 放弃当前事务
discard
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set key1 value1
QUEUED
127.0.0.1:6379[2]> set key2 value2
QUEUED
我们看到,在事务中set key之后,会出现一个QUEUED,这就证明了该指令已经被放入了队列中,但是还没有被执行.如果我们另外打开一个客户端的话,发现这几个key并没有被设置.
127.0.0.1:6379> SELECT 2
OK
127.0.0.1:6379[2]> keys *
(empty array)
在我们执行了exec命令之后,才会执行事务.
127.0.0.1:6379[2]> EXEC
1) OK
2) OK
127.0.0.1:6379[2]> keys *
1) "key2"
2) "key1"
如果使用discard命令,事务就会被放弃.
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set key3 value3
QUEUED
127.0.0.1:6379[2]> set key4 value4
QUEUED
127.0.0.1:6379[2]> DISCARD
OK
127.0.0.1:6379[2]> keys *
1) "key2"
2) "key1"
注意: 要是我们在开启事务的过程中,没有提交事务,而是重新启动了Redis服务器,那么该事务就和discard的效果一样,直接被抛弃了.
但是有时候会出现以下的情况,比如在一个客户端1的事务中,我们设置了一个key,value值为222,但是在客户端1的事务还没有提交的时候,客户端2中设置了key的value值为333.此时客户端1对事务进行了提交.此时key的值就被重新设置为了222.
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set key3 333
QUEUED
127.0.0.1:6379[2]> set key3 222
OK
127.0.0.1:6379[2]> EXEC
1) OK
127.0.0.1:6379[2]> get key3
"333"
这时候,我们就需要引入一个操作watch
,这个watch的用途就是监控某个key是否在开启事务之后,执行事务之前发生了改变.想要在事务中监控某个key,避免在另一个客户端修改key,我们就可以在事务开启之前使用watch key
操作.我们把刚才的操作引入watch再来操作一次.
127.0.0.1:6379[2]> del key3
(integer) 1
127.0.0.1:6379[2]> MULTI
OK
127.0.0.1:6379[2]> set key3 333
QUEUED
127.0.0.1:6379[2]> set key3 222
OK
127.0.0.1:6379[2]> EXEC
(nil)
我们看到,如果在事务提交之前,被watch的key如果在另一个客户端被修改之后,最后提交事务的时候,就会返回一个nil.证明这个事务执行是失败的.
那么watch的实现原理是怎样的呢?其实watch的实现原理就类似于我们在之前并发编程中学习的乐观锁的实现,即"解决CAS中的ABA问题"的策略.
和ABA问题中的实现策略一样,Redis的watch就相当于是基于版本号这样的机制,来实现了乐观锁.
还是那上面的例子.在watch key3之后,Redis就会记录key3的版本号,开启事务之后,如果在其他的客户端修改了key3的值,那么key3的版本号就会改变,在提交事务的时候,事务就会发现watch的key的版本号发生了改变,于是事务就会执行失败.watch本质上就是给exec加上了一个对特定key的判定条件.
举例说明:判断老公是否出轨.
离开家之前,把枕头放到一个特定的位置,拍一张照片,如果回来之后,枕头的位置发生了改变,则证明老公出轨了.