Redis实现乐观锁

2023年 9月 25日 55.7k 0

系列文章目录

(一)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软件,方便区分)。

image-20230923120208524

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

相关文章

服务器端口转发,带你了解服务器端口转发
服务器开放端口,服务器开放端口的步骤
产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
如何使用 WinGet 下载 Microsoft Store 应用
百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

发布评论