系列文章目录
(一)Redis(windows+Linux)安装及入门教程 - 掘金 (juejin.cn)
(二)Redis中的五大数据类型 - 掘金 (juejin.cn)
(三)Redis中的三种特殊类型 - 掘金 (juejin.cn)
前言
本文先讲解了Redis中的事务,然后再结合乐观锁讲解。
乐观锁,在开始数据更新操作之前,先把数据的当前状态读取出来,然后基于这个状态进行数据更新,更新时检查数据的当前状态是否还和刚才读取的状态一致,如果一致则执行更新,如果不一致(也就是在这期间有其他操作修改了这个数据)则拒绝更新。
一、Redis中的事务🍙
Redis事务官方文档-Transactions | Redis
Redis中的WATCH命令官方文档-WATCH | Redis
Redis中的事务是一组命令的集合,可以一次性、顺序性地执行多个命令,而不会被其他命令插入打断。
reids事务是没有隔离级别的概念
Redis单条命令是保持原子性的,但是事务不保证原子性
Redis事务的3个阶段:
- 开始事务(MULTI)
- 命令入队
- 执行事务(EXEC)
命令演示
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379(TX)> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK
2) OK
3) OK
4) "v1"
所有指令没有立即执行,而是全部入队后,最后再执行
放弃事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set key1 value1
QUEUED
127.0.0.1:6379(TX)> DISCARD #取消事务
OK
127.0.0.1:6379> get ket1 #事务队列中的所有指令都不会执行
(nil)
使用事务时可能会遇上以下两种错误:
事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用
maxmemory
设置了最大内存限制的话)。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> GEOADD k3 #错误命令
(error) ERR wrong number of arguments for 'geoadd' command
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec #执行事务报错,所有命令都没有执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)
命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面。
127.0.0.1:6379> set k1 hello #设置一个字符串
OK
127.0.0.1:6379> get k1
"hello"
127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379(TX)> INCR k1 #使用数字自增
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range #报错
2) OK
3) OK
4) "v3"
Redis不支持回滚
如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
二、实现乐观锁🥟
加锁
悲观锁:
- 很悲观,认为任何情况下都会出现问题,无论做什么都会加锁
乐观锁:
- 很乐观,认为什么情况都不会出问题,所以不会上锁!更新数据的时候去判断一下,再在此期间是否有人修改过数据
- 获取version字段值
- 更新时比较version
Redis监视测试
正常执行案例
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
测试多客户端操作情况,使用watch代替乐观锁操作
现在同时开启两个会话(用了两个SSH软件,方便区分)。
1号会话,监视money,然后开启事务,不执行事务
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10 #执行之前另外一个线程修改值,这个时候会导致事务执行失败
QUEUED
2号会话,修改money的值
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
1号会话执行事务,事务执行失败
127.0.0.1:6379(TX)> EXEC #执行失败
(nil)
重新执行事务要先解锁,再加锁获取最新值
#1会话
127.0.0.1:6379> UNWATCH #如果发现事务提交失败,就先解锁
OK
127.0.0.1:6379> WATCH money #获取最新的值,再次监视
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 1
QUEUED
127.0.0.1:6379(TX)> INCRBY out 1
QUEUED
127.0.0.1:6379(TX)> exec #比对值是否发送变化
1) (integer) 999
2) (integer) 21
参考资料
【狂神说Java】Redis最新超详细版教程通俗易懂_哔哩哔哩_bilibili