Redis事务实现
Posted Baret-H
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis事务实现相关的知识,希望对你有一定的参考价值。
1、Radis事务执行流程
Redis事务是一组命令的集合,有以下特性:
- 一次性:在一个队列里面一次性一起执行
- 顺序性:一个事务中所有的命令都会序列化;事务执行的过程中,会按照预先定义的顺序执行
- 排他性:执行过程中不允许外界干扰
执行流程如下:
1️⃣ 事务开启
multi
命令的执行,标识着一个事务的开始,实际上是通过在客户端状态的flags属性中打开了REDIS_MULTI标识来完成。(在该命令执行之前,可以通过watch
命令监控一个或多个key)
2️⃣ 命令入队
当一个客户端切换到事务状态后,服务器会根据这个客户端发来的命令来执行不同的操作。如果客户端发送的命令为multi
、exec
、watch
、discard
中的一个,则立即执行这个命令;此外的命令,服务器不会立即执行,而是首先检查命令的格式是否正确,如果不正确,服务器会在客户端状态的flags属性关闭REDIS_MULTI标识,并且返回错误信息给客户端。如果正确则将命令放入一个事务队列中,然后向客户端返回queued回复
3️⃣ 事务执行
客户端发送exec
指令,服务器执行exec命令逻辑
- 如果客户端状态的flags属性不包含REDIS_MULTI标识,或者包含 REDIS_DIRTY_CAS 或 REDIS_DIRTY_EXEC 标识,那么直接取消事务的执行
- 如果客户端状态flags属性包含REDIS_MULTI标识,即客户端处于事务状态,则会遍历客户端的事务队列,然后执行事务队列中的命令,最后将返回结果全部返回给客户端
注意:
-
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!
-
Redis单条命令的执行是保证原子性的,但是redis事务不保证原子性
-
redis不支持事务回滚机制,但是会检查每一个事务中的命令是否错误
-
redis事务不支持检查那些程序员自己的逻辑错误,例如对字符串类型进行incr自增操作
2、案例1—正常执行事务
127.0.0.1:6379> multi #开启事务
OK
# 命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK
3、案例2—放弃事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard #取消事务
OK
127.0.0.1:6379> get k4 #事务中的命令都不会被执行
(nil)
4、案例3—编译型异常
(代码问题,命令有错),事务中所有的命令都不会被执行
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> sett k3 v3 #错误的命令,报错!!
(error) ERR unknown command `sett`, with args beginning with: `k3`, `v3`,
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行事务报错,所有的命令不会被执行
(error) EXECABORT Transaction discarded because of previous errors.
5、案例4—运行时异常
例如:1/0等错误
- 事务中的命令存在语法性错误,执行时,错误的命令会抛出异常,其他命令正常执行
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> incr k1 #k1为字符串类型,不能自增,语法错误
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec #语法错的命令不执行报错,其余命令正常执行
1) (error) ERR value is not an integer or out of range
2) OK
3) "v2"
6、Redis乐观锁—watch监控
回顾
悲观锁
&乐观锁
悲观锁:很悲观,无论做什么都加上锁
乐观锁:很乐观,所以不会上锁;但更新数据的时候会判断数据是否被修改过
在mysql中,通过version来实现乐观锁,首先获取version,然后更新时比较version,如果更改,则事务执行失败;如果没有失败,则执行成功
Redis通过
watch
监控测试,相当于乐观,Redis执行事务时会判断watch监视的对象是否更改
1️⃣ 模拟单线程正常执行
127.0.0.1:6379> set money 100 #100块钱
OK
127.0.0.1:6379> set spend 0 #花费了0元
OK
127.0.0.1:6379> watch money #监视money对象
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decrby money 20 #花了20快,money029
QUEUED
127.0.0.1:6379> incrby spend 20 #花费钱数+20
QUEUED
127.0.0.1:6379> exec #执行事务
1) (integer) 80
2) (integer) 20
2️⃣ 模拟多线程情况
xshell开两个客户端,连接阿里云服务器,连接redis服务,作为两个线程
首先对于第一个线程:先监视money
,开启事务,输入命令,但是还未执行
#线程1
127.0.0.1:6379> watch money #监视money
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby spend 10
QUEUED
此时对于第二个线程修改了money
的值
#线程2
[root@iZ2ze3zdx4jq8v6hetjjuxZ ~]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set money 1000 #更改money的值
OK
然后第一个线程再执行事务
#线程1
127.0.0.1:6379> exec #执行事务,返回空
(nil)
发现返回空,表示事务执行失败了!这是一个线程1执行事务时会判断监视的money
的值还是不是100,发现不是100,已经改变,所以执行失败
此时,就需要解锁然后获取最新的值再次监视
#线程1
127.0.0.1:6379> unwatch #解锁
OK
127.0.0.1:6379> watch money #获取最新的值再次监视
OK
然后再进行操作;好比JUC中自旋锁一样,直到事务执行成功!
以上是关于Redis事务实现的主要内容,如果未能解决你的问题,请参考以下文章