Redis设计与实现总结--独立功能的实现

1、发布与订阅

        Redis的发布订阅功能由PUBLISH、SUBCRIBE、PSUBCRIBE等命令组成。通过执行SUBSCRIBE命令,客户端可以订阅一个或多个频道,从而成为这些频道的订阅者(subscriber):每当有其他客户端向被订阅的频道发送消息(message)时,频道的所有订阅者都会收到这条消息。

1.1 频道的订阅与退订

        Redis将所有频道的订阅关系都保存在服务器状态的pubsub_channels字典里面,这个字典的键是某个被订阅的频道,而键的值则是一个链表,链表里面记录了所有订阅这个频道的客户端。

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

        当客户端订阅(SUBSCRIBE)一个频道,服务器会在pubsub_channels字典里找到对应的频道键(没有则新创建一个键),将客户端添加到列表尾部。当客户端退订(UNSUBSCRIBE)一个频道,服务器会在pubsub_channels字典里找到对应的频道键,将客户端从列表中删除。如果删除退订客户端后列表为空,说明这个频道没有订阅者了,那么Redis会删除对应的键。

1.2 模式的订阅与退订

        服务器也将所有模式的订阅关系都保存在服务器状态的pubsub_patterns属性里面。pubsub_patterns属性是一个链表,链表中的每个节点都包含着一个pubsubPattern结构,这个结构的pattern属性记录了被订阅的模式,而client属性则记录了订阅模式的客户端。

struct redisServer {
    //保存所有模式订阅关系
    list *pubsub_patterns;
}

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

        当客户端订阅(PSUBSCRIBE)某个模式时,服务器会新建一个pubsubPattern结构,将结构的pattern属性设置为被订阅的模式,client属性设置为订阅模式的客户端。并把这个结构添加到pubsub_patterns链表的表尾。当客户端退订(PUNSUBSCRIBE)某个模式时,Redis将会在pubsub_patterns链表中删除掉对应的pubsubPattern结构。

1.3 发送消息

        当一个Redis客户端执行PUBLISH <channel> <message>命令时,服务器需要执行如下操作:

  • 因为服务器状态中的pubsub_channels字典记录了所有频道的订阅关系,所以为了将消息发送给channel频道的所有订阅者,PUBLISH命令要做的就是在pubsub_channels字典里找到频道channel的订阅者名单(一个链表),然后将消息发送给名单上的所有客户端。        
  • 因为服务器状态中的pubsub_patterns链表记录了所有模式的订阅关系,所以为了将消息发送给所有与channel频道相匹配的模式的订阅者,PUBLISH命令要做的就是遍历整个pubsub_patterns链表,查找那些与channel频道相匹配的模式,并将消息发送给订阅了这些模式的客户端。

1.4 查看订阅消息

        PUBSUB命令是Redis2.8新增加的命令之一,客户端可以通过这个命令来查看频道或者模式的相关信息。

  • PUBSUB CHANNELS [pattern]用于返回服务器当前被订阅的频道,其中pattern参数是可选的。是通过遍历服务器pubsub_channels字典的所有键(每个键都是一个被订阅的频道),然后记录并返回所有符合条件的频道来实现的。
  • PUBSUB NUMSUB [channel-1 channel-2...channel-n]命令接收任意多个频道作为输入参数,并返回这些频道的订阅者数量。是通过在pubsub_channels字典中找到频道对应的订阅者链表,然后返回订阅者链表的长度来实现的(订阅者链表的长度就是频道订阅者的数量) 。
  • PUBSUB NUMPAT用于返回服务器当前被订阅模式的数量。是通过返回pubsub_patterns链表的长度来实现的,因为这个链表的长度就是 服务器被订阅模式的数量。

2、事务

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

2.1 事务的实现

2.1.1 事务开始

        MULTI命令的执行标志着事务的开始,MULTI命令可以将执行该命令的客户端从非事务状态切换至事务状态,这一切换是通过在客户端状态的flags属性中打开REDIS_MULTI标识来完成的。

2.1.2 命令入队

        服务器判断命令是该入队还是执行的过程如下:

在这里插入图片描述

2.1.3 事务队列 

        每个Redis客户端都有自己的事务状态,这个事务状态保存在客户端状态的mstate属性里面:

typedef struct redisClient {
    //事务状态
    multiState mstate;
} redisClient; 

事务状态包含一个事务队列,以及一个已入队命令的计数器

typedef struct multiState {
    //事务队列,FIFO 顺序
    multiCmd *commands;
 
    //已入队命令计数
    int count; 
} multiState; 

事务队列是一个multiCmd类型的数组,数组中的每个multiCmd结构都保存了一个已入队命令的相关信息,包括指向命令实现函数的指针、命令的参数,以及参数的数量:

typedef struct multiCmd {
    //参数
    robj **argv;
 
    //参数数量
    int argc;
 
    //命令指针
    struct redisCommand *cmd; 
} multiCmd; 

在这里插入图片描述

2.1.4 事务执行

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

2.2 WATCH命令的实现

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

2.2.1 使用WATCH命令监视数据库键

        每个Redis数据库都保存着一个watched_keys字典,这个字典的键是某个被WATCH命令监视的数据库键,而字典的值则是一个链表,链表中记录了所有监视相应数据库键的客户端。

typedef struct redisDB{
    //正在被WATCH命令监视的键
    dict *watched_keys;	//键为被监视的数据库键, 值为监视该键的客户端链表
}

2.2.2 WATCH机制的触发

        当数据库执行修改命令之后,都会调用 touchWatchKey函数对 watches_keys字典进行检查, 检查是否有客户端正在监视刚刚被命令修改过的数据库键, 如果有的话,那么touchWatchKey函数会将监视被修改键的客户端的REDIS_DIRTY_CAS标志打开, 表示该客户端的事务安全性被破坏了。

2.2.3 判断事务是否安全

        当服务端收到一个客户端发来的EXEC命令时,服务器会根据这个客户端是否打开了REDIS_DIRTY_CAS标志来决定是否执行事务。

2.3 事务的ACID性质

        在Redis中事务总是具有原子性(Atomicity)、一致性(Consistency)、隔离性 (Isolation),当Redis运行在某种特定的持久化模式下时,事务也具有耐久性 (Durability)。Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制 (rollback),即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。

3、排序

        Redis的SORT命令可以对列表键、集合键或者有序集合键的值进行排序。

3.1 SORT<key>命令的实现

        服务器执行SORT numbers命令的详细步骤如下:

3.2  ALPHA选项的实现

        通过使用ALPHA选项,SORT命令可以对包含字符串值的键进行排序 。

 3.3 ASC选项和DESC选项的实现

         默认情况,SORT命令执行升序排序,执行SORT命令时使用DESC选项,可以让命令执行降序排序,让排序后的结果按值的大小从大到小排列。

 3.4 BY选项的实现

         在默认情况下,SORT命令使用被排序键包含的元素作为排序的权重,元素本身决定了元素在排序后所处的位置。通过BY选项,SORT命令可以指定某些字符串键,或者某个哈希键所包含的某些域(filed)来作为元素的权重,对一个键进行排序。

3.5 带有ALPHA选项的BY选项的实现 

         BY选项默认假设权重键保存的值为数字值,如果权重键保存的是字符串值的话,就需要在使用BY选项时,配合使用ALPHA选项。

3.6 LIMIT选项的实现 

        LIMIT <offset> <count>

  • offset表示要跳过已排序元素数量
  • count表示跳过给定数量的已排序元素之后,要返回的已排序元素数量。

 3.7 GET选项的实现

         默认情况下,SORT命令在对键进行排序之后,总是返回被排序键本身所包含的元素。通过使用GET选项,我们可以让SORT命令返回GET选项所指定模式对应的某些键的值。

 3.8 STORE选项的实现

        通过使用STORE选项,我们可以将排序结果保存在指定的键里面,并在有需要时重用这个排序结果。

 3.9 多个选项的执行顺序

        排序>限制排序结果集的长度>获取外部键>保存排序结果>向客户端返回排序结果集 

 3.10 选项的摆放顺序

         除了GET选项之外,改变选项的摆放顺序并不会硬性SORT命令执行这些选项的顺序。

4、慢查询日志

        服务器配置有两个和慢查询日志相关的选项:

  • slowlog-log-slower-than 选项指定执行时间超过多少微秒(1 秒等于 1,000,000 微秒)的命令请求会被记录到日志上。
  • slowlog-max-len 选项指定服务器最多保存多少条慢查询日志。服务器使用先进先出的方式保存多条慢查询日志。

        服务器状态中包含了几个和慢查询日志功能有关的属性:

struct redisServer {

    // ...

    // 下一条慢查询日志的 ID
    long long slowlog_entry_id;

    // 保存了所有慢查询日志的链表
    list *slowlog;

    // 服务器配置 slowlog-log-slower-than 选项的值
    long long slowlog_log_slower_than;

    // 服务器配置 slowlog-max-len 选项的值
    unsigned long slowlog_max_len;

    // ...

};

slowlog 链表保存了服务器中的所有慢查询日志, 链表中的每个节点都保存了一个 slowlogEntry 结构, 每个 slowlogEntry 结构代表一条慢查询日志:

typedef struct slowlogEntry {

    // 唯一标识符
    long long id;

    // 命令执行时的时间,格式为 UNIX 时间戳
    time_t time;

    // 执行命令消耗的时间,以微秒为单位
    long long duration;

    // 命令与命令参数
    robj **argv;

    // 命令与命令参数的数量
    int argc;

} slowlogEntry;

在这里插入图片描述

5、监视器

        通过执行MONITOR命令,客户端可以将自己变为一个监视器,实时地接收并打印服务器当前处理的命令请求的相关信息。当其他客户端向服务器发送一条命令请求时,服务器除了会处理这条命令请求之外,还会将这条命令请求的信息发送给所有监视器。

        redisServer 维护一个 monitors 的链表,记录自己的监视器,每次收到 MONITOR 命令之后,将客户端追加到链表尾。伪代码如下:

def MONITOR():
    # 打开客户端的监视器标志
    client.flags |= REDIS_MONITOR
    
    # 将客户端添加到服务器状态的monitors链表的末尾
    server.monitors.append(client)

    # 向客户端返回OK
    send_reply("ok")

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值