发布与订阅
Redis通过发布订阅提供一对多甚至是多对多的节点消息通信,发布订阅由PUBLISH、SUBSCRIBE、PSUBSCRIBE、PUBSUB等命令组成。
- SUBSCRIBE命令:订阅某频道,在redisServer结构中通过pubsub_channels字典属性保存当前服务器所有频道的订阅关系,字典键时频道名称,字典值是一个链表,记录了所有订阅这个频道的客户端。
- UNSUBSCRIBE命令:退订频道,调用该命令之后,会把订阅关系从pubsub_channels中删掉,如果键对应的链表为空了,则把键从字典中删除。
- PSUBSCRIBE命令:订阅模式,服务器将所有的模式订阅关系保存在redisServer结构的pubsub_patterns属性中,该属性是一个链表,每个链表节点包含一个pubsubPattern结构,该结构记录了被订阅的模式和订阅该模式的客户端。
- PUNSUBSCRIBE命令:退订模式,调用该命令之后,服务器会遍历pubsub_patterns链表,把客户端和模式都匹配的那个节点删除。
- PUBLISH命令:发送订阅消息,服务器接收到该命令之后,先遍历pubsub_channels找出频道订阅者,把消息发送给所有频道订阅者,然后遍历pubsub_patterns找出与channel匹配的模式,并将消息发送给订阅了这些模式的客户端。
- PUBSUB命令,查看订阅信息包含下面三个子命令:
- PUBSUB CHANNLES [pattern]:返回当前服务器被订阅的频道,如指定pattern,则返回与模式匹配的频道
- PUBSUB NUMSUB [channel-1 ... channel-n]:返回指定频道的订阅者数量
- PUBSUB NUMPAT:返回服务器当前被订阅模式的数量
事务
Redis通过MULTI、EXEC、WATCH等命令来实现事务功能,实现将多个命令打包然后一次性顺序执行。事务以MULTI命令开始,EXEC命令结束。在执行MULTI命令之后执行的命令不立刻执行,而是向客户端返回一个QUEUED,等执行EXEC命令之后再执行。
事务的实现
事务开始
执行MULTI命令之后,服务器把客户端从非事务态切换到事务态,通过设置redisClient的flags标识,把REDIS_MULTI标识打开。
命令入队
客户端处于非事务态时,客户端发送的命令立刻执行,当处于事务态时:
如果客户端发送的命令为EXEC、DISCARD、WATCH、MULTI其中的一个时,服务器立即执行。
如果客户端发送的命令是其它的命令,服务器不会立刻执行这个命令,而是把命令放到事务队列中,然后向客户端返回QUEUED。
redisClient中有个multiState结构的属性mstate记录客户端的事务状态,在multiState结构中有个commands列表属性,它记录了调用MULTI之后该客户端入队的命令,count属性记录入队命令的数量。multiCmd结构记录命令实现函数指针,命令的参数,已经参数的数量。事务队列以FIFO的方式保存入队命令,先入队的命令先执行。
执行事务
执行EXEC命令提交事务,服务器接收该命令之后:
- 遍历client.mstate.commands,获取已入队的命令并且挨个执行,把命令的返回值添加到回复队列尾部。
- 清空客户端的事务状态,包括清零入队命令计数器,释放事务队列。
- 把执行结果返回给客户端。
WATCH命令实现
WATCH命令是一个乐观锁,在执行EXEC之前监视制定的数据库键,在执行EXEC命令执行时检查被监视的键是否至少有一个已经被修改过,如果是的话,服务器拒绝执行命令,并向客户端返回代表事务执行失败的空回复。
Redis数据库的redisDb结构保存了一个watched_keys字典,字典的键时某个被WATCH命令监视的键,值是一个链表,链表中记录了所有监视相应数据库键的客户端。
在执行对数据库修改的命令时,执行之后会调用multi.c/touchWatchKey函数对watched_keys字典进行检查,查看是否有客户端正在监视刚刚被命令修改过的数据库键,如果有的话,touchWatchKey函数将监视被修改键的客户端的REDIS_DIRTY_CAS标识打开,表示该客户端的事务安全性已经被破坏。当服务器接收到一个客户端发来的EXEC命令时,服务器根据这个客户端是否打开了REDIS_DIRTY_CAS标识来决定是否执行事务。
事务的ACID
原子性:对Redis事务来说,事务中的多个命令被当做一个整体,事务队列中的命令要么全部被执行,要么都不执行,Redis事务时具有原子性的,但是Redis事务部支持回滚,即使队列中某个命令执行出现了错误也不影响后面的命令,整个事务会继续执行。
一致性:在命令入队错误、执行错误、服务器停机时都不会影响数据库的一致性。ps:当入队错误时对2.6.5之前的版本事务会继续执行,2.6.5之后事务会被拒绝执行
隔离性:Redis事务执行期间不会被其它命令中断,以串行的方式运行,Redis事务是具有隔离性的
持久性:
- 当服务器运行在无持久化的内存模式下时,事务不具有持久性:一旦服务器停机,所有数据都会丢失
- 当服务器运行在RDB持久化模式下时,当服务器停机时会有一段时间的数据会丢失,事务不具有持久性
- 当服务器运行在AOF持久化模式下,并且appendsync选项值为always时,数据会被及时保存到硬盘中,此时事务具有持久性(前提是没有打开no-appendfsync-on-rewrite选项,打开该选项之后,为了尽可能的减少IO操作,当服务器执行BGSAVE或BGREWRITEAOF时,会停止AOF文件同步,所以此时事务也不具有持久性)
- 当服务器运行在AOF持久化模式下,并且appendsync选项值为everysec时,可能会丢失1秒的数据,此时事务不具有持久性
- 当服务器运行在AOF持久化模式下,并且appendsync选项值为no,服务器停机时数据会丢失,此时事务不具有持久性
慢查询日志
Redis 的慢查询日志功能用于记录执行时间超过给定时长的命令请求, 用户可以通过这个功能产生的日志来监视和优化查询速度。
服务器配置有两个和慢查询日志相关的选项:
-
slowlog-log-slower-than
选项指定执行时间超过多少微秒(1
秒等于1,000,000
微秒)的命令请求会被记录到日志上。举个例子, 如果这个选项的值为
100
, 那么执行时间超过100
微秒的命令就会被记录到慢查询日志; 如果这个选项的值为500
, 那么执行时间超过500
微秒的命令就会被记录到慢查询日志; 诸如此类。 -
slowlog-max-len
选项指定服务器最多保存多少条慢查询日志。服务器使用先进先出的方式保存多条慢查询日志: 当服务器储存的慢查询日志数量等于
slowlog-max-len
选项的值时, 服务器在添加一条新的慢查询日志之前, 会先将最旧的一条慢查询日志删除。举个例子, 如果服务器
slowlog-max-len
的值为100
, 并且假设服务器已经储存了100
条慢查询日志, 那么如果服务器打算添加一条新日志的话, 它就必须先删除目前保存的最旧的那条日志, 然后再添加新日志。