趣文网 > 作文大全

「玩转Redis面试第2讲」面试官再问Redis事务把这篇文章扔给他

2020-11-25 05:15:01
相关推荐

1. Redis事务生命周期

开启事务:使用MULTI开启一个事务命令入队列:每次操作的命令都会加入到一个队列中,但命令此时不会真正被执行提交事务:使用EXEC命令提交事务,开始顺序执行队列中的命令2. Redis事务到底是不是原子性的?

先看关系型数据库ACID 中关于原子性的定义:

原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

官方文档对事务的定义:

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。EXEC 命令负责触发并执行事务中的所有命令:如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。官方认为Redis事务是一个原子操作,这是站在执行与否的角度考虑的。但是从ACID原子性定义来看,严格意义上讲Redis事务是非原子型的,因为在命令顺序执行过程中,一旦发生命令执行错误Redis是不会停止执行然后回滚数据。

3. Redis为什么不支持回滚(roll back)?

在事务运行期间虽然Redis命令可能会执行失败,但是Redis依然会执行事务内剩余的命令而不会执行回滚操作。如果你熟悉mysql关系型数据库事务,你会对此非常疑惑,Redis官方的理由如下:

只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。支持事务回滚能力会导致设计复杂,这与Redis的初衷相违背,Redis的设计目标是功能简化及确保更快的运行速度。

对于官方的这种理由有一个普遍的反对观点:程序有bug怎么办?但其实回归不能解决程序的bug,比如某位粗心的程序员计划更新键A,实际上最后更新了键B,回滚机制是没法解决这种人为错误的。正因为这种人为的错误不太可能进入生产系统,所以官方在设计Redis时选用更加简单和快速的方法,没有实现回滚的机制。

4. Redis事务失败场景

有三种类型的失败场景:

(1)在事务提交之前,客户端执行的命令缓存(队列)失败,比如命令的语法错误(命令参数个数错误,不支持的命令等等)。如果发生这种类型的错误,Redis将向客户端返回包含错误提示信息的响应,同时Redis会清空队列中的命令并取消事务。

127.0.0.1:6379> set name xiaoming # 事务之前执行OK127.0.0.1:6379> multi # 开启事务OK127.0.0.1:6379> set name zhangsan # 事务中执行,命令入队列QUEUED127.0.0.1:6379> setset name zhangsan2 # 错误的命令,模拟失败场景(error) ERR unknown command `setset`, with args beginning with: `name`, `zhangsan2`,127.0.0.1:6379> exec # 提交事务,发现由于上条命令的错误导致事务已经自动取消了(error) EXECABORT Transaction discarded because of previous errors.127.0.0.1:6379> get name # 查询name,发现未被修改"xiaoming"(2)事务提交后开始顺序执行命令,之前缓存在队列中的命令有可能执行失败。

127.0.0.1:6379> multi # 开启事务OK127.0.0.1:6379> set name xiaoming # 设置名字QUEUED127.0.0.1:6379> set age 18 # 设置年龄QUEUED127.0.0.1:6379> lpush age 20 # 此处仅检查是否有语法错误,不会真正执行QUEUED127.0.0.1:6379> exec # 提交事务后开始顺序执行命令,第三条命令执行失败1) OK2) OK3) (error) WRONGTYPE Operation against a key holding the wrong kind of value127.0.0.1:6379> get name # 第三条命令失败没有将前两条命令回滚"xiaoming"(3)由于乐观锁失败,事务提交时将丢弃之前缓存的所有命令序列。通过开启两个redis客户端并结合watch命令模拟这种失败场景。

# 客户端1127.0.0.1:6379> set name xiaoming # 客户端1设置nameOK127.0.0.1:6379> watch name # 客户端1通过watch命令给name加乐观锁OK# 客户端2127.0.0.1:6379> get name # 客户端2查询name"xiaoming"127.0.0.1:6379> set name zhangsan # 客户端2修改name值OK# 客户端1127.0.0.1:6379> multi # 客户端1开启事务OK127.0.0.1:6379> set name lisi # 客户端1修改nameQUEUED127.0.0.1:6379> exec # 客户端1提交事务,返回空(nil)127.0.0.1:6379> get name # 客户端1查询name,发现name没有被修改为lisi"zhangsan"在事务过程中监控的key被其他客户端改变,则当前客户端的乐观锁失败,事务提交时将丢弃所有命令缓存队列。

5. Redis事务相关命令

(1)WATCH

可以为Redis事务提供 check-and-set (CAS)行为。被WATCH的键会被监视,并会发觉这些键是否被改动过了。如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败。

(2)MULTI

用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行,而是被放到一个队列中,当 EXEC命令被调用时, 所有队列中的命令才会被执行。

(3)UNWATCH

取消 WATCH 命令对所有 key 的监视,一般用于DISCARD和EXEC命令之前。如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。因为 EXEC 命令会执行事务,因此 WATCH 命令的效果已经产生了;而 DISCARD 命令在取消事务的同时也会取消所有对 key 的监视,因此这两个命令执行之后,就没有必要执行 UNWATCH 了。

(4)DISCARD

当执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空,并且客户端会从事务状态中退出。

(5)EXEC

负责触发并执行事务中的所有命令:

如果客户端成功开启事务后执行EXEC,那么事务中的所有命令都会被执行。

如果客户端在使用MULTI开启了事务后,却因为断线而没有成功执行EXEC,那么事务中的所有命令都不会被执行。需要特别注意的是:即使事务中有某条/某些命令执行失败了,事务队列中的其他命令仍然会继续执行,Redis不会停止执行事务中的命令,而不会像我们通常使用的关系型数据库一样进行回滚。

阅读剩余内容
网友评论
相关内容
延伸阅读
小编推荐

大家都在看

我的心愿400字作文 青春梦想作文600字 开学了的作文怎么写 爱国情怀作文800字 女生被打屁股的作文 消防安全在身边作文 我开学了作文300字 感悟类作文600字初中 奋斗青春励志作文800字 窗外作文600字初二 春雨作文400字左右 高中语文作文多少分 爱国作文300字左右 我理想的职业 英语作文 我学会了350字作文 生活的启示600字作文 我学会了作文350字 让道德之花绽放作文 我们眼中的缤纷世界作文 奇妙的想象作文200字 生活需要友情作文600字 介绍自己的家英语作文 美丽的瞬间600字作文 家乡美食作文300字 感恩母亲作文800高中 幸福作文唯美开头结尾 读懂为话题的作文 包粽子的方法作文 被男友打屁股作文 一封书信作文