您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
Redis 事务
 
作者: hulunbao
   次浏览      
2020-11-18 
 
编辑推荐:

本文主要介绍了事务是什么?事务的特性、为什么使用事务以及使用事务的整个流程。
本文来自博客园,由火龙果软件Anna编辑、推荐。

事务

事务指的程序中一系列严密的逻辑操作,其中包含的操作必须要完成,否则在每个操作中的更改都会被撤销。

举个简单的例子:一群鸭子过河,要么都过去,要么都不过去。

事务的特性

原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。

一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。(可理解为:即A账户只要减去了100,B账户则必定加上了100)

隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

持久性(Durability):当事务正确完成后,它对于数据的改变是永久性的。

为什么使用事务

在传统的关系型数据库中,常常使用事务的ACID 性质来保证数据的一致性、完整性。

Redis事务

Redis事务:把多个redis命令放到队列,然后一次性的顺序执行。并且在执行过程中不会被中断,执行完所有队列命令后才执行其他客户端其他命令。

下面举个简单的例子,MULTI 开启一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令

一个事务从开始到执行经历三个阶段

开始事务

命令入队

执行事务

开始事务

MULTI 命令的执行标志着事务的开始,这个命令做的就是, 将客户端的 REDIS_MULTI 选项打开, 让客户端从非事务状态切换到事务状态。

命令入队

当客户端处于非事务状态下,所有发送给服务端的命令会立即被执行。

但客户端切换到事务状态后,服务端接受到客户端的命令不会立即执行,而是把这些命令放到事务队列里,然后返回 QUEUED , 表示命令已入队。

可以由一下流程图表示:

事务队列是一个数组, 每个数组项是都包含三个属性:

要执行的命令(cmd)

命令的参数(argv)

参数的个数(argc)

以上图命令为例子,那么程序将为客户端创建以下事务队列:

数组索引 cmd argv argc
0 SET ["name","xiaoming"] 2
1 SET ["age","25"] 2
2 INCR ["age"] 1

执行事务

客户端进入到事务状态后,客户端发送的命令不会直接被执行,而是会放到事务队列里。

但并不是所有命令都会放到事务队列,如 EXEC 、 DISCARD 、 MULTI 和 WATCH 这四个命令,无视事务状态,直接被服务器执行。

如果客户端正处于事务状态, 那么当 EXEC 命令执行时, 服务器根据客户端所保存的事务队列, 以先进先出(FIFO)的方式执行事务队列中的命令: 最先入队的命令最先执行, 而最后入队的命令最后执行。

执行事务中的命令所得的结果会以 FIFO 的顺序保存到一个回复队列中。

如上图,程序将为队列中的命令创建如下回复队列:

数组索引 回复类型 回复内容
0 status code reply OK
1 status code reply OK
2 integer reply 26

当事务队列里的所有命令被执行完之后, EXEC 命令会将回复队列作为自己的执行结果返回给客户端, 客户端从事务状态返回到非事务状态, 至此, 事务执行完毕。

事务过程伪代码:

def execute_transaction():

# 创建空白的回复队列
reply_queue = []

# 取出事务队列里的所有命令、参数和参数数量
for cmd, argv, argc in client.transaction_queue:

# 执行命令,并取得命令的返回值
reply = execute_redis_command(cmd, argv, argc)

# 将返回值追加到回复队列末尾
reply_queue.append(reply)

# 清除客户端的事务状态
clear_transaction_state(client)

# 清空事务队列
clear_transaction_queue(client)

# 将事务的执行结果返回给客户端
send_reply_to_client(client, reply_queue)

DISCARD、MULTI、WATCH命令

DISCRAD 命令用于取消一个事务, 它清空客户端的整个事务队列, 然后将客户端从事务状态调整回非事务状态, 最后返回字符串 OK 给客户端, 说明事务已被取消。

MULTI命令开启一个事务,Redis 的事务是不可嵌套的, 当客户端已经处于事务状态, 而客户端又再向服务器发送 MULTI 时, 服务器只是简单地向客户端发送一个错误, 然后继续等待其他命令的入队。 MULTI 命令的发送不会造成整个事务失败, 也不会修改事务队列中已有的数据。

WATCH命令只能在客户端进入事务状态之前执行, 在事务状态下发送 WATCH 命令会引发一个错误, 但它不会造成整个事务失败, 也不会修改事务队列中已有的数据(和前面处理 MULTI 的情况一样)

带 WATCH 的事务

WATCH 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。

如下图例子:

客户端A

此时客户端B修改了name

客户端A执行事务失败

watch命令实现

在每个代表数据库的 redis.h/redisDb 结构类型中, 都保存了一个 watched_keys 字典, 字典的键是这个数据库被监视的键, 而字典的值则是一个链表, 链表中保存了所有监视这个键的客户端。

如下图:

WATCH 命令的作用, 就是将当前客户端和要监视的键在 watched_keys 中进行关联。

举个例子,如果客户端client5执行 WATCH key1 key2 时,上图将变成下面这样。

通过 watched_keys 字典, 如果程序想检查某个键是否被监视, 那么它只要检查字典中是否存在这个键即可; 如果程序要获取监视某个键的所有客户端, 那么只要取出键的值(一个链表), 然后对链表进行遍历即可。

WATCH 触发

任何对Redis键值的修改操作成功后,multi.c/touchWatchedKey 函数都会被调用,它检查数据库的 watched_keys 字典, 看是否有客户端在监视已经被命令修改的键, 如果有的话, 程序将所有监视这个/这些被修改键的客户端的 REDIS_DIRTY_CAS 选项打开:

当客户端发送 EXEC 命令、触发事务执行时, 服务器会对客户端的状态进行检查:

如果客户端的 REDIS_DIRTY_CAS 选项已经被打开,那么说明被客户端监视的键至少有一个已经被修改了,事务的安全性已经被破坏。服务器会放弃执行这个事务,直接向客户端返回空回复,表示事务执行失败。

如果 REDIS_DIRTY_CAS 选项没有被打开,那么说明所有监视键都安全,服务器正式执行事务。

伪代码如下

def check_safety_before_execute_trasaction():

if client.state & REDIS_DIRTY_CAS:
# 安全性已破坏,清除事务状态
clear_transaction_state(client)
# 清空事务队列
clear_transaction_queue(client)
# 返回空回复给客户端
send_empty_reply(client)
else:
# 安全性完好,执行事务
execute_transaction()

最后,当一个客户端结束它的事务时,无论事务是成功执行,还是失败, watched_keys 字典中和这个客户端相关的资料都会被清除。

Redis事务的ACID性质

Redis事务具有原子性、一致性、隔离性,并且当Redis运行在某种特定的持久化模式下,也具有持久性。

原子性

Redis事务,事务队列中的命令要么全部执行,要么一个都不执行。所以Redis事务具有原子性。

Redis事务与传统的关系型数据库事务最大的区别在于:Redis事务不支持回滚机制(rollback),即使事务队列中的命令执行期间出现了错误,整个事务会继续下去,直到事务队列里面的命令都执行完。

如下例子:

一致性

Redis通过错误检测和简单的设计保证事务一致性。

入队错误

如果一个事务在入队过程中,出现了命令不存在,或者命令格式不正确,Redis拒绝执行这个事务。

如下例子

因为拒掉入队错误的事务,所以一致性不会被入队错误的事务影响。

执行错误

事务执行过程中,错误的命令会被识别出来,并进行相应的错误处理,所以一致性不会受到影响。

服务器停机

如果服务器运行在没有持久化的内存模式下,那么重启后数据库是空白的,因此数据是一致的。

如果服务器运行在RDB或者AOF模式下,中途停机可以用两种模式恢复,如果找不到恢复文件,数据库是空白的,数据是一致的。

隔离性

Redis采用的单线程的方式实行事务,并且在事务执行过程中不会对事务中断,因此Redis事务以串行的方式运行的,所以Redis事务具有隔离性。

持久性

Redis事务,用队列保存了一些列命令,没有为事务提供额外的持久化功能,所有Redis的持久化由Redis的持久化模式决定。

没有持久化

Redis没有开启持久化模式,事务不具有持久性,一旦服务器停机,包括事务在内的数据都将丢失

RDB

当服务器运行在RDB模式下,服务器只在特定的条件满足时,才会执行BGSAVE命令保存数据库数据。然而异步执行的BGSAVE命令不能保证事务数据第一时间保存到磁盘。

因此RDB模式下的事务不具备持久性。

AOF

当服务器运行在AOF模式下,并且appendfsync 选项的值为 always 时,程序总会在执行命令后调用调用 sync 函数,将数据同步到磁盘中,因此在此场景中AOF是具有持久性的。

其他情况下,不能保证每执行一个命令就能将数据同步到磁盘,所以事务不具备持久性。

 

   
次浏览       
相关文章

基于EA的数据库建模
数据流建模(EA指南)
“数据湖”:概念、特征、架构与案例
在线商城数据库系统设计 思路+效果
 
相关文档

Greenplum数据库基础培训
MySQL5.1性能优化方案
某电商数据中台架构实践
MySQL高扩展架构设计
相关课程

数据治理、数据架构及数据标准
MongoDB实战课程
并发、大容量、高性能数据库设计与优化
PostgreSQL数据库实战培训
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]
 
最新文章
InfluxDB概念和基本操作
InfluxDB TSM存储引擎之数据写入
深度漫谈数据系统架构——Lambda architecture
Lambda架构实践
InfluxDB TSM存储引擎之数据读取
最新课程
Oracle数据库性能优化、架构设计和运行维护
并发、大容量、高性能数据库设计与优化
NoSQL数据库(原理、应用、最佳实践)
企业级Hadoop大数据处理最佳实践
Oracle数据库性能优化最佳实践
更多...   
成功案例
某金融公司 Mysql集群与性能优化
北京 并发、大容量、高性能数据库设计与优化
知名某信息通信公司 NoSQL缓存数据库技术
北京 oracle数据库SQL优化
中国移动 IaaS云平台-主流数据库及存储技术
更多...