事务
Redis通过MULTI
,EXEC
,WATCH
等命令实现事务功能。
事务是将多个命令打包,然后原子地按顺序地执行的机制,执行期间服务器不会中断事务执行其他客户端的命令请求。下面展示了一次完整事务的执行命令:
1 | redis> MULTI |
事务的实现
可以看出,事务主要有3个阶段:事务开始、命令入队、事务执行。
事务开始
MULTI命令表示事务的开始,将客户端从非事务状态切换为事务状态,在flags属性中打开REDIS_MULTI
标识。
命令入队
当客户端处于事务状态时,命令不会被立即执行(除了EXEC、DISCARD、WATCH、MULTI),而是加入事务队列。
事务队列
客户端的事务状态保存在mstate里:
1 | typedef struct redisCLient{ |
事务状态包括事务队列和入队命令计数器:
1 | typedef struct multiState{ |
事务队列的实例结构:
1 | typedef struct multiCmd { |
先入队的命令先放入数组,后入队的后放入。
执行事务
当收到客户端的EXEC命令时,将立即执行,然后服务器遍历客户端的事务队列,保存命令,执行命令,返回结果给客户端,最后移除事务标识。
WATCH命令的实现
WATCH命令是一个乐观锁,可以在EXEC前监视任意数量的键,如果在EXEC执行时,发现这些被监视的键被修改过,服务器将拒绝执行事务。
使用WATCH命令监视数据库键
每个Redis数据库都保存着watched_keys字典,键是某个被WATCHED命令监视的键,值是一个链表,记录所有监视该键的客户端。
1 | typedef struct redisDb{ |
监控机制的触发&事务安全
在执行数据库修改命令时,都会调用multi.c/touchWatchKey
函数对watched_keys字典进行检查,查看是否有客户端正在监视的刚被命令修改过的键,如果有,将watched_keys该键对应的值,也就是监听的客户端都打开REDIS_DIRTY_CAS
标识,表示事务的安全性已经被破坏。此时,服务器拒绝执行该客户端的事务。
事务的ACID性质
Redis的事务有原子性、一致性和隔离性,当Redis运行在特定的持久化模式下时,才具有持久性。
原子性
Redis事务队列中的命令,要么全部都执行,要么一个都不执行,因此,具有原子性。Redis进行事务命令入队时,如果命令入队出错,会被拒绝执行。但是命令的语法错误(执行错误),不会导致整个命令不被执行,也就是说Redis不支持事务的回滚机制。
下面例子表示发生入队错误(一致性时将提到入队错误和执行错误)时,事务中的所有命令都不会被执行:
1 | redis> MULTI |
发生执行错误,不影响其他命令的执行:
1 | redis> SET msg "hello" # msg键是一个字符串 |
不支持事务回滚是考虑到了复杂性,与其简单高效的理念不符,并且Redis的设计者认为,Redis事务的执行时错误通常都是编程错误产生的,在开发环境中会有,但生产环境不应该出现,因此,没有设计回滚机制。
一致性
一致性表示在事务的执行前后,成功与否,数据库都是一致的,也就是数据符合数据库本身定义和要求,没有非法或无效错误数据。
Redis通过简单的错误检测来保证一致性。
- 入队错误
在2.6.5之后的版本,如果一个事务在入队时出现了命令不存在,Redis则拒绝执行这个事务。
- 执行错误
对于命令执行期间发现的错误,不会影响其他命令的执行。服务器会识别出错的命令,并进行相应处理,这些命令不会对数据库做修改,不影响一致性。
- 服务器停机
如果Redis在执行事务过程中停机,数据也是一致的。如果没有开启持久化,重启后数据库是空白的。开启持久化后,重启后会还原到一致状态。
隔离性
事务的隔离性是指,即使数据库中有多个事务并发地执行,各个事务之间也不会互相影响。
Redis是单线程的,并且服务器保证在执行事务期间不会对事务进行中断,因此Redis的事务总是以串行的方式运行,是具有隔离性的。
持久性
持久性的意思是,事务执行的结果被永久性地保存,执行事务的结果不会丢失。
因为Redis没有单独为事务队列提供持久化功能,所以取决于持久化模式,只有AOF方式持久化并且appendsync的值为always,而且没有打开no-appendfsync-on-rewrite
时,才具有持久性。因为其他方式并不能保证事务的执行结果被第一时间保存到硬盘里。
注:
no-appendfsync-on-rewrite
打开后,在执行BGSAVE
和BGREWRITEAOF
时会暂停对AOF文件的同步。
慢查询日志
Redis的慢查询日志功能用于记录执行时间超过给定时长的命令请求。可通过两个参数配置:
slowlog-log-slower-than
:执行时间超过多少微秒的命令会被记录到日志上。slowlog-max-len
:指定服务器最多保存多少条慢查询日志,超过时会删除最久的那条日志。
可以使用CONSIG SET slowlog-log-slower-than <microsecond>
直接修改配置,使用SLOWLOG GET
来查询慢查询日志。
慢查询记录的保存
相关慢查询日志的属性记录在redisServer中:
1 | struct redisServer { |
slowlog是一个链表,有几个节点就表示有几条慢查询日志,节点是一个slowlogEntry实例:
1 | typedef struct slow1ogEntry { |
新添加的日志会被放到slowlog链表的表头。
监视器
执行MONITOR命令,客户端就成为了监视器,实时接收并打印服务器处理的命令。当其他客户端发送请求时,服务器除了执行,还会将相关信息发送给所有监视器。
成为监视器
redisServer中有monitors链表,记录所有成为监视器的客户端。如果某个客户端发送MONITOR命令,就会打开它的REDIS_MONITOR
标志,并将其插入到该链表的尾部。
向监视器发送命令信息
服务器处理命令前都会调用replicationFeedMonitors
函数,将相关信息发送给各个监视器。主要是封装要发送给监视器的信息、遍历监视器、发送信息这三步。
总结
本文的重点是Redis的事务,其他的作以了解。