Redis事务实现

Posted Baret-H

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis事务实现相关的知识,希望对你有一定的参考价值。

image-20210509232551480

1、Radis事务执行流程

Redis事务是一组命令的集合,有以下特性:

  • 一次性:在一个队列里面一次性一起执行
  • 顺序性:一个事务中所有的命令都会序列化;事务执行的过程中,会按照预先定义的顺序执行
  • 排他性:执行过程中不允许外界干扰

执行流程如下:

1️⃣ 事务开启

multi命令的执行,标识着一个事务的开始,实际上是通过在客户端状态的flags属性中打开了REDIS_MULTI标识来完成。(在该命令执行之前,可以通过watch命令监控一个或多个key)

2️⃣ 命令入队

当一个客户端切换到事务状态后,服务器会根据这个客户端发来的命令来执行不同的操作。如果客户端发送的命令为multiexecwatchdiscard中的一个,则立即执行这个命令;此外的命令,服务器不会立即执行,而是首先检查命令的格式是否正确,如果不正确,服务器会在客户端状态的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事务实现的主要内容,如果未能解决你的问题,请参考以下文章

redis代码解析-事务

Redis源代码分析(十七)--- multi事务操作

19 事务 相关操作

19 事务 相关操作

Redis 设计与实现 --事务

片段事务中的实例化错误