redis学习(十三)——订阅与发布

定义

一个或多个客户端订阅某个channel,当向该channel发布消息时,已经订阅该channel的所有客户端均会收到消息。channel通常称为频道

类型

基于频道(channel)

命令

  • 订阅
SUBSCRIBE channel [channel ...]
  • 取消订阅
UNSUBSCRIBE [channel [channel ...]]
  • 发布
publish channel message
  • 查看通道订阅数
PUBSUB NUMSUB [channel [channel ...]]

源码分析

pubsub_channels定义的属性是一个字典类型,保存着客户端和channel信息,key 值保存的就是频道名value 是一个链表,链表中保存的是客户端 id

img

  • 结构体定义:
// pubsub_channels

struct redisServer {
    //...
    
    /* Pubsub */
    dict *pubsub_channels;  /* Map channels to list of subscribed clients */
    dict *pubsub_patterns;  /* A dict of pubsub_patterns */
    int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
                                   xor of NOTIFY_... flags. */
    dict *pubsubshard_channels;  /* Map shard channels to list of subscribed clients */
    
    //...
};
  • 订阅源码分析:
// redis\src\pubsub.c

/* 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, pubsubtype type) {
    dictEntry *de;
    list *clients = NULL;
    int retval = 0;

    /* Add the channel to the client -> channels hash table */
    // 添加频道至字典
    if (dictAdd(type.clientPubSubChannels(c),channel,NULL) == DICT_OK) {
        retval = 1;
        incrRefCount(channel);
        /* Add the client to the channel -> list of clients hash table */
        // 添加客户端至链表
        de = dictFind(*type.serverPubSubChannels, channel);
        if (de == NULL) {
            // 如果找不到,创建并添加
            clients = listCreate();
            dictAdd(*type.serverPubSubChannels, channel, clients);
            incrRefCount(channel);
        } else {
            // 找得到,获取链表
            clients = dictGetVal(de);
        }
        // 将客户端添加至链表尾部
        listAddNodeTail(clients,c);
    }
    /* Notify the client */
    // 客户端通知
    addReplyPubsubSubscribed(c,channel,type);
    return retval;
}
  • 取消订阅源码分析:
// redis\src\pubsub.c

/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
 * 0 if the client was not subscribed to the specified channel. */
// 1:成功 0:失败
int pubsubUnsubscribeChannel(client *c, robj *channel, int notify, pubsubtype type) {
    dictEntry *de;
    list *clients;
    listNode *ln;
    int retval = 0;

    /* Remove the channel from the client -> channels hash table */
    incrRefCount(channel); /* channel may be just a pointer to the same object
                            we have in the hash tables. Protect it... */
    if (dictDelete(type.clientPubSubChannels(c),channel) == DICT_OK) {
        retval = 1;
        /* Remove the client from the channel -> clients list hash table */
        // 从频道中删除客户端
        de = dictFind(*type.serverPubSubChannels, channel);
        serverAssertWithInfo(c,NULL,de != NULL);
        // 获取客户端链表
        clients = dictGetVal(de);
        // 查找链表中对应节点
        ln = listSearchKey(clients,c);
        serverAssertWithInfo(c,NULL,ln != NULL);
        listDelNode(clients,ln);
        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(*type.serverPubSubChannels, channel);
            /* As this channel isn't subscribed by anyone, it's safe
             * to remove the channel from the slot. */
            if (server.cluster_enabled & type.shard) {
                slotToChannelDel(channel->ptr);
            }
        }
    }
    /* Notify the client */
    // 通知客户端
    if (notify) {
        addReplyPubsubUnsubscribed(c,channel,type);
    }
    decrRefCount(channel); /* it is finally safe to release it */
    return retval;
}

基于模式

命令

  • 订阅
# 订阅一个或多个符合给定模式的频道
PSUBSCRIBE pattern [pattern ...]
  • 取消订阅
# 取消模式订阅
PUNSUBSCRIBE [pattern [pattern ...]]
  • 获取订阅模式数量
# 返回订阅模式的数量,客户端订阅的所有模式的数量总和。时间复杂度 O(1)
PUBSUB NUMPAT

示例

订阅端

# 订阅端
127.0.0.1:6379> PSUBSCRIBE test.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "test.*"
3) (integer) 1
1) "pmessage"
2) "test.*"
3) "test.list"
4) "hello redis"
1) "pmessage"
2) "test.*"
3) "test.set"
4) "hello go"

发布端

# 发布端
127.0.0.1:6379> PUBLISH test.list "hello redis"
(integer) 1
127.0.0.1:6379> PUBLISH test.set "hello go"
(integer) 1
127.0.0.1:6379>

取消订阅

127.0.0.1:6379> PUNSUBSCRIBE test.*
1) "punsubscribe"
2) "test.*"
3) (integer) 1

源码分析

如果有某个/某些模式和该频道匹配,所有订阅这个/这些频道的客户端也同样会收到信息。

img

  • 结构体:
// pubsub_patterns

struct redisServer {
    //...
    
    /* Pubsub */
    dict *pubsub_channels;  /* Map channels to list of subscribed clients */
    dict *pubsub_patterns;  /* A dict of pubsub_patterns */
    int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
                                   xor of NOTIFY_... flags. */
    dict *pubsubshard_channels;  /* Map shard channels to list of subscribed clients */
    
    //...
};
  • 订阅源码分析:
// redis\src\pubsub.c

/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
// 1:成功 0:客户端已订阅
int pubsubSubscribePattern(client *c, robj *pattern) {
    dictEntry *de;
    list *clients;
    int retval = 0;

    if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
        retval = 1;
        // 新增pattern添加至链表尾部
        listAddNodeTail(c->pubsub_patterns,pattern);
        incrRefCount(pattern);
        /* Add the client to the pattern -> list of clients hash table */
        de = dictFind(server.pubsub_patterns,pattern);
        if (de == NULL) {
            clients = listCreate();
            dictAdd(server.pubsub_patterns,pattern,clients);
            incrRefCount(pattern);
        } else {
            clients = dictGetVal(de);
        }
        listAddNodeTail(clients,c);
    }
    /* Notify the client */
    addReplyPubsubPatSubscribed(c,pattern);
    return retval;
}
  • 取消订阅源码分析:
// redis\src\pubsub.c

/* 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) {
    dictEntry *de;
    list *clients;
    listNode *ln;
    int retval = 0;

    incrRefCount(pattern); /* Protect the object. May be the same we remove */
    if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) {
        retval = 1;
        listDelNode(c->pubsub_patterns,ln);
        /* Remove the client from the pattern -> clients list hash table */
        // 删除该模式下的客户端
        de = dictFind(server.pubsub_patterns,pattern);
        serverAssertWithInfo(c,NULL,de != NULL);
        clients = dictGetVal(de);
        ln = listSearchKey(clients,c);
        serverAssertWithInfo(c,NULL,ln != NULL);
        listDelNode(clients,ln);
        if (listLength(clients) == 0) {
            /* Free the list and associated hash entry at all if this was
             * the latest client. */
            // 如果没有客户端,则完全释放列表和关联的哈希条目
            dictDelete(server.pubsub_patterns,pattern);
        }
    }
    /* Notify the client */
    if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
    decrRefCount(pattern);
    return retval;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值