飞奔的炮台 发表于 2021-12-7 12:17:37

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

@(第8章 发布订阅与事务)
前言
参考资料:《Redis设计与实现 第二版》;
第三部分为独立功能的实现,主要由以下模块组成:发布订阅、事务、Lua 脚本、排序、二进制位数组、慢查询日志、监视器;
本篇将介绍 Redis 的发布订阅与事务。Redis 提供了频道与模式的订阅与退订,支持对频道发送消息。Redis 的事务机制支持一次性、按顺序执行多个命令,以及事务的 ACID 性质;
与本章相关的 Redis 命令总结在下篇文章,欢迎点击收藏,本篇将不再重复:
《Redis常用命令及示例总结(API)》:https://blog.51cto.com/dlhjw/4744855
1. 发布订阅

1.1 频道的订阅与退订

[*]客户端使用 SUBSCRIBE 命令订阅某个或某些频道;
[*]客户端使用 UNSUBSCRIBE 命令退订频道;
[*]Redis 将所有频道的订阅关系保存在服务器状态的 pubsub_challens 字典里:
redisService{
//...
//保存所有频道的订阅关系
dict *pubsub_channels;
};


[*]频道订阅的情况:

[*]频道已有其他订阅者,则将客户端添加到订阅者链表末端;
[*]反之,字典里没有该频道,则创建一个键值对项;

[*]频道退订的情况:

[*]找到频道对应链表,删除客户端信息;
[*]若删除后链表长度为0,则删除键;



1.2 模式的订阅与退订

[*]客户端使用 PSUBSCRIBE 命令订阅某个或某些模式;
[*]客户端使用 PUNSUBSCRIBE 命令退订模式;
[*]Redis 将所有模式的订阅关系保存在服务器状态的 pubsub_patterns 链表里:
redisServer{
//...
//保存所有模式订阅关系,记录被订阅的模式
list *pubsub_patterns;
};
[*]pubsub_patterns 链表保存的结构体如下: struct pubsubPattern{
//订阅模式的客户端
redisClient *client;
//被订阅的模式
robj *pattern;
} pubsubPattern;


[*]客户端在订阅模式时,会创建一个 pubsubPattern 结构体,并添加到链表尾部;
[*]客户端在退订模式时,遍历链表删除对应模式;


1.3 发送消息

[*]客户端执行 PUBLISH channel message 命令将 message 消息发送给 channel 频道,然后服务器将消息发送给频道与模式订阅者;
[*]将消息发送给频道订阅者:

[*]在 pubsub_channels 字典里找到频道 channel 的所有订阅者名单(链表),然后将消息发送给名单上的所有客户端;

[*]将消息发送给模式订阅者:

[*]遍历 pubsub_patterns 链表,查找与 channel 频道相匹配的模式,然后将消息发送给订阅了这些模式的客户端;




1.4 查看订阅消息

[*]客户端使用 PUBSUB 命令查看频道或模式的相关信息;
[*]PUBSUB CHANNELS 命令用于返回服务器当前被订阅的频道;
[*]PUBSUB NUMSUB 命令接受任意多个频道作为输入参数,返回这些频道的订阅者数量。通过查询 pubsub_channels 字典中对应频道键的链表值的长度;
[*]PUBSUB NUMPAT 命令用于返回服务器当前被订阅模式的数量。通过查询 pubsub_patterns 链表的长度;
2. 事务

[*]事务提供一种将多个命令打包,然后一次性、按顺序执行多个命令的机制;
[*]并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求;

2.1 事务的实现

[*]事务开始:

[*]使用 MULTI 命令;
[*]通过修改客户端状态中 flags 属性为 REDIS_MULTI 实现;

[*]命令入队:

[*]当客户端切换到事务模式时,会根据命令不同采取不同的操作;
[*]与事务相关的命令有:EXEC、DISCARD、WATCH、MULTI;


[*]事务队列:

[*]Redis 客户端里有事务状态属性 mstate:
struct redisClient{
    //...
    //事务状态
    multiState mstate;
} redisClient;

[*]multiState 事务状态结构,包含事务队列与计数器:
struct multiState{
    //事务队列,FIFO排序
    multiCmd *commands;
    //已入队命令计数
    int count;
} multiState;

[*]执行事务:

[*]处于事务状态的客户端向服务器发送 EXEC 命令时,会执行事务;
[*]服务器遍历客户端的事务队列,执行队列中保存的所有命令,将执行结果返回给客户端;


2.2 WATCH 命令的实现

[*]WATCH 命令是一个乐观锁;
[*]在执行 EXEC 命令前:监视任意数量的数据库建;
[*]在执行 EXEC 命令时:检查被监视的键是否至少有一个已经被修改,是则拒绝执行事务,返回错误;
[*]Redis 数据库保存一个 watched_keys 字典:
struct redisDb{
//...
// 字典,键表示被 WATCH 命令监视的数据库键;值为链表,记录监视该键的客户端
dict *watched_keys;
} redisDb;
[*]所有对数据库进行修改的命令,在执行后都会调用 multi.c/touchWatchKey 函数对 watched_keys 字典进行检查:

[*]如果有客户端监视被修改的键,则将客户端的 REDIS_DIRTY_CAS 标识打开,表示客户端的事务安全性被破坏;

[*]服务器接收到 EXEC 命令时,会根据客户端是否打开 REDIS_DIRTY_CAS 标识决定是否执行事务:

[*]如果打开,说明本次提交不安全,服务器会拒绝执行客户端提交的事务;
[*]否则说明事务安全,可以提交;



2.3 事务的 ACID 性质

[*]Redis 数据库的事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、耐久性(Durability);
[*]原子性:

[*]事务队列要么全部执行,要么一个都不执行;
[*]Redis 不支持事务回滚机制(rollback),事务队列中某个命令在执行期间出现错误,后续事务也会继续执行;

[*]一致性:

[*]一致指:数据符合数据库本身的定义和要求,没有包含非法或无效的错误数据;
[*]数据库在执行事务之前是一致的,在执行事务之后,无论事务是否成功,数据库也应该是一致的;
[*]Redis 的一致性有:入队错误、执行错误、服务器停机;

[*]隔离性:

[*]数据库中多个事务并发执行,各个事务之间不会互相影响,并且与串行执行的结果相同;
[*]原因:Redis 使用单线程方式执行事务以及事务队列中的命令,且服务器保证在事务执行期间不会对事务中断;

[*]耐久性:

[*]当一个事务执行完毕,执行事务所得的结果会被保存到永久性存储介质;
[*]Redis 的事务耐久性由持久化模式支持:
服务器的持久化模式事务的耐久性说明无持久化模式不具有RDB 持久化模式不具有服务器只会在特定条件下执行 BGSAVEAOF 持久化模式,且appendfsync 的值为 always具有程序总在执行命令后调用同步函数AOF 持久化模式,且appendfsync 的值为 everysec不具有程序每秒同步一次命令数据到硬盘AOF 持久化模式,且appendfsync 的值为 no不具有同步操作由操作系统决定服务器打开了 no-appendfsync-on-rewrite 选项不具有该选项打开时,服务器在执行 BGSAVE 或 BGREWRITEAOF 命令时,会暂时停止对 AOF 文件进行同步(尽可能减少 I/O 阻塞)

[*]不管 Redis 在上面模式下运行,在事务最后加上 SAVE 命令总可以保证事务的耐久性。但因为效率低,不具有实用性;

最后
::: hljs-center
新人制作,如有错误,欢迎指出,感激不尽!
:::
::: hljs-center
欢迎关注公众号,会分享一些更日常的东西!
:::
::: hljs-center
如需转载,请标注出处!
:::
::: hljs-center

:::

            </div>
      
      <div id="asideoffset"></div>

https://blog.51cto.com/dlhjw/4754076
页: [1]
查看完整版本: Redis | 第8章 发布订阅与事务《Redis设计与实现》#yyds干货盘点#