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

1元 10元 50元





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



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
   
 
     
   
 订阅
  捐助
Redis 事务
 
作者: hulunbao
91 次浏览     评价:  
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是具有持久性的。

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

 

   
91 次浏览     评价: 订阅 捐助
相关文章

我们该如何设计数据库
数据库设计经验谈
数据库设计过程
数据库编程总结
 
相关文档

数据库性能调优技巧
数据库性能调整
数据库性能优化讲座
数据库系统性能调优系列
相关课程

高性能数据库设计与优化
高级数据库架构师
数据仓库和数据挖掘技术
Hadoop原理、部署与性能调优
最新课程计划
 
最新文章
InfluxDB概念和基本操作
InfluxDB TSM存储引擎之数据写入
深度漫谈数据系统架构——Lambda architecture
Lambda架构实践
InfluxDB TSM存储引擎之数据读取
最新课程
Oracle数据库性能优化、架构设计和运行维护
并发、大容量、高性能数据库设计与优化
NoSQL数据库(原理、应用、最佳实践)
企业级Hadoop大数据处理最佳实践
Oracle数据库性能优化最佳实践
更多...   
成功案例
某金融公司 Mysql集群与性能优化
北京 并发、大容量、高性能数据库设计与优化
知名某信息通信公司 NoSQL缓存数据库技术
北京 oracle数据库SQL优化
中国移动 IaaS云平台-主流数据库及存储技术
更多...