编辑推荐: |
本文主要介绍了事务是什么?事务的特性、为什么使用事务以及使用事务的整个流程。
本文来自博客园,由火龙果软件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是具有持久性的。
其他情况下,不能保证每执行一个命令就能将数据同步到磁盘,所以事务不具备持久性。
|