说明
- Redis中的事务与传统关系型数据库(如mysql)的事务是不同的。
- Redis中的事务是一组命令的集合,事务与命令都是最小执行单位,原理是先将属于一个事务的命令发送给Redis,然后Redis一次执行这些命令。
- Redis的事务可以保证一个事务内的命令一次执行而不被其他命令插入影响。
事物实现
Redis通过MULTI、EXEC、WATCH等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕才去处理其他客户端的命令请求。
具体流程为:
1. 事务开始(以MULTI为标志)
2. 命令入队(命令一次进入队列)
3. 事务执行(通过EXEC执行)
例子:
MULTI命令
MULTI命令将执行该命令的客户端从非事务状态切换至事务状态。
命令入队
- 当一个客户端处于非事务状态时,这个客户端发送的命令会立即被服务器执行。
- 当一个客户端切换到事务状态之后,服务器会根据这个客户端发来的不同命令执行不同的操作。
- 如果时EXEC、DISCARD、WATCH、MULTI四个命令中的一个,服务器就会立即执行这个命令。
- 如果不是上述四个命令,则将命令放进一个事务队列,并向客户端返回QUEUED回复。
事务队列
- 每个Redis客户端都有自己的事务状态,这个事务状态保存在客户端状态的mstate属性里面。
- 事务状态包含一个事务队列,以及一个计数器表示事务队列的长度,即当前已经入队列的命令个数。
- 事务队列是一个multiCmd类型的数组,数组中的每个multiCmd结构都保存了一个已入队命令的相关信息。
- 事务队列以先进先出的方式保存入队的命令,较先入队的命令会被放到数组的前面,而较后入队的命令则会被放到数组的后面。
执行事务
当满足下列条件时,开始执行事务:
- 客户端出于事务状态。
- 客户端想服务器发送EXEC命令。
服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令,最后将执行命令所得的结果以队列的形式全部返回给客户端。
WATCH命令
WATCH命令是一个乐观锁,它可以在EXEC执行之前,监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过,如果被修改过,则服务器拒绝执行事务,并向客户端返回代表事务执行失败的空回复。
WATCH命令监视数据库键
每个Redis数据库都保存着一个watched_keys字典,这个字典的键是被某个WATCH命令监视的数据库键,而字典值则是一个链表,链表中记录了所有监视相应数据库键的客户端。
监视机制的触发
所有对数据库进行修改的命令,比如SET、LPUSH、SADD、ZREM、DEL、FLUSHDB等,在执行之后都会调用multi.c/touchWatchKey函数对watched_keys字典进行检查,查看是否客户端正在监视刚刚被命令修改过的数据库键,如果有的话,那么touchWatchKey函数会将监视被修改键的客户端的REDIS_DIRTY_CAS标识打开,表示该客户端的事务安全性已经被破坏。
事务安全性判断
当服务器接收到一个客户端发来的EXEC命令时,服务器会根据这个客户端是否打开了REDIS_DIRTY_CAS标识来决定是否执行事务。
总结
- 事务提供了一种将多个命令打包,然后一次性、有序的执行的机制。
- 多个命令会被添加到事务队列中,然后按先进先出的方式顺序执行。
- 事务在执行过程中不会被中断,当事务队列中的所有命令都被执行完毕后,事务才会结束。
- 带有WATCH命令的事务会将客户端和被监视的键在数据库的watched_keys字典进行关联,当键被修改时,程序会将所有被监视且被修改键的客户端的REDIS_DIRTY_CAS标志打开。
- 只有客户端的REDIS_DIRTY_CAS标志未被打开是,服务器才会执行客户端提交的事务,否则的话,服务器将拒绝执行客户端提交的事务。
- Redis的事务总是具有ACID中的原子性、一致性和隔离性,当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务具有持久性。