redis4-发布与订阅

1. channel的订阅与退订
  • UBSCRIBE, UNSUBSCRIBE and PUBLISH implement the Publish/Subscribe messaging paradigm where senders (publishers) are not programmed to send their messages to specific receivers (subscribers). Rather, published messages are characterized into channels, without knowledge of what (if any) subscribers there may be. Subscribers express interest in one or more channels, and only receive messages that are of interest, without knowledge of what (if any) publishers there are.
  • Messages sent by other clients to these channels will be pushed by Redis to all the subscribed clients.
  • A client subscribed to one or more channels should not issue commands, although it can subscribe and unsubscribe to and from other channels. The replies to subscription and unsubscription operations are sent in the form of messages, so that the client can just read a coherent stream of messages where the first element indicates the type of message. The commands that are allowed in the context of a subscribed client are SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE, PUNSUBSCRIBE, PING and QUIT.

    • Please note that redis-cli will not accept any commands once in subscribed mode and can only quit the mode with Ctrl-C.
  • channel订阅实现:在客户端结构体的pubsub_channels(为dict)添加key为订阅的channel的entry,在server结构体的pubsub_chanels(dict)添加key为channel的entry,该entry为所有订阅该channel的list
2.Pattern-matching订阅与退订
  • The Redis Pub/Sub implementation supports pattern matching. Clients may subscribe to glob-style patterns in order to receive all the messages sent to channel names matching a given pattern.
  • pattern订阅实现:在客户端结构体的pubsub_patterns(为list)添加node,该node的value为订阅的pattern(robj),在server结构体的pubsub_patterns(为list)添加pubsubPattern Node,该node的client为订阅该pattern的客户端指针,该pattern为订阅pattern的robj.
//src/server.h
typedef struct pubsubPattern {
     client *client;                                                
     robj *pattern;                                                   
} pubsubPattern;                                                     
3.源码分析
  • 订阅channel代码:
/* 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;

    /* Add the channel to the client -> channels hash table */
    //将订阅的channel添加到client结构体的pubsub_channels字典
    //key为channel
    if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) {
        retval = 1;
        incrRefCount(channel);
        /* Add the client to the channel -> list of clients hash table */
        //将channel添加到server的pubsub_channels字典
        //首先在dict中查找是否该channel存在,如果存在,将c添加到entry的value链表中
        //如果不存在,在字典中添加entry,该entry的key为robj,value为client的链表
        de = dictFind(server.pubsub_channels,channel);
        if (de == NULL) {
            //den=NULL表示该channel在dict不存在
            clients = listCreate();
            dictAdd(server.pubsub_channels,channel,clients);
            //robj引用+1
            incrRefCount(channel);
        } else {
            //该channel在dict中已经存在
            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;
}
  • 订阅pattern代码:
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
//订阅一个pattern
int pubsubSubscribePattern(client *c, robj *pattern) {
    int retval = 0;

    //首先查找链表中是否存在该pattern
    if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
        retval = 1;
        pubsubPattern *pat;
        //如果不存在,则添加到client的pubsub_patterns链表结尾
        listAddNodeTail(c->pubsub_patterns,pattern);
        //增加引用计数
        incrRefCount(pattern);

        //将该pattern添加到server的pubsub_patterns链表 
        pat = zmalloc(sizeof(*pat));
        pat->pattern = getDecodedObject(pattern);
        pat->client = c;
        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;
}
4.参考文献

redis.io

redis_reading

<think>好的,用户想了解如何在redis-plus-plus库中使用发布订阅功能,需要示例代码。首先,我需要回忆一下redis-plus-plus的基本用法,特别是发布订阅相关的接口。 记得redis-plus-plus是一个C++的Redis客户端,支持同步和异步操作。发布订阅模式通常涉及订阅频道、发布消息以及处理接收的消息。用户可能需要知道如何创建订阅对象,如何发布消息,以及如何处理接收到的消息。 查看用户提供的引用内容,虽然大部分是关于Spring Boot和Java的,但其中提到了发布订阅的基本概念,比如使用通配符订阅、消息监听器的配置等。不过这些是Java的实现,而用户现在问的是C++的redis-plus-plus库,所以需要转换思路。 首先,确认redis-plus-plus的文档中关于发布订阅的部分。通常,发布订阅需要用到subscriber对象。用户需要创建一个subscriber,订阅特定的频道或模式,然后定义回调函数来处理接收到的消息。发布消息则使用publish命令。 接下来,考虑示例代码的结构。可能需要展示如何连接Redis,创建subscriber,订阅频道,以及发布消息。同时,处理消息的回调函数需要正确设置,可能涉及到多线程,因为订阅会阻塞当前线程,所以通常会在单独的线程中运行订阅循环。 还要注意错误处理,比如网络中断或订阅失败的情况。但为了示例简洁,可能先忽略这些,专注于基本用法。 用户提供的引用中有提到通配符订阅,比如使用PatternTopic,这在redis-plus-plus中可能对应的是psubscribe方法,允许使用通配符订阅多个频道。因此,在示例中可以展示普通订阅和通配符订阅两种方式。 另外,引用中提到了异步处理和消息队列,但redis-plus-plus的发布订阅默认是同步的,可能需要使用异步接口或者多线程来实现非阻塞操作。不过,根据库的文档,订阅循环通常是阻塞的,所以需要在单独的线程中运行。 总结步骤: 1. 引入头文件和创建Redis连接。 2. 创建subscriber对象并订阅频道或模式。 3. 定义消息、订阅成功、取消订阅的回调函数。 4. 在另一个线程中运行订阅循环以接收消息。 5. 在发布端使用publish命令发送消息。 6. 处理可能的异常和资源清理。 然后,将这些步骤转化为代码示例,确保语法正确,并添加注释说明每个部分的作用。同时,需要提醒用户关于线程管理和阻塞的问题,比如订阅循环会阻塞线程,因此需要在独立线程中运行,避免主线程被阻塞。 可能的问题包括回调函数的线程安全性,以及如何正确关闭订阅。但示例中可能不涉及这些高级话题,保持简单明了。 最后,根据用户的要求生成相关问题,比如性能分析、多频道处理、通配符使用、异步实现等,这些问题应该围绕redis-plus-plus库的发布订阅功能展开。</think>以下是基于redis-plus-plus库实现Redis发布订阅功能的详细说明及示例代码: ### 一、redis-plus-plus发布订阅基础用法 redis-plus-plus通过`subscriber`对象实现订阅功能,发布消息直接使用`publish`命令。订阅流程需要单独线程运行以避免阻塞主线程[^1]。 ```cpp #include <sw/redis++/redis++.h> #include <thread> using namespace sw::redis; // 消息回调函数 void handle_message(const std::string& channel, const std::string& msg) { std::cout << "收到频道[" << channel << "]消息: " << msg << std::endl; } // 订阅成功回调 void handle_subscribed(const std::string& channel, long long count) { std::cout << "已订阅频道[" << channel << "],当前订阅总数: " << count << std::endl; } // 取消订阅回调 void handle_unsubscribed(const std::string& channel, long long count) { std::cout << "已取消订阅[" << channel << "],剩余订阅数: " << count << std::endl; } int main() { // 1. 创建Redis连接 auto redis = Redis("tcp://127.0.0.1:6379"); // 2. 创建订阅者对象 auto subscriber = redis.subscriber(); // 3. 注册回调函数 subscriber.on_message(handle_message); subscriber.on_psubscribe(handle_subscribed); subscriber.on_punsubscribe(handle_unsubscribed); // 4. 订阅频道(支持通配符) subscriber.subscribe("news"); subscriber.psubscribe("sports*"); // 匹配sports开头的所有频道 // 5. 在独立线程中运行订阅循环 std::thread subscribe_thread([&subscriber]() { try { subscriber.run(); // 阻塞方法 } catch (const Error &e) { std::cerr << "订阅异常: " << e.what() << std::endl; } }); // 6. 发布消息示例 redis.publish("news", "Breaking: Redis 7.0 released!"); redis.publish("sports_football", "Goal! Team A scores!"); // 7. 保持主线程运行 std::this_thread::sleep_for(std::chrono::seconds(3)); // 8. 取消订阅 subscriber.unsubscribe(); subscriber.punsubscribe(); subscribe_thread.join(); return 0; } ``` ### 二、关键功能说明 1. **通配符订阅**:通过`psubscribe`方法使用`*`匹配多个频道,如`user.*`匹配`user.events`和`user.updates` 2. **线程管理**:订阅循环必须在独立线程运行,`subscriber.run()`会阻塞当前线程 3. **同步/异步发布**: - 同步发布:`redis.publish(channel, message)` - 异步发布:`redis.command<void>("publish", channel, message)`[^2] ### 三、高级用法示例(带消息队列) ```cpp #include <queue> #include <mutex> std::queue<std::string> msg_queue; std::mutex queue_mutex; // 自定义消息处理线程 void message_processor() { while (true) { std::lock_guard<std::mutex> lock(queue_mutex); while (!msg_queue.empty()) { auto msg = msg_queue.front(); msg_queue.pop(); // 处理消息 std::cout << "处理消息: " << msg << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } // 修改回调函数 void async_handle_message(const std::string& channel, const std::string& msg) { std::lock_guard<std::mutex> lock(queue_mutex); msg_queue.push("[" + channel + "]: " + msg); } // 主函数中启动处理线程 std::thread processor_thread(message_processor); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值