Redis发布订阅和事务实现原理

Posted 热爱编程的大忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis发布订阅和事务实现原理相关的知识,希望对你有一定的参考价值。

Redis发布订阅和事务实现原理


发布订阅

Redis的发布订阅由PUBLISH,SUBSCRIBE,PSUBSCRIBE等命令组成,例子如下:



redis中我们还可以通过PSUBSCRIBE "user.*"命令完成频道的模式订阅,也就是模糊匹配,而SUBSCRIBE命令是明确订阅某个频道,也就是精确匹配。

当我们通过publish向某个频道发送命令时,该消息不仅会发送给订阅该频道的所有用户,同时也会发送给与该频道相匹配的模式的订阅者。


实现

频道订阅与退订

redis服务器全局状态由redisServer结构体对象保存,该对象内部保存了所有频道的订阅关系:

struct redisServer
   //...
   
   //保存所有频道的订阅关系
   dict *pubsub_channels;
  
  //... 

pubsub_channels属性的数据类型是字典类型,该字典中的key保存了频道名,value链表将订阅该频道的所有客户端串联起来:

  • 当我们通过subscribe命令订阅某个频道的时候,所做的工作如下:

  • 当我们通过unsubscribe命令退订某个频道时,所做的工作如下:


频道模式订阅与退订

struct redisServer
   //...
   
   //保存所有频道的订阅关系
   dict *pubsub_channels;
   //保存所有模式订阅关系
   list *pubsub_patterns;
  
  //... 


typedef struct pubsubPattern
   //订阅模式的客户端
   redisClient *client;
   //被订阅的模式
   robj *pattern
pubsubPattern

pubsub_patterns链表中保存的元素类型是pubsubPattern,该结构体记录了每个客户端所订阅的频道模式。

  • 订阅模式
  • 退订模式


发送消息

当一个redis客户端执行PUBLISH channel message命令时,服务器需要执行以下两步:

  1. 将消息发送给channel频道的所有订阅者

  1. 如果有一个或多个模式pattern与channel匹配,那么将消息发送给pattern模式的订阅者


事务

Redis通过MULTI,EXEC,WATCH等命令来实现事务功能,事务提供了将多个命令请求打包,然后一次性,按顺序执行的机制,并且在事务执行期间,服务器不会中断事务去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才会去处理其他客户端的请求。


事务执行分为三个阶段:

  • 事务开始: 通过multi命令表示开启事务,标记当前客户端进入事务状态

  • 命令入队

  • 事务执行


事务队列

每个Redis客户度都通过multiState属性来记录当前事务状态:

struct redisClient 
      //事务状态
      multistate mstate;
      ... 

事务状态包含一个事务队列和已经入队的命令计数器:

struct multistate 
   //事务队列--FIFO顺序
   multicmd *cmds;
   //已经入队的命令计数器
   int count; 

事务队列中每个multicmd都保存了当前已入队命令的信息:

struct multicmd 
  //参数
  robj **argv;
  //参数个数
  int argc;
  //命令指针
  struct redisCommand *cmd;

事务队列以先入先出顺序保存命令,例如:

事务队列中保存命令顺序:


执行事务

当一个处于事务状态的客户端向服务器发送EXEC命令时,该命令将会立刻执行,服务器会遍历当前客户端的事务队列,执行队列中保存的所有命令,最后将命令执行的结果全部返回给客户端:


WATCH命令实现

WATCH命令是一个乐观锁,它可以在EXEC命令执行前,监视任意数量的key,并在EXEC命令执行时,检查被监视的key是否至少有一个已经被修改了,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。

每个Redis数据库都保存着一个watched_keys字典,该字典的key是某个被watch命令监视的数据库键,而值是一个链表,链表中记录了所有监视当前key的客户端。

struct redisDb
   //正在被watch命令监视的key
   dict *watched_keys;


所有对数据库进行修改的命令,如: SET,LPUSH,SADD,ZREM,DEL等,在执行后都会调用touchWatchKey函数对watched_keys字典进行检查,如果字典中存在该key,那么会将监视该key对应的客户端的REDIS_DIRTY_CAS标记打开,表示当前客户端的安全性已经被破坏了。


当exec事务执行命令被调用时,服务器会检查当前客户端对应的REDIS_DIRTY_CAS标识是否已经被打开了,如果被打开了,就拒绝执行事务:


ACID

原子性

redis事务队列中的命令要么全部执行,要么全部不执行。

如果命令在入队过程中,出现了命令语法格式错误导致命令入队失败,那么当前事务中所有命令都不会被执行。


如果事务队列中命令执行时,发生错误,那么redis不提供回滚机制,并且命令将会继续执行下去,直到执行完毕:


一致性

  • 出现入队错误会导致当前事务被拒绝执行
  • 事务执行时出现错误,不会中断事务执行
  • redis服务器执行事务过程中停机不会导致数据不一致,服务器重启时可以通过rdb或者aof文件恢复数据

空白数据库总是可以看做是一致的


隔离性

数据库的隔离性指的是多个并发执行事务互不干扰,并且并行事务执行结果要与串行执行一致。

Redis使用单线程执行事务,并且执行事务期间不会对事务进行中断,因此,redis的事务总是以串行化方式运行。


持久性

因为Redis的事务不过是简单地用队列包裹起了一组Redis命令,Redis并没有为事务提供任何额外的持久化功能,所以Redis事务的耐久性由Redis所使用的持久化模式决定:

□ 当服务器在无持久化的内存模式下运作时,事务不具有耐久性:一旦服务器停机,包括事务数据在内的所有服务器数据都将丢失。

□ 当服务器在RDB持久化模式下运作时,服务器只会在特定的保存条件被满足时,才会执行BGSAVE 命令,对数据库进行保存操作,并且异步执行的BGSAVE 不能保证事务数据被第一时间保存到硬盘里面,因此RDB持久化模式下的事务也不具有耐久性。

□ 当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always 时,程序总会在执行命令之后调用同步(sync)函数,将命令数据真正地保存到硬盘里面,因此这种配置下的事务是具有耐久性的。

□ 当服务器运行在AOF持久化模式下,并且appendfsync选项的值为everysec 时,程序会每秒同步一次命令数据到硬盘。因为停机可能会恰好发生在等待同步的那一秒钟之内,这可能会造成事务数据丢失,所以这种配置下的事务不具有耐久性。

□ 当服务器运行在AOF持久化模式下,并且appendfsync 选项的值为no时,程序会交由操作系统来决定何时将命令数据同步到硬盘。因为事务数据可能在等待同步的过程中丢失,所以这种配置下的事务不具有耐久性。


以上是关于Redis发布订阅和事务实现原理的主要内容,如果未能解决你的问题,请参考以下文章

Redis:高级特性和底层工作原理

Redis专题3:发布订阅模式事务Lua脚本揭秘

Redis | 第8章 发布订阅与事务《Redis设计与实现》#yyds干货盘点#

#yyds干货盘点#Redis中的事务原理与操作分享

redis事务以及发布订阅

NOSQL数据库事务的CAPBASE原理--redis