1. 基本介绍
存在的问题:
Redis执行指令过程中,多条连续执行的指令被干扰,打断,插队 ,例如, 当客户端一 set一个name为张三时,在取的一刻, 另一个客户端set另外一个值,将打乱原先的设定
所以就需要事务的控制
redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列)。当执行时,一次性 按照添加顺序依次执行,中间不会被打断或者干扰。
2. 基本操作
Redis 通过 MULTI
、EXEC
、DISCARD
、WATCH
、UNWATCH
来实现事务功能
-
multi
开始事务,设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
-
exec
执行事务 ,设定事务的结束位置,同时执行事务。与multi成对出现,成对使用
加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行
-
discard
取消事务 ,终止当前事务的定义,发生在multi之后,exec之前
-
watch
监视这个某个或者某些 key 在该事务期间是否被修改过,如果有至少一个 key 被修改了,那么在 EXEC 命令执行时会拒绝执行,(java cas乐观锁)
-
unwatch
取消监控
事务的工作流程
示意图:
- 当执行一个命令时,若此时不是事务状态,将普通的执行此指令
- 若识别到执行了mutil 指令,将创建一个队列,并返回"OK"
- 后面如果继续会执行普通的指令,但是此时在事务状态,将会把这些指令放到队列中
- 如执行到exec指令,则会依次执行队列中的命令,并关闭事务状态
注意事项
若在事务的过程中,命令发生错误怎么办?
-
语法错误 , 指命令书写格式有误 .那么整体事务中所有命令均不会执行。包括那些语法正确的命令。
-
运行错误 指命令格式正确,但是无法正确的执行。那么能够正确运行的命令会执行,运行错误的命令不会被执行 (需要手动回滚即手动set回原来的值,意味着Redis的事务不支持原子性)
3. Redis 事务的 ACID 原则
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability) 是关系型数据库中事务的最基本要求, 那么Redis的事务又支持几种呢
结论:Redis 的事务满足一致性和隔离性,但是原子性和持久性就不支持了。下面详细分析。
原子性:
定义:
- 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。
- 事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
但是在上面的介绍中, Redis在执行事务过程中,若是执行命令错误(非语法错误),将继续执行其他命令,很明显不符合原子性定义
持久性:
定义:
- 在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚
Redis采用不同的持久化规则,结果不同,若使用RDB模式和always策略除外的AOF 模式,Redis将会有丢失数据的风险,不具有持久性,
若采用AOF的 always策略, 则命令将同步到硬盘中,具有持久性
隔离性 :
定义:
- 隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作
通俗点将就是多个事务并发执行互不干扰。由于 Redis 使用的单线程方式来执行事务的且可以保证在整个事务执行期间不会中断去执行其他命令,所以 Redis 的事务总是可以保证隔离性的。
一致性 :
定义:
- 一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。 (即执行结果满足期望,)
首先,如果一个事务的指令结果全部执行成功,则数据库的状态满足一致性
如果一个事务的指令有错误,则数据库的状态也满足一致性
最后,如果事务运行到某条指令时,进程被kill掉了,那么要分下面几种情况讨论:
-
如果当前redis采用的是内存模式,那么重启之后redis数据库是空的,那么满足一致性条件
-
如果当前采用RDB模式存储的 ,在执行事务时,Redis 不会中断事务去执行保存 RDB 的工作 ,所以在重启后,会恢复到事务开启前的状态
-
如果当前采用的是AOF存储的,那么可能事务的内容还未写入到AOF文件,那么此时肯定是满足一致性的,但是事务的内容有部分写入到AOF文件中,那么这部分的数据是不确定的了, 不满足一致性
那么需要用工具把AOF中事务执行部分成功的指令移除,这时,移除之后的AOF文件也是满足一致性的
所以,总体redis事务满足一致性约束
4. 使用Redis命令实现分布式锁
-
使用 setnx 设置一个公共锁
setnx lock-key value
利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功
- 对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
- 对于返回设置失败的,不具有控制权,排队或等待
操作完毕通过del操作释放锁
业务场景:
解决多个微服务同时读取同一个库存,并进行修改,解决同步问题
但是此时又有问题,由于锁操作由用户控制加锁解锁,必定会存在加锁后未解锁的风险 .
例如某个用户操作时对应客户端宕机,且此时已经获取到锁。如何解决?
解决方案:
-
使用 expire 为锁key添加时间限定,到时不释放,放弃锁
expire lock-key second pexpire lock-key milliseconds
由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。
- 例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
- 测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
- 锁时间设定推荐:最大耗时120%+平均网络延迟110%
- 如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可
相关框架: redssion