事务
一个事务从开始到结束通常会经历以下三个阶段:
- 事务开始。
- 命令人队。
- 事务执行/取消
事务开始
MULTI命令的执行标志着事务的开始。MULTI命令可以将执行该命令的客户端从非事务状态切换至事务状态,这一切换是通过在客户端状态的flags属性中打开REDIS_MULTI标识来完成的。
命令入队
- 当一个客户端处于非事务状态时,这个客户端发送的命令会立即被服务器执行;
- 而当一个客户端切换到事务状态之后,服务器会根据这个客户端发来的不同命令执行不同的操作:
- 如果客户端发送的命令为EXEC, DISCARD, (UN)WATCH, MULTI四个命令的其中一个,那么服务器立即执行这个命令。
- 与此相反,如果客户端发送的是其他命令,那么服务器并不立即执行这个命令,而是将这个命令放入一个事务队列里面(打包后一次性发送给redis服务端),然后向客户端返回QUEUED回复

事务队列
每个Redis客户端都有自己的事务状态,这个事务状态保存在客户端状态的master属性里面。事务状态包含一个事务队列,以及一个已入队命令的计数器(也可以说是事务队列的长度)。事务队列是一个multiCmd类型的数组,数组中的每个multiCmd结构都保存了一个 已入队命令的相关信息,包括指向命令实现函数的指针、命令的参数、参数的数量。事务队列以先进先出(FIFO)的方式保存入队的命令,较先人队的命令会被放到数组的前面,而较后人队的命令则会被放到数组的后面。如下是master属性结构的示例:

执行事务/取消
当一个处于事务状态的客户端向服务器发送EXEC命令时,将立即被服务器执行:服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令(不会被打断),最后将执行命令所得的结果全部返回给客户端。 调用discard命令会清空事务队列并退出事务。
本地事务
单机实现的事务。Redis 中的事务是一组命令的集合,是 Redis 的最小执行单位。它使用MULTI, EXEC, DISCARD 和 WATCH 命令来实现事务功能。事务可以一次执行多个命令。
两个重要的保证
- 隔离性:每个事务是一个单独的隔离操作,事务中的所有命令都被序列化(数组)并按顺序执行;
- 顺序性:Redis执行事务期间,不会被其它客户端发送的命令打断,事务中的所有命令都作为一个隔离操作顺序执行。直到事务命令全部执行完毕才会执行其他客户端的命令。
不支持回滚和都成功
Redis事务是原子操作,所有命令都执行或者都不执行,且是不支持回滚的。使用EXEC 命令触发一个事务中所有命令的执行。所以,如果一个客户端在调用EXEC 命令前丢失连接,那么所有的命令不会被执行,相反,如果EXEC 被调用,那么所有命令会被执行,但不保证都成功
不支持回滚?
Redis 的事务不支持回滚,但是执行的命令有语法错误,Redis 会执行失败,这些问题可以从程序层面捕获并解决。但是如果出现其他问题,则依然会继续执行余下的命令。这样做的原因是因为回滚需要增加很多工作,而不支持回滚则可以保持简单、快速的特性。
不保证都成功?
虽然命令都会被执行,但因为事务内的命令可能出现:1.语法错误的时候才失败;2.要执行的key数据类型不匹配,导致一些命令会执行失败等
实现过程
(单线程处理客户端请求)
- Redis使用 MULTI 命令,打开事务状态,标记事务开始,它总是返回OK。
- MULTI执行之后,客户端可以发送多条命令,Redis会把这些命令保存在队列(数组)当中,而不是立刻执行这些命令
- 所有的命令会在调用EXEC 命令之后执行
- 如果不调用EXEC,调用 DISCARD 会清空事务队列并退出事务
- WATCH 命令可以为 Redis 事务提供 check-and-set(CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令
扩展一
当使用 append-only file 方式持久化时,Redis使用单个 write(2) 系统调用将事务写到磁盘上。但是,如果Redis服务器崩溃或被系统管理员以某种硬方式杀死,则可能只注册了部分操作。Redis重启的时候会检测到这种情况,并返回错误退出。使用 redis-check-aof 工具可以删除部分事务,这样Redis可以重新启动。从2.2起,Redis提供了额外的保证,以类似check-and-set (CAS)的乐观锁形式。
扩展二
MULTI命令实现
MULTI命令的执行标志着事务的开始,将执行该命令的客户端从非事务状态切换至事务状态
DISCARD命令实现
调用discard命令会清空事务队列并退出事务
EXEC命令实现
事务内所有命令会在调用EXEC 命令之后执行(如果一个客户端在调用EXEC 命令前丢失连接,那么所有的命令不会被执行)
WATCH命令实现
WATCH命令是一个乐观锁(optimistic locking),它可以在EXEC命令执行之前,监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复
原理
每个Redis数据库都保存着一个watched_keys字典,这个字典的键是某个被WATCH命令监视的数据库键,而字典的值则是一个记录了所有监视相应数据库键客户端的链表。通过watched_keys字典,服务器可以清楚地知道哪些数据库键正在被监视,以及哪些客户端正在监视这些数据库键
监视机制的触发
所有对数据库进行修改的命令,比如SET、LPUSH、SADD、ZREM、DEL、FLUSHDB 等等,在执行之后都会调用multi.c/touchWatchKey函数(redis的事件函数之一)对watched_keys字典进行检查,查看是否有客户端正在监视刚刚被命令修改过的数据库键,如果有的话,那么 touchWatchKey函数会将监视被修改键的客户端的REDIS_DIRTY_CAS标识打开,表示该客户端的事务安全性已经被破坏
判断事务是否安全
当服务器接收到一个客户端发来的EXEC命令时,服务器会根据这个客户端是否打开了REDIS_DIRTY_CAS标识来决定否执行事务

UNWATCH命令实现
UNWATCH 命令用于取消对所有Key的监视。执行 UNWATCH 后,Redis将不再监视任何Key的变化,事务将按照正常流程执行
1091

被折叠的 条评论
为什么被折叠?



