一箫一剑平生意,负尽狂名十五年。这篇文章主要讲述鸡肋的Redis事务相关的知识,希望能为你提供帮助。
没错,Redis也有事务管理,但是功能很简单,在正式开发中也并不推荐使用。但是面试中有可能会问到,所以本文简单谈一谈Redis的事务。
通过这篇文章,你会了解
- Redis为什么要提供事务?
- Redis事务基本指令和使用方法
- CAS乐观锁是什么?
- Redis事务为什么不支持回滚?
get
、set
、mget
、mset
等指令。在涉及到多个命令的时候,如果需要把多个命令设置为一个不可分割的处理序列,就需要用到事务了。
比如,招财和陀螺各有100元,招财给陀螺转了10元,这时候需要在Redis中把招财的金额总数-10,同时需要把陀螺的金额总数+10。这两个操作要么同时成功,要么同时失败,这时候就需要事务了。
2. 事务的用法 2.1 5个基本指令
Redis提供了以下5个基本指令,先混个眼熟就行,接下来在案例中进行实操,想记不住都难
- MULTI
- EXEC
- DISCARD
- WATCH
- UNWATCH
命令 | 格式 | 作用 | 返回值 |
---|---|---|---|
MULTI | MULTI | 显式开启Redis事务,后续命令将排队,等候使用EXEC进行原子执行 | always OK. |
EXEC | EXEC | 执行事务中的commands 队列,恢复连接状态。如果WATCH 在之前被调用,只有监测中的Keys 没有被修改,命令才会被执行,否则停止执行(详见下文,CAS 机制) |
成功: 返回数组 —— 每个元素对应着原子事务中一个 command 的返回结果;
失败: 返回NULL (Ruby 返回nil );
|
DISCARD | DISCARD | 清除事务中的commands 队列,恢复连接状态。如果WATCH 在之前被调用,释放监测中的Keys |
always OK. |
WATCH | WATCH key [key ...] | 将给出的Keys 标记为监测态,作为事务执行的条件 |
always OK. |
UNWATCH | UNWATCH | 清除事务中Keys 的监测态,如果调用了EXEC 或者 DISCARD ,则没有必要再手动调用UNWATCH |
always OK. |
案例场景:招财和陀螺各有100元,招财给陀螺转了10元,这时候需要在Redis中把招财的金额-10,同时需要把陀螺的金额+10。
2.2.1 事务提交我们首先为陀螺和招财初始化自己的金额;然后使用
MULTI
命令显式开启
Redis事务。 该命令总是直接返回OK。此时用户可以发送多个指令,Redis不会立刻执行这些命令,而是将这些指令依次放入
当前事务的指令队列中;EXEC
被调用后,所有的命令才会被依次执行。# 给陀螺初始化100元
127.0.0.1:6379>
set tuoluo 100
OK
# 给招财初始化100元
127.0.0.1:6379>
set zhaocai 100
OK
# 显式开启事务
127.0.0.1:6379>
MULTI
OK
# 给陀螺增加10元
127.0.0.1:6379(TX)>
INCRBY tuoluo 10
QUEUED
# 给招财减少10元
127.0.0.1:6379(TX)>
DECRBY zhaocai 10
QUEUED
# 执行事务中的所有指令(提交事务)
127.0.0.1:6379(TX)>
EXEC
1) (integer) 110
2) (integer) 90
2.2.2 嵌套事务Redis不支持嵌套事务,多个
MULTI
命令和单个MULTI
命令效果相同。# 第一次开启事务
127.0.0.1:6379>
MULTI
OK
# 尝试嵌套事务
127.0.0.1:6379(TX)>
MULTI
(error) ERR MULTI calls can not be nested
# 仍然处于第一个事务当中
127.0.0.1:6379(TX)>
2.2.3 放弃事务如果开启事务之后,中途后悔了怎么办?调用
DISCARD
可以清空事务中的指令队列,退出事务。127.0.0.1:6379>
MULTI
OK
# 在事务中调用DISCARD指令
127.0.0.1:6379(TX)>
DISCARD
OK
# 会退出当前事务
127.0.0.1:6379>
2.2.4 watch指令假如我们在一个客户端连接中开启了事务,另一个客户端连接修改了这个事务涉及的变量值,将会怎样?
文章图片
client1开启了一个转账的事务,事务开始时招财和陀螺各自拥有100元,在执行
EXEC
指令之前,client2将陀螺的余额添加了10元,此时执行EXEC
之后,陀螺最终的金额为120元,招财为90元。很明显,这种情况下存在数据安全问题。
为此Redis提供了
WATCH
的指令,该指令可以为Redis事务提供CAS乐观锁
行为,即多个连接同时更新变量的时候,会和变量的初始值进行比较,只在这个变量的值没有被修改的情况下才会更新成新的值。2.2.4.1 WATCH用法
对应我们的案例,我们可以使用
WATCH
监听一个或多个key,如果开启事务之前,至少有一个被监视的key在EXEC
执行之前被修改了,那么整个事务都会被取消,直接返回nil
(见下面的案例)。UNWATCH
是WATCH
的反操作。文章图片
2.2.4.2 CAS机制
CAS需要三个变量信息,分别是内存位置(java中的内存地址,V),旧的预期值(A)和新值(B)。CAS执行时,当且仅当V和预期值A相等时,更新V的值为新值B,否则不执行更新。
文章图片
3. 事务执行出错怎么办事务执行时可能遇到问题,按照发生的时机不同分为两种:
- 执行
EXEC
之前 - 执行
EXEC
之后
比如指令存在语法错误(参数数量不对,指令单词拼错)导致不能进入
commands
队列,这一步主要是编译错误,还未到运行时。127.0.0.1:6379>
MULTI
OK
127.0.0.1:6379(TX)>
SET tuoluo
(error) ERR wrong number of arguments for set command
127.0.0.1:6379(TX)>
EXEC
(error) EXECABORT Transaction discarded because of previous errors.
这种情况下事务会执行失败,队列中的所有指令都不会得到执行。
3.2 执行EXEC之后发生错误
这种错误往往是类型错误,比如对String使用了Hash的命令,这是运行时错误,编译期间不会出错.
127.0.0.1:6379>
MULTI
OK
127.0.0.1:6379(TX)>
SET tuoluo 100
QUEUED
127.0.0.1:6379(TX)>
LPOP tuoluo
QUEUED
127.0.0.1:6379(TX)>
EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
我们发现,
SET tuoluo 100
的命令居然执行成功了,也就是在发生了运行时异常的情况下,错误的指令不会被执行,但是其他的命令不会受影响。文章图片
这种方式显然不符合我们对
原子性
的定义,也就是Redis的事务无法实现原子性,无法保证数据一致。针对这种缺陷,Redis官方也是做了说明的。
4. Redis事务为什么不支持回滚引自Redis官方文档。
文章图片
为了方便大家理解,我翻译一下就是:
Redis官方认为,只有在命令语法错误或者类型错误的时候,Redis命令才会执行失败。而且他们认为有这种错误的语法一般也不会进入到生产环境。而且不支持回滚可以使
这种说法多牛!如果出问题就是程序员的问题,写错了还让代码进入生产环境,那就是罪上加罪,你永远赖不着Redis官方。
【鸡肋的Redis事务】这可能就是不推荐使用Redis事务的原因了吧,鸡肋是一方面,万一被官方打脸了呢?所以Redis事务的知识稍微了解一下就好,面试被问到能回到上来就可以了。
5. 推荐阅读
- 就这?Redis持久化策略——AOF
- 就这?Redis持久化策略——RDB
- 到底应该先操作缓存还是先操作数据库?
推荐阅读
- [kubernetes] 交付dubbo之持续交付dubbo-monitor和dubbo-consumer(完结篇)
- kubesphere 3.1.1 部署有状态应用
- [kubernetes] 交付dubbo之jenkins联动docker
- [kubernetes] deployment
- linux开机破密码
- [kubernetes] 持久化存储之静态PV/PVC
- Linux CentOS7本地源编写
- 什么鬼,开了redis之后我的阿里云服务器被植入了挖矿病毒!
- 在Ubuntu20.04上安装Kubernetes-Kubeadm和Minikube