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

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 【玩转Redis面试第2讲】面试官再问Redis事务把这篇文章扔给他

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 # 事务之前执行
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set name zhangsan # 事务中执行,命令入队列
QUEUED
127.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 # 开启事务
OK
127.0.0.1:6379> set name xiaoming # 设置名字
QUEUED
127.0.0.1:6379> set age 18 # 设置年龄
QUEUED
127.0.0.1:6379> lpush age 20 # 此处仅检查是否有语法错误,不会真正执行
QUEUED
127.0.0.1:6379> exec # 提交事务后开始顺序执行命令,第三条命令执行失败
1) OK
2) OK
3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get name # 第三条命令失败没有将前两条命令回滚
"xiaoming"


(3)由于乐观锁失败,事务提交时将丢弃之前缓存的所有命令序列。通过开启两个redis客户端并结合watch命令模拟这种失败场景。

# 客户端1
127.0.0.1:6379> set name xiaoming # 客户端1设置name
OK
127.0.0.1:6379> watch name # 客户端1通过watch命令给name加乐观锁
OK
# 客户端2
127.0.0.1:6379> get name # 客户端2查询name
"xiaoming"
127.0.0.1:6379> set name zhangsan # 客户端2修改name值
OK
# 客户端1
127.0.0.1:6379> multi # 客户端1开启事务
OK
127.0.0.1:6379> set name lisi # 客户端1修改name
QUEUED
127.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不会停止执行事务中的命令,而不会像我们通常使用的关系型数据库一样进行回滚。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
NoSQL Redis 数据库
10- 你们用过Redis的事务吗 ? 事务的命令有哪些 ?
```markdown Redis事务包括MULTI、EXEC、DISCARD、WATCH四个命令。虽具备事务功能,但在实际开发中使用较少。 ```
43 7
|
1月前
|
NoSQL Redis
Redis事务:保证数据操作的一致性和可靠性
Redis事务:保证数据操作的一致性和可靠性
|
5天前
|
监控 NoSQL 关系型数据库
深入浅出Redis(五):Redis的事务机制与ACID原则
深入浅出Redis(五):Redis的事务机制与ACID原则
|
5天前
|
监控 NoSQL 关系型数据库
Redis 事务 与 管道
Redis 事务 与 管道
9 0
|
8天前
|
分布式计算 监控 大数据
《吊打面试官》- 大数据工程师50道中大厂面试真题保姆级详解
《吊打面试官》- 大数据工程师50道中大厂面试真题保姆级详解
22 1
《吊打面试官》- 大数据工程师50道中大厂面试真题保姆级详解
|
17天前
|
缓存 NoSQL Java
【Redis系列笔记】Redis事务
Redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
53 3
|
20天前
|
监控 Java 测试技术
面试准备不充分,被Java守护线程干懵了,面试官主打一个东西没用但你得会
面试准备不充分,被Java守护线程干懵了,面试官主打一个东西没用但你得会
29 1
|
24天前
|
NoSQL MongoDB Redis
Python与NoSQL数据库(MongoDB、Redis等)面试问答
【4月更文挑战第16天】本文探讨了Python与NoSQL数据库(如MongoDB、Redis)在面试中的常见问题,包括连接与操作数据库、错误处理、高级特性和缓存策略。重点介绍了使用`pymongo`和`redis`库进行CRUD操作、异常捕获以及数据一致性管理。通过理解这些问题、易错点及避免策略,并结合代码示例,开发者能在面试中展现其技术实力和实践经验。
315 8
Python与NoSQL数据库(MongoDB、Redis等)面试问答
|
1月前
|
缓存 NoSQL Java
面试官:Redis如何实现延迟任务?
延迟任务是计划任务,用于在未来特定时间执行。常见应用场景包括定时通知、异步处理、缓存管理、计划任务、订单处理、重试机制、提醒和数据采集。Redis虽无内置延迟任务功能,但可通过过期键通知、ZSet或Redisson实现。然而,这种方法精度有限,稳定性较差,适合轻量级需求。Redisson的RDelayedQueue提供更简单的延迟队列实现。
365 9
|
1月前
|
监控 NoSQL Java