当前位置: 首页 > backend >正文

图解 Redis 事务 ACID特性 |源码解析|EXEC、WATCH、QUEUE

写在前面

Redis 通过 MULTIEXECWATCH 等命令来实现事务功能。Redis的事务是将多个命令请求打包,然后一次性、按照顺序的执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而该去执行其他客户端的命令请求。 就像下面这样:

redis> MULTI
OK
redis(TX)> SET fanone 1
QUEUED
redis(TX)> SET fantwo 2
QUEUED
redis(TX)> GET fanone
QUEUED
redis(TX)> EXEC
1) OK
2) OK
3) "1"

本文我们就从redis的事务执行过程以及ACID四个方面来介绍redis的事务

事务实现

在这里插入图片描述
从上面的例子我们可以知道,redis的事务是从MULTI命令开始的,所有的命令都会按照FIFO的顺序进入一个QUEUE队列中,当执行EXEC操作后才将这些命令逐步执行。

MULTI

事务队列是一个multiCmd类型的数组,数组中的每个multiCmd结构都保存了一个已入队命令的相关信息,包括指向命令实现函数的指针、命令的参数,以及参数的数量:

typedef struct multiCmd{robj **argv; 	// 参数int argc; 		// 参数数量struct redisCommand *cmd // 命令指针
} multiCmd;

事务队列以FIFO先进先出的方式保存入队的命令,还是用上面的例子来画一个原型图:

在这里插入图片描述
当一个处于事务状态的客户端向服务器发送EXEC命令的时候,这个EXEC命令将立即被服务器执行。服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令,最后将执行命令所得的结果全部返回给客户端。EXEC的伪代码如下:

void EXEC() {std::vector<Reply> reply_queue;  // 创建空白的回复队列for (const auto& cmd : client.mstate.commands) { // 执行事务中的所有命令Reply reply = execute_command(cmd.command, cmd.argv, cmd.argc);reply_queue.push_back(reply);}client.flags &= ~REDIS_MULTI; // 清理事务状态client.mstate.count = 0;release_transaction_queue(client.mstate.commands);send_reply_to_client(client, reply_queue);// 发送回复给客户端
}

WATCH

接着我们来讲讲WATCH命令,其实这是一个乐观锁(optimistic locking),可以在EXEC命令执行之前,监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否有已经被修改过了的,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复

在这里插入图片描述
比如下面这个例子:
在这里插入图片描述

时间客户端A客户端B
T1WATCH “fanone”
T2MULTI
T3SET “fanone” 1
T4SET “fanone” 2
T5EXEC

而在时间T4,客户端B修改了fanone键的值,当客户端A在T5执行EXEC命令的时候,服务器会发现WATCH监视的键fanone已经被修改了,因此服务器拒绝执行客户端A的事务,并且向客户端A返回空回复

每个Redis数据库都保存着一个watched_keys字典,这个字典的键是某个被WATCH命令监视的数据库键,而字典的值则是一个链表,链表中记录了所有监视数据库键的客户端。

typedef struct redisDb {dict *watched_keys; // 正在被WATCH命令监视的键 
}

在这里插入图片描述

ACID

Redis的事务是否符合ACID呢?

原子性 Atomicity

在这里插入图片描述

事务具有原子性是指,数据库将事务中的多个操作当作一个整体来执行,要么全部执行,要么全部不执行。对于Redis的事务功能来说,事务队列中的命令要么就都全部都执行,要么就一个都不执行,因此Redis的事务是具有原子性的。 比如以下成功执行的事务,事务中的所有命令都被执行:

redis> MULTI
OK
redis(TX)> SET fanone 1
QUEUED
redis(TX)> GET fanone
QUEUED
redis(TX)> EXEC
1) OK
2) "1"

与此相反,如果其中有一个命令是错误的,那么整个命令就不会执行。fanone这个key是一个string,并不是set,所以不能使用SADD命令执行。

redis> MULTI
OK
redis(TX)> SET fanone 1
QUEUED
redis(TX)> SADD fanone 2
QUEUED
redis(TX)> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value

那么大家会发现Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制,也就是rollback。

Redis的作者在事务功能的文档中也解释道,不支持事务回滚是因为这种复杂的功能和Redis追求简单高效的设计主旨不相符。

在这里插入图片描述

一致性 Consistency

事务具有一致性指的是,如果数据库在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据库也应该依然是一致的。 而这个一致指的是 数据符合数据库本身的定义和要求,没有包含非法或者无效的错误数据

比如执行完事务不会多一个之前没有的命令,或者某个key是string类型,不会变成set类型。我们用上面同一个例子来说明:

redis> MULTI
OK
redis(TX)> SET fanone 1
QUEUED
redis(TX)> SADD fanone 2
QUEUED
redis(TX)> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value

fanone 这个key是string类型,但是无法通过SADD命令将fanone变成set类型。虽然在入队列的时候,redis没有报错,但是在EXEC的时候,redis报了,所以这个key一开始是string类型,事务执行完后也会是string类型,事务执行前后保持了一致。

在这里插入图片描述

隔离性 Isolation

事务的隔离型是指**即使数据库中有多个事务并发的执行,各个事务之间也不会互相影响,并且在并发状态下执行的事务和穿行执行的事务的结果是完成相同的。**

在这里插入图片描述

因为Redis是使用单线程的方式来执行事务以及事务队列中的命令,并且在服务器稳定的情况下,执行事务不会中断,因此,redis的事务总是串行的方式执行的,所以具备隔离性。
在这里插入图片描述

持久性 Durability

事务的持久性值得是当一个事务执行完毕的时候,执行这个事务所得的结果已经被保存到永久性存储介质里面了,即使服务器在事务执行完毕之后停机,执行事务所得的结果也不会丢失。

由于Redis的事务比较简洁,没有提供持久化的能力,所以Redis的事务是依赖于Redis所使用的持久话模式,也就是AOF、RDB,我们一个个来讨论

  • 当服务器无持久化运行的时候,事务不具备持久性,一旦服务器宕机,事务数据将会丢失。

  • 当服务器在RDB持久化模式下运作的时候,服务器只会在特定的保存条件下满足,比如使用BGSAVE命令,队数据库进行保存,但是异步执行的BGSAVE也不能保证,第一时间保存在硬盘中,因此RDB持久化模式下事务不具备持久性
    在这里插入图片描述

  • 而当服务器运行在AOF持久话模式下,并且appendfsync选项是always的时候,服务器总会在执行完命令之后调用同步sync函数,将命令数据保存在硬盘中,而此时事务具备持久性,其他选择比如everysec或者no的时候都不具备持久性。

在这里插入图片描述

http://www.xdnf.cn/news/2301.html

相关文章:

  • 【数据湖】Time Travel时间旅行
  • 每日学习Java之一万个为什么?
  • 3.1 掌握RDD的创建
  • 英语学习4.26
  • 进行物联网安全PoC时的注意事项
  • 【Java-Day 1】开启编程之旅:详解Java JDK安装、环境配置与运行HelloWorld
  • 用c语言实现——一个动态顺序存储的串结构
  • 山东大学软件学院项目实训-基于大模型的模拟面试系统-前端美化滚动条问题
  • 2025年4月25日第一轮
  • Vue Composition API 与 Options API:全面对比与使用指南
  • HTML快速入门-4:HTML <meta> 标签属性详解
  • 【漫话机器学习系列】224.双曲正切激活函数(Hyperbolic Tangent Activation Function)
  • 现在流行的linux面板管理工具
  • 三款实用工具推荐:图片无损放大+音乐格式转换+音视频格式转换!
  • TCGA 数据下载与生存分析 //todo
  • FreeRTOS事件标志组详解:高效的任务间通知机制
  • 结合五层网络结构讲一下用户在浏览器输入一个网址并按下回车后到底发生了什么?
  • 机器学习基础理论 - 频率派 vs 贝叶斯派
  • Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?
  • 什么是Lua模块?你会如何使用NGINX的Lua模块来定制请求处理流程?
  • Spring 学习笔记之 @Transactional 异常不回滚汇总
  • 【机器学习-线性回归-3】深入浅出:简单线性回归的概念、原理与实现
  • 【VMware】虚拟机如何扩展存储
  • LLM基础之源码一
  • asammdf 库的依赖项和安装指南
  • 【数据结构】优先级队列
  • 【人工智能之大模型】详述大模型中流水线并行(Pipeline Parallelism)的​GPipe推理框架?
  • 【树莓派 PICO 2 测评】ADC 水位监测系统
  • ZBrush2025.1.3 中文版【ZBrush2025版下载】附安装教程
  • tkinter中Listbox列表框常用的操作方法