Redis底层学习-事务
Redis 底层学习-事务
Redis 通过 multi、exec、watch 等命令来实现事务功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求 ,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
eg:
事务的实现
一个事务从开始到结束通常会经历以下三个阶段
- 事务开始
- 命令入队
- 事务执行
事务开始
multi 命令的执行标志着事务的开始
multi 命令可以将执行该命令的客户端从非事务状态切换至事务状态,这一切换是通过客户端的 flags 属性中打开 redis_multi 标识来实现的。
命令入队
当一个客户端处于非事务状态时,这个客户端发送的命令会立即被服务器执行。
与此不同的是,放一个客户端切换到事务状态之后,服务器会根据这个客户端发来的不同命令执行不同的操作。
- 如果客户端发送的命令为 exec、discard、watch、multi 四个命令的其中一个,那么服务器会立即执行这个命令。
- 与此相反,如果客户端发送的是上述 4 个命令之外的其他命令,那么服务器并不立即执行这个命令,而是将这个命令放入一个事务队列中,然后将客户端返回 queued 回复。
事务队列
每个 Redis 客户端都有自己的事务状态,这个事务状态保存在客户端状态的 mstate 属性里面。
事务状态包含一个事务队列,以及一个已入队命令的计数器-事务队列的长度
typedef struct multiState{
//事务队列,fifo 顺序
multiCmd *commonds;
//已入队命令计数
int count;
}multiState;
事务队列以先进先出 fifo 的方式保存入队的命令,较先入队的命令会被放到数组的前面,而较后入队的命令则会被放到数组的后面。
完整结构:
执行事务
当一个处于事务状态的客户端向服务器发送 exec 命令时,这个 exec 命令将立即被服务器执行,服务器会遍历这个客户端的事务队列,执行队列中的所有命令,最后将执行命令所得的结果全部返回给客户端。
watch 命令的实现
watch 命令是一个乐观锁,它可以在 exec 命令执行之前,监视任意数量的数据库键,并在 exec 命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务 ,并向客户端返回代表事务执行失败的空回复。
两个命令同时执行
在时间 t4,客户端 B 修改了“name”键的值,当客户端 A 在执行 exec 命令时,服务器会发现 watch 监视的键“name”已经被修改,因此服务器拒绝执行客户端 A 的事务,并向客户端 A 返回空回复。
实现
每个 Redis 数据库都保存着一个 watched_keys 字典,这个字典的键是某个被 watch 命令监视的数据库键,而字典的值则是一个链表,链表中记录了所有监视相应数据库键的客户端。
监控机制的触发
所有对数据库进行修改命令,比如 set、lpush、sadd、zerm、del、flushdb 等等,在执行之后都会调用函数对 wached_keys 字典进行检查,查看是否有客户端正在监视刚刚被命令修改过的数据库键,如果有的话,那么会修改键的客户端的 redis_dirty_cas 标示打开,表示该客户端的事务安全性已经被破坏。
事务的 ACID 性质
在 Redis 中,事务总是具有原子行、一致行和隔离型,并且当 Redis 运行在某种特定的持久化模式下时,事务也具有耐久性。
原子性
事务具有原子行指的是,数据库将事务中的多个操作当成一个整体来执行,服务器要么就执行事务中的所有擦欧总,要么就一个操作也不执行。
Redis 的事务和传统的关系型数据库事务的最大区别在于,Redis 不支持事务回滚机制,即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。
一致性
事务具有一致性指的是,如果数据库在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据库也仍然是一致的。
Redis 通过谨慎的错误检测和简单的设计来保证事务的一致性。
入队错误
如果一个事务在入队命令的过程中,出现了命令不存在,或者命令的格式不正确等情况,那么 Redis 将拒绝执行这个事务。
执行错误
除了入队时可能出现错误意外,事务还可能在执行的过程中发生错误,关于这种错误有 2 个需要说明的地方:
- 执行过程中发生的错误都是一些不能在入列时被服务器发现的错误,这些错误只会在命令实际执行时被触发
- 即使在事务的执行过程中发生了错误 ,服务器也不会中断事物的执行,它会继续执行事务中余下的其他命令,并且一致性的命令不会被出错的命令影响
因为在事务执行过程中,出错的命令会被服务器识别出来,并进行相应的错误处理, 所以这些出错命令不会对数据库做任何修改,也不会对事务的一致行产生任何影响
服务器停机
如果 Redis 服务器在执行事务的过程中停机,那么根据服务器所使用的持久化模式,事务执行中途发生的停机都不会影响服务的一致性。
隔离性
Redis 使用单线程的方式来执行事务,并且服务器保证,在执行事务期间不会对事务进行中断,因此,Redis 的事务总是以串行的方式运行的,并且事务也总是具有隔离性的。
持久性
事务的持久性指的是,当一个事务执行完毕时,执行这个事务所得的结果已经被保存到永久性存储介质里面了,即使服务器在事务执行完毕之后停机,执行事务所得的结果也不会丢失。
Redis 的事务不过是简单地用队列包裹起了一组 Redis 命令,Redis 并没有为事务提供额外的持久化功能,所以 Redis 事务的耐久性由 Redis 所使用的持久化模式决定
- 无持久化措施,事务不具有持久性,一旦服务器停机,包括事务数据在内的所有服务器数据都将丢失。
- RDB 持久化模式,只有特定条件下才会进行 save 或者 bgsave,中间可能会丢失数据,所有 RDB 持久化模式下的事务也不具有持久性
- AOF 模式。并且 appedfsync 选项为 always 时,程序总会在执行命令之后调用同步 sync 函数,将命令数据真正地保存到硬盘里面,因此这种配置下的事务是具有持久性的,其他模式下的选项值,要么一秒丢失,要么由操作系统决定,都可能出现丢失。