环境说明:redis源码版本 5.0.3;我在阅读源码过程做了注释,git地址:https://gitee.com/xiaoangg/redis_annotation 如有错误欢迎指正
参考书籍:《redis的设计与实现》

目录

一 频道的订阅与退订

1.1订阅频道

1.2 取消订阅

二 模式的订阅与退订

2.1订约模式

2.2取消订约模式

三 发送消息

四 查看订阅信息



Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

Redis 客户端通过subscribe可以订阅任意数量的频道;

发布与订阅的命令:

  • PSUBSCRIBE pattern [pattern ...] 订阅一个或多个符合给定模式的频道。
  • SUBSCRIBE channel [channel ...] 订阅给定的一个或多个频道的信息。
  • UNSUBSCRIBE channel [channel ...]   命令用于退订给定的一个或多个频道的信息。
  • PSUBSCRIBE pattern [pattern ...]    订阅一个或多个符合给定模式的频道。
  • PUNSUBSCRIBE [pattern [pattern ...]]    退订所有给定模式的频道。
  • PUBSUB <subcommand> [argument [argument ...]]   查看订阅与发布系统状态,它由数个不同格式的子命令组成。

一 频道的订阅与退订

当一个客户端执行SUBSCRIIBE命令时,客户端和chanel之间就建立了订阅关系;

client结构体中哈希表pubsub_channels属性记录 当前客户端订阅的所有channel;

server服务端状态哈希表pubsub_channels属性 记录了订阅channel的所有客户端信息;

server.h/redisServer

struct redisServer {
    //.............

     /* 订约发布相关 */
    /* Pubsub */
    dict *pubsub_channels;  /* Map channels to list of subscribed clients */ //记录订约每个channel的客户端信息    

    //...............
}

server.h/client

typedef struct client {
   
     //.........
    dict *pubsub_channels;  /* channels a client is interested in (SUBSCRIBE) */ // 
                            /*发布/订约相关信息, clien订约的channel信息,SUBSCRIBE  */
    list *pubsub_patterns;  /* patterns a client is interested in (SUBSCRIBE) */
    sds peerid;             /* Cached peer ID. */
 
   //.............
  
} client;

1.1订阅频道

客户单执行SUBSCRITEB命令订阅某个频道;

服务端收到命令后将执行以下操作:

  1. 将channel信息添加到clien的pubsub_channels哈希表中,如果channel已经在pubsub_channels表中,不会重复添加;
  2. 将客户端添加到 服务端状态server 的pubsub_channels哈希表中; 同样 如果channel已经在pubsub_channels表中,不会重复添加;

SUBSCRITEB命令的实现代码位于 pubsub.c/pubsubSubscribeChannel()
 

/**
 * 为客户机订阅频道。
 * 操作成功,则返回1;
 * 如果客户端已订阅该通道,则返回0
 */ 
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
 * 0 if the client was already subscribed to that channel. */
int pubsubSubscribeChannel(client *c, robj *channel) {
    dictEntry *de;
    list *clients = NULL;
    int retval = 0;

    /**
     * 将channel记录到client.pubsub_channels 哈希表
     * 注意 dictAdd函数,如果channel已经存在pubsub_channels哈希表中,将不会重复添加
     */ 
    /* Add the channel to the client -> channels hash table */
    if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
        retval = 1;
        incrRefCount(channel);
        
        //订约这个channel的客户端列表
        /* Add the client to the channel -> list of clients hash table */
        de = dictFind(server.pubsub_channels,channel);
        
        if (de == NULL) {
            clients = listCreate(); 
            dictAdd(server.pubsub_channels,channel,clients);
            incrRefCount(channel);
        } else {
            clients = dictGetVal(de);
        }
        
        listAddNodeTail(clients,c);
    }
    /* Notify the client */
    addReply(c,shared.mbulkhdr[3]);
    addReply(c,shared.subscribebulk);
    addReplyBulk(c,channel);
    addReplyLongLong(c,clientSubscriptionsCount(c));
    return retval;
}

1.2 取消订阅

客户单执行UNSUBSCRITEB命令取消订阅某个频道;

操作正好的SUBSCRITEB命令的操作相反:

  1. 将channel信息 从clien的pubsub_channels哈希表中删除;
  2. 将客户端 从 服务端状态server 的pubsub_channels哈希表中删除;

UNSUBSCRITEB命令的实现位于 pubsub.c/unsubscribeCommand

删除单个channel和删除所有channel的实现都是调用的pubsub.c/pubsubUnsubscribeChannel():

/**
 * 从频道取消订阅客户端。如果操作成功,则返回1;如果客户端未订阅指定通道,则返回0。
 */ 
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
 * 0 if the client was not subscribed to the specified channel. */
int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
    dictEntry *de;
    list *clients;
    listNode *ln;
    int retval = 0;

    // 从client->pubsub_channels 哈希表中移除从channel
    /* Remove the channel from the client -> channels hash table */

    //TODO 理解引用计数为啥要+1; channel可能只是指向哈希表中相同对象的指针。引用计数+1保护它 
    incrRefCount(channel); /* channel may be just a pointer to the same object
                            we have in the hash tables. Protect it... */
    
    //dictDelete 从client->pubsub_channels 哈希表中移除从channel
    if (dictDelete(c->pubsub_channels,channel) == DICT_OK) {
        retval = 1;

        //从server->pubsub_channels哈希表中移除channel
        /* Remove the client from the channel -> clients list hash table */
        de = dictFind(server.pubsub_channels,channel);
        serverAssertWithInfo(c,NULL,de != NULL);
        clients = dictGetVal(de);
        ln = listSearchKey(clients,c);
        serverAssertWithInfo(c,NULL,ln != NULL);
        listDelNode(clients,ln);

        //如何channel已经没有clien订阅他了,释放server.pubsub_channels的channel
        if (listLength(clients) == 0) {
            /* Free the list and associated hash entry at all if this was
             * the latest client, so that it will be possible to abuse
             * Redis PUBSUB creating millions of channels. */
            dictDelete(server.pubsub_channels,channel);
        }
    }
    /* Notify the client */
    if (notify) {
        addReply(c,shared.mbulkhdr[3]);
        addReply(c,shared.unsubscribebulk);
        addReplyBulk(c,channel);
        addReplyLongLong(c,dictSize(c->pubsub_channels)+
                       listLength(c->pubsub_patterns));

    }
    
    //TODO 这个应该是和上面的incrRefCount呼应,理解这两块
    decrRefCount(channel); /* it is finally safe to release it */
    return retval;
}

 

二 模式的订阅与退订

除了SUBSCRIBE和UNSCRIBE命令,redis还支持GLOB风格的订阅命令;

和订阅类似 
client结构体中列表pubsub_patterns属性记录 当前客户端订阅的所有channel;
server服务端状态中列表pubsub_patterns属性 记录所有的订阅模式,pubsub_patterns是个server.h/pubsubPattern结构体列表;

server.h/redisServer

struct redisServer {
    //.............

     /* 订约发布相关 */
    /* Pubsub */
    list *pubsub_patterns;  /* A list of pubsub_patterns */ //所有 模式订阅 的列表
    //...............
}

server.h/client

typedef struct client {
   
     //.........
    list *pubsub_patterns;  /* patterns a client is interested in (SUBSCRIBE) */
                         /*发布/订约, client 模式订约的channel信息,SUBSCRIBE  */
   //.............
  
} client;

server.h/pubsubPattern

/**
 * 模式订阅
 * server->pubsub_patterns属性就是pubsubPattern列表
 */ 
typedef struct pubsubPattern {
    client *client; //订阅的客户端
    robj *pattern;  //模式信息
} pubsubPattern;

2.1订约模式

客户端执行PSUBSCRITEB命令订阅某个模式;

服务端收到命令后执行以下操作:

  1. 将参数中的pattern添加到客户端的client->pubsub_patterns列表属性中;
  2. 将pubsubPattern类型的订阅模式 添加到server->pubsub_patterns列表中;

PSUBSCRITEB命令的实现入口pubsub.c/pubsubSubscribePattern()
 

/**
 * 客户端模式订阅
 */ 
/** Subscribe a client to a pattern. 
 * Returns 1 if the operation succeeded,
 * or 0 if the client was already subscribed to that pattern. 
 */
int pubsubSubscribePattern(client *c, robj *pattern) {
    int retval = 0;
    
    //client没有订阅pattern
    if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
        retval = 1;
        pubsubPattern *pat;
        
        //pattern添加到client没有订阅pattern->pubsub_patterns列表中
        listAddNodeTail(c->pubsub_patterns,pattern);
        incrRefCount(pattern);
        pat = zmalloc(sizeof(*pat));
        pat->pattern = getDecodedObject(pattern);
        pat->client = c;
        //将pubsubPattern pat添加到server->pubsub_patterns列表中
        listAddNodeTail(server.pubsub_patterns,pat);
    }
    /* Notify the client */
    addReply(c,shared.mbulkhdr[3]);
    addReply(c,shared.psubscribebulk);
    addReplyBulk(c,pattern);
    addReplyLongLong(c,clientSubscriptionsCount(c));
    return retval;
}

2.2取消订约模式

同样取消和 订阅 操作正好相反,PUNSUBSCRIPT命令的实现位于 pubsub.c/pubsubUnsubscribePattern()

/**
 * 取消订阅模式;
 * 操作成功返回1,失败返回0;
 */ 
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
 * 0 if the client was not subscribed to the specified channel. */
int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
    listNode *ln;
    pubsubPattern pat;
    int retval = 0;

    incrRefCount(pattern); /* Protect the object. May be the same we remove */
    
    //从客户端client->pubsub_patterns 查找pattern
    if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) {
        retval = 1;
        
        //从client->pubsub_patterns 列表中删除pubsub_patterns
        listDelNode(c->pubsub_patterns,ln);
        pat.client = c;
        pat.pattern = pattern;
        //从server->pubsub_patterns 列表中删除pat
        ln = listSearchKey(server.pubsub_patterns,&pat);
        listDelNode(server.pubsub_patterns,ln);
    }
    /* Notify the client */
    if (notify) {
        addReply(c,shared.mbulkhdr[3]);
        addReply(c,shared.punsubscribebulk);
        addReplyBulk(c,pattern);
        addReplyLongLong(c,dictSize(c->pubsub_channels)+
                       listLength(c->pubsub_patterns));
    }
    decrRefCount(pattern);
    return retval;
}

 

三 发送消息

当有客户端执行PUBLISH <channel> <msg> ,将msg发送给channel时,服务端执行以下两个操作:

  1. 将消息发送给channel订阅者
  2. 将消息发送给模式订阅者

代码实现的入口:pubsub.c/pubsubPublishMessage

/**
 * 推送一条消息
 */ 
/* Publish a message */
int pubsubPublishMessage(robj *channel, robj *message) {
    int receivers = 0;
    dictEntry *de;
    listNode *ln;
    listIter li;

    /* 从server.pubsub_channels查找channel。没有找到说明没有客户端订阅该channel */
    /* Send to clients listening for that channel */
    de = dictFind(server.pubsub_channels,channel);
    if (de) {
        list *list = dictGetVal(de);
        listNode *ln;
        listIter li;

        listRewind(list,&li);
        //遍历channel的订阅者发送消息
        while ((ln = listNext(&li)) != NULL) {
            client *c = ln->value;

            addReply(c,shared.mbulkhdr[3]);
            addReply(c,shared.messagebulk);
            addReplyBulk(c,channel);
            addReplyBulk(c,message);
            receivers++;
        }
    }
    
    /* 遍历模式订阅者 发送消息*/
    /* Send to clients listening to matching channels */
    if (listLength(server.pubsub_patterns)) {
        listRewind(server.pubsub_patterns,&li);
        channel = getDecodedObject(channel);
        
        //遍历模式订阅,匹配成功发送消息
        while ((ln = listNext(&li)) != NULL) {
            pubsubPattern *pat = ln->value;

            if (stringmatchlen((char*)pat->pattern->ptr,
                                sdslen(pat->pattern->ptr),
                                (char*)channel->ptr,
                                sdslen(channel->ptr),0)) {
                addReply(pat->client,shared.mbulkhdr[4]);
                addReply(pat->client,shared.pmessagebulk);
                addReplyBulk(pat->client,pat->pattern);
                addReplyBulk(pat->client,channel);
                addReplyBulk(pat->client,message);
                receivers++;
            }
        }
        decrRefCount(channel);
    }
    return receivers;
}

 

四 查看订阅信息

redis可以通过PUBSUB subcommand命令查看频道或者模式相关信息;

PUBSUB的实现入口位于pubsub.c/pubsubCommand;

可以通过代码看到pubsub命令支持以下子命令:

  • PUBSUB help  查帮助 
  • PUBSUB CHANNELS [<pattern>]     获取当前服务器所有被订阅的channel,可选参数pattern:和pattern匹配的    遍历server->pubsub_channels;时间复杂度O(n)
  • PUBSUB NUMSUB [Channel_1 ... Channel_N] 返回给定各个channel的订约这数量 遍历server->pubsub_channels;时间复杂度O(k) 传入参数的数量
  • PUBSUB NUMPAT   返回服务器当前被订约的模式的数量    直接返回server->pubsub_patterns 列表长度 ;时间复杂度O(1)
/**
 */ 
/* PUBSUB command for Pub/Sub introspection. */
void pubsubCommand(client *c) {
    if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
        const char *help[] = {
"CHANNELS [<pattern>] -- Return the currently active channels matching a pattern (default: all).",
"NUMPAT -- Return number of subscriptions to patterns.",
"NUMSUB [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).",
NULL
        };
        addReplyHelp(c, help);
    } else if (!strcasecmp(c->argv[1]->ptr,"channels") &&
        (c->argc == 2 || c->argc == 3))
    {   //获取当前服务器被订约的channel,可选参数pattern:返回被订约的与pattern模式匹配的频道
        /* PUBSUB CHANNELS [<pattern>] */
        sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr;
        dictIterator *di = dictGetIterator(server.pubsub_channels);
        dictEntry *de;
        long mblen = 0;
        void *replylen;

        replylen = addDeferredMultiBulkLength(c);
    
        //遍历server.pubsub_channels 
        while((de = dictNext(di)) != NULL) {
            robj *cobj = dictGetKey(de);
            sds channel = cobj->ptr;

            //匹配了pattern可选参数的channel
            if (!pat || stringmatchlen(pat, sdslen(pat),
                                       channel, sdslen(channel),0))
            {
                addReplyBulk(c,cobj);
                mblen++;
            }
        }
        dictReleaseIterator(di);
        setDeferredMultiBulkLength(c,replylen,mblen);
    } else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) {
        // 返回给定 各个chanenl 订约者的数量
        /* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
        int j;

        addReplyMultiBulkLen(c,(c->argc-2)*2);
        for (j = 2; j < c->argc; j++) {
            list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);

            addReplyBulk(c,c->argv[j]);
            addReplyLongLong(c,l ? listLength(l) : 0);
        }
    } else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) {
        //返回服务器当前被订约的模式的数量
        /* PUBSUB NUMPAT */
        addReplyLongLong(c,listLength(server.pubsub_patterns));
    } else {
        addReplySubcommandSyntaxError(c);
    }
}