一个Redis事务,可以让你征服面试官半小时。
理解原子性
我们知道,java中的 i++ 或者 ++i 这些自增操作不具备原子性,因为自增操作在我们代码层面是一个指令,但是在 jvm 底层,它分为这两个步骤:
- 从局部变量表中读取 i 的值压入操作数栈
- 将局部变量表中 i 的值加1
若 jvm 在执行第 1 步的时候,其他线程先于这个线程改变了 i 的值,然后执行第二步后得出的值就不是我们希望的。
在 redis 中也有类似的自增操作,如我们之前学的 string 数据类型中的 incr 指令,它可以对一个 integer 类型的值加1。但是 incr 指令与 java 自增不一样的是,它是一个原子操作,因为 incr 是调用操作系统底层的指令,它不会被其他线程打断。所以 incr 指令具有原子性。
为什么叫原子性?
原子性其实就是有类似原子的性质,我们知道原子中有质子和中子,他们构成了原子,不可分割,如果分割了,就不是一个原子了,是不是很好理解。整理redis的同时也整理了最近两个月最新的面试题。需要的朋友可以点击:这个,点这个!!,备注:csdn。
什么是事务
事务就是指一系列操作,这些操作要么同时成功,要么同时失败,它是一种原子操作。原子就是一个整体,不可分割,事务中的这些操作也是一样。
假设有这样一个场景:张三向李四转账 100 块钱,最少需要经历两个步骤
- 张三的银行卡中钱减100块
- 李四的银行卡中钱加100块
在没有事务的情况下,可能会导致 1 号操作执行成功,2 号操作执行失败,那结果是张三的钱少了 100 块,李四的钱却没增加,张三的 100 块凭空消失了,这肯定会出问题,所以这两部操作必须要放在事务中执行,要么转账成功,要么转账失败,这就是事务的作用。
redis事务
redis 是单线程的程序,每个指令都保证原子性,但是不保证多个指令的原子性。
当有多个客户端连接同一个 redis 时,客户端之间会竞争cpu资源,来争夺 rdis 指令的执行权,所以如果一个 client 要执行一系列指令时,可能会被其他客户端打断。
要使多个指令也具有原子性,可以使用 redis 事务
redis 事务保证两点:
- 执行一个事务时,所有的命令都会被顺序执行,且不会被其他客户端打断
- 执行一个事务时,所有命令要么同时成功,要么同时失败
如果 redis 开启 AOF 模式,在执行 redis 事务之前,redis 会先把事务写在磁盘中再执行命令。如果事务执行到一半,redis 进程突然就挂了,那么就会导致事务中只有一部分指令执行成功,还有一部分指令还未执行。这种情况下,在 redis 下一次启动时会检测到错误,并退出 redis。使用 redis-check-aof 工具可以对 AOF 文件进行修复,删除掉磁盘中的未执行成功的事务,然后重新启动加载 AOF 文件,redis 就恢复到事务没有执行创建的状态,就可以正常启动了
事务相关的命令
和 redis 事务有关的指令有:
- multi
- exec
- discard
下面开始介绍每个命令
multi
使用 multi 指令来开启事务
127.0.0.1:6379> set zhangsan 1000
OK
127.0.0.1:6379> set lisi 1000
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby zhangsan -100
QUEUED
127.0.0.1:6379> incrby lisi 100
QUEUED
multi 指令返回 ok,证明事务开启成功。
开启事务后,接下来执行每条正确的指令都返回 QUEUE,代表命令被放入等待队列,但并没有执行。
exec
使用 exec 命令来执行事务
127.0.0.1:6379> set zhangsan 1000
OK
127.0.0.1:6379> set lisi 1000
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby zhangsan -100
QUEUED
127.0.0.1:6379> incrby lisi 100
QUEUED
127.0.0.1:6379> exec # 执行事务
1) (integer) 900
2) (integer) 1100
使用 exec 执行事务后,会按顺序返回事务中每天指令的返回结果。
还整理了Java核心的知识点,大家也可以看看。需要的朋友可以点击:这个,点这个!!,备注:csdn。
discard
如果事务创建成功,但你又不想执行了,使用 discard 命令来取消
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> incrby zhangsan -100
QUEUED
127.0.0.1:6379> incrby lisi 100
QUEUED
127.0.0.1:6379> discard # 取消事务
OK
redis 事务乐观锁
乐观锁,即认为所有时候都不会出现问题,它不会去加锁,他会在修改数据的时候判断这个数据是否被改动过,如果没有被其他人改动过,就更新数据。
在 redis 中也有乐观锁的操作,来防止数据出现问题,现在有如下一个场景:
你想通过事务对 redis 中的 key/value 进行修改,在没有执行 exec 命令之前,这时候另一个 client 修改了这个 key/value,这样再执行 exec 后可能会出现问题。这种情况下用 redis 的乐观锁操作就可以很轻松解决
在 redis 中有一种 check and set(CAS)的乐观锁操作,相关命令如下
- watch
- unwatch
watch
通过 watch 命令来监视这个 key/value,如果发现该 key/value 被修改,则调用 exec 时事务不会被执行
127.0.0.1:6379> watch zhangsan lisi # 监视 zhangsan 和 lisi
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby zhangsan -100
QUEUED
127.0.0.1:6379> incrby lisi 100
QUEUED
================ 这时候zhangsan的值被其他客户端修改=======================
127.0.0.1:6379> exec # 事务执行失败
(nil)
unwatch
取消监视,与 watch 命令相对
在执行 exec 后,不管是否执行成功,watch 命令都会 unwatched
客户端退出后,watch 命令也会 unwatched
127.0.0.1:6379> watch zhangsan lisi # 监视 zhangsan 和 lisi
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby zhangsan -100
QUEUED
127.0.0.1:6379> incrby lisi 100
QUEUED
127.0.0.1:6379> unwatch # 取消监视
OK
和事务相关的错误
在 redis 事务中,可能会发生两种错误
1、编译型错误
在 multi 后,exec 调用之前,由于输入不正确的 redis 命令导致的错误
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sets zhangsan 22 # 事务中命令输错
(error) ERR unknown command `sets`, with args beginning with: `zhangsan`, `22`,
127.0.0.1:6379> set zhangsan 22
QUEUED
127.0.0.1:6379> exec # 事务执行不成功
(error) EXECABORT Transaction discarded because of previous errors.
在这种情况下事务不会执行成功
2、运行时错误
在 exec 调用之后,由于 redis 命令执行不成功导致的错误
127.0.0.1:6379> set test abc
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr test # 由于test中存的不是 integer,在exec时会出错
QUEUED
127.0.0.1:6379> set info aaa
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
从执行结果看出,在 exec 时,即使其中有命令出错,后面的命令不会被影响,依旧会被执行从这里也可以看出,redis 不支持回滚
最后提供免费的Java架构学习资料,学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。
点击:点这个!点这个!暗号:csdn。
还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书。
上一篇: 阿里面试官:什么是消息队列,为什么用消息队列,用了有什么问题
下一篇: js原生扫雷(第一版)