redis的sub/pub

发布订阅(pub/sub)是一种消息通信模式,主要的目的是解耦消息发布者和消息订阅者之间的耦合,这点和设计模式中的观察者模式比较相似。pub/sub 不仅仅解决发布者和订阅者直接代码级别耦合也解决两者在物理部署上的耦合。redis 作为一个 pub/sub 的 server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过 subscribe 和 psubscribe 命令向 redis server 订阅自己感兴趣的消息类型,redis 将消息类型称为通道(channel)。当发布者通过publish 命令向 redis server 发送特定类型的消息时。订阅该消息类型的全部 client 都会收到此消息。这里消息的传递是多对多的。一个client可以订阅多个channel,也可以多个channel发送消息

如果我一个在server1的用户想要给在server2的用户发送消息要怎么办呢?

是像下面这样把每个服务器连接起来么?

基于Redis的聊天 redis 聊天室_redis

所以,我们可以借鉴交换机连接PC的思想,引入Redis消息队列中间件

基于Redis的聊天 redis 聊天室_java_02


当客户端登录的时候,服务器吧它的id号 subscribe到redis中间件,表示该服务器对这个id发生的事件感兴趣,而Redis收到发送给该id的消息时就会 把消息转发到这个服务器上。

遗留问题:redis实现sub/pub的原理

#ifndef REDIS_H
#define REDIS_H

#include <hiredis/hiredis.h>
#include <thread>
#include <functional>

using namespace std;
using redis_handler = function<void(int,string)>;

class Redis
{
public:
    Redis();
    ~Redis();

    //连接Redis服务器
    bool connect();

    //向Redis指定的通道channel发布消息
    bool publish(int channel, string message);

    //向Redis指定的通道subscribe订阅消息
    bool subscribe(int channel);

    //取消订阅
    bool unsubscribe(int channel);

    //独立线程中接收订阅通道的消息
    void observer_channel_message();

    //初始化业务层上报通道消息的回调对象
    void init_notify_handler(redis_handler handler);

private:
    //hiredis同步上下文对象,负责publish消息
    redisContext *publish_context_;

    //负责subscribe消息
    redisContext *subcribe_context_;

    //回调操作,收到消息给service上报
    redis_handler notify_message_handler_;
};

#endif



#include "Redis.hpp"
#include <iostream>

Redis::Redis() : publish_context_(nullptr), subcribe_context_(nullptr)
{
}

Redis::~Redis()
{
    if (publish_context_ != nullptr)
    {
        redisFree(publish_context_);
    }

    if (subcribe_context_ != nullptr)
    {
        redisFree(subcribe_context_);
    }
}

//连接Redis服务器
bool Redis::connect()
{
    publish_context_ = redisConnect("127.0.0.1", 6379);
    if (publish_context_ == nullptr)
    {
        cerr << "connect redis failed!" << endl;
        return false;
    }

    subcribe_context_ = redisConnect("127.0.0.1", 6379);
    if (subcribe_context_ == nullptr)
    {
        cerr << "connect redis failed!" << endl;
        return false;
    }

    thread t([&]() {
        observer_channel_message();
    });
    t.detach();

    cout << "connect redis-server success!" << endl;
    return true;
}

//向Redis指定的通道channel发布消息
bool Redis::publish(int channel, string message)
{
    //相当于publish 键 值
    redisReply *reply = (redisReply *)redisCommand(publish_context_, "PUBLISH %d %s", channel, message.c_str());
    if (reply == nullptr)
    {
        cerr << "publish command failed!" << endl;
        return false;
    }

    freeReplyObject(reply);
    return true;
}

//向Redis指定的通道subscribe订阅消息
bool Redis::subscribe(int channel)
{
    //redisCommand 会先把命令缓存到context中,然后调用RedisAppendCommand发送给redis
    //redis执行subscribe是阻塞,不会响应,不会给我们一个reply
    if (REDIS_ERR == redisAppendCommand(subcribe_context_, "SUBSCRIBE %d", channel))
    {
        cerr << "subscibe command failed" << endl;
        return false;
    }

    int done = 0;
    while (!done)
    {
        if (REDIS_ERR == redisBufferWrite(subcribe_context_, &done))
        {
            cerr << "subscribe command failed" << endl;
            return false;
        }
    }

    return true;
}

//取消订阅
bool Redis::unsubscribe(int channel)
{
    //redisCommand 会先把命令缓存到context中,然后调用RedisAppendCommand发送给redis
    //redis执行subscribe是阻塞,不会响应,不会给我们一个reply
    if (REDIS_ERR == redisAppendCommand(subcribe_context_, "UNSUBSCRIBE %d", channel))
    {
        cerr << "subscibe command failed" << endl;
        return false;
    }

    int done = 0;
    while (!done)
    {
        if (REDIS_ERR == redisBufferWrite(subcribe_context_, &done))
        {
            cerr << "subscribe command failed" << endl;
            return false;
        }
    }

    return true;
}

//独立线程中接收订阅通道的消息
void Redis::observer_channel_message()
{
    redisReply *reply = nullptr;
    while (REDIS_OK == redisGetReply(subcribe_context_, (void **)&reply))
    {
        //reply里面是返回的数据有三个,0. message , 1.通道号,2.消息
        if (reply != nullptr && reply->element[2] != nullptr && reply->element[2]->str != nullptr)
        {
            //给业务层上报消息
            notify_message_handler_(atoi(reply->element[1]->str), reply->element[2]->str);
        }

        freeReplyObject(reply);
    }

    cerr << "----------------------- oberver_channel_message quit--------------------------" << endl;
}

//初始化业务层上报通道消息的回调对象
void Redis::init_notify_handler(redis_handler handler)
{
    notify_message_handler_ = handler;
}

redis常见API