说明
- Redis-09-Redis事务
- 包括Redis事务特性、事务执行过程、事务冲突问题、连接超时问题、超卖问题、秒杀完库存遗留问题
- 官方文档:https://redis.io/documentation
- Redis命令参考:http://redisdoc.com/
核心概念
》什么是Redis事务?
- 串联多个命令,防止别的命令插队
- Redis事务跟MySQL不同,不具备ACID功能,仅仅是相当于一个批量组队操作或者类似于管道操作。
》Redis事务特性
- 单独隔离操作
- 没有隔离级别
- 不保证原子性
》Redis事务执行过程
- 组队阶段(multi)
- 开启事务后,将此后收到的所有的命令加入一个预执行命令组队队列,而不真正执行
- 未收到开始执行命令前,只要不报错就不影响整个队列命令的执行,反之任何一个命令报错就会结束组队,从而取消整个预处理命令队列
- 执行阶段(exec)
- 收到开始执行命令后,按预处理命令队列进行顺序执行,中间任意一个命令报错都不会影响整个组队的队列
- 撤销执行(discard)
- 收到撤销命令后,取消全部预处理命令队列中所有命令的执行
》事务冲突问题
- 悲观锁
- 所有人操作同一个锁,性能相对较差
- 比如可以用setnx方式实现
- 乐观锁
- 给数据增加版本号,但是不加锁,最终执行完业务逻辑,需要先修改版本号再提交事务,如果版本号与起初拿到的版本号不同则取消提交事务,从而整个命令执行失败
- 乐观锁适用于多读场景,相对能一定程度提高性能
- 乐观锁的实现方式
- 核心步骤:通过监视(watch)某个key来实现
- 先设置一个用于被监听的key
- 开启事务前先监视该key
- 注意:必须多个客户端并行执行事务时才有效果(这是废话)
- 退出监视(unwatch)
》连接超时问题
- 使用连接池pool
》超卖问题
- 开启事务multi
- 开启乐观锁,watch监视,只有事务成功后才成功
》秒杀完库存遗留问题
- 使用乐观锁会造成库存遗留问题
- 解决方案:使用lua脚本,将多个Redis命令一次提交给Redis执行
操作步骤
》事务执行场景
- 场景一:正常创建事务并执行
127.0.0.1:6379> flushdb OK 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> exec 1) OK 2) OK
- 场景二:创建事务后组队报错,整个事务执行失败
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k1 (error) ERR wrong number of arguments for 'set' command 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors.
- 场景三:创建事务后组队成功,执行报错,整个事务执行成功
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> incr k1 QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) ERR value is not an integer or out of range
- 场景四:创建事务后取消执行
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> discard OK 127.0.0.1:6379> keys * (empty list or set)
》事务冲突问题场景
- 乐观锁
- setnx实现,就不演示了
- 悲观锁
- 客户端A(先执行的所以最终成功)
127.0.0.1:6379> set k 1 OK 127.0.0.1:6379> watch k OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr k QUEUED 127.0.0.1:6379> exec 1) (integer) 2
- 客户端B(后执行的所以最终失败)
127.0.0.1:6379> watch k OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr k QUEUED 127.0.0.1:6379> exec (nil)
- 退出监视
127.0.0.1:6379> unwatch OK
- 客户端A(先执行的所以最终成功)