Redis的Pub/Sub客戶端實現

丶Pz發表於2019-01-07

前言

  在學習T-io框架,從寫一個Redis客戶端開始一文中,已經簡單介紹了Redis客戶端的實現思路,並且基礎架構已經搭建完成,只不過支援的命令不全,不過後期在加命令就會很簡單了。本篇就要實現Publish/Subscribe功能。

Pub/Sub

  釋出訂閱模式在很多場景中用的都很頻繁,這裡不再贅述。下面看一下Redis中的命令。參考資料:https://redis.io/topics/pubsub

//釋出
PUBLISH
//訂閱
SUBSCRIBE
//模式匹配訂閱
PSUBSCRIBE
//取消訂閱
UNSUBSCRIBE
//取消訂閱(模式匹配)
PUNSUBSCRIBE
//其他

PUBLISH/SUBSCRIBE

  命令使用方式很簡單:

PUBLISH CHANNEL MESSAGE

例如:publish user helloworld

Client類中增加程式碼:

    @Override
    public void publish(final String channel, final String message) {
        sendCommand(Command.PUBLISH, channel, message);
    }

    @Override
    public void subscribe(final String... channels) {
        sendCommand(SUBSCRIBE, channels);
    }

除錯程式碼如下:

 //釋出
 Tedis tedisPublish =  new Tedis("192.168.1.225", 6379);
 tedisPublish.publish("channel1","hello world");
 //訂閱
 Tedis tedis = new Tedis("192.168.1.225", 6379);
 tedis.subscribe(new MyPubSub(),"channel1");

先訂閱,後釋出,訂閱響應結果:

*3
$9
subscribe
$8
channel1
:1

通過響應結果可以看出,我們當前的命令是 subscribe,然後訂閱的是channel1,當前共訂閱了:1個。

釋出響應結果:

:1

總共發給了:1個訂閱客戶端。這個結果就是訂閱客戶端的個數。

PSUBSCRIBE

命令格式如下:

PSUBSCIRBE  news.*

修改一下除錯程式碼:

訂閱

  tedis.pSubscribe(new MyPubSub(),"news.*");

響應結果:

*3
$10
psubscribe
$6
news.*
:1

釋出

  tedisPublish.publish("news.sports","welcome to NBA");
  tedisPublish.publish("news.country","this is china news");

訂閱客戶端收到訊息:

*4
$8
pmessage
$6
news.*
$11
news.sports
$14
welcome to NBA


*4
$8
pmessage
$6
news.*
$12
news.country
$18
this is china news

從響應結果可以看出,客戶端訂閱了 news.*,然後收到了news.sports,news.country的訊息。

響應訊息解析

  上述程式碼中有一個MyPubSub物件,它繼承自抽象類TedisPubSub.這個類做了釋出訂閱核心的業務處理。通過對服務端返回的訊息格式,我們可以發現,它的訊息格式是統一的。

    EVENT_NAME --事件

    CHANNEL_NAME --頻道
    
    OTHER --其他資訊,根據每個事件可能不同
    

所以我們在做釋出訂閱的響應訊息解析時,可以返回 List。這裡以SUBSCRIBE/PSUBSCRIBE舉例

 private boolean handleSubscribe(byte[] resp,List<Object> reply){
        //是否普通訂閱
        boolean isSubscribe = Arrays.equals(SUBSCRIBE.raw, resp);
        //是否模式匹配訂閱
        boolean isPSubscribe = Arrays.equals(Keyword.PSUBSCRIBE.raw, resp);

        if (isSubscribe || isPSubscribe) {
            resetSubscribedChannels(reply);
            //第二個值為 channel 名稱
            final byte[] channelBytes = (byte[]) reply.get(1);
            //轉化為 string
            final String channel = getString(channelBytes);

            //呼叫事件 (onSubscribe,onPSubscribe 子類可以重寫)
            if (isSubscribe) {
                onSubscribe(channel);
            } else {
                onPSubscribe(channel);
            }
            return true;
        }
        return false;
    }
    public abstract void onSubscribe(final String channel);
    public abstract void onPSubscribe(final String channelPatterns);
    

MyPubSub中重寫上述兩個方法。

    @Override
    public void onSubscribe(String channel) {
        System.out.println("訂閱了:"+channel);
    }

    @Override
    public void onPSubscribe(String channelPatterns) {
        System.out.println("訂閱了:"+channelPatterns);
    }

這樣,我們就能夠收到回撥訊息了。

訂閱了:news.*

接收到訊息同理:

   @Override
    public void onMessage(String channel, String message) {
        System.out.println(channel + " 收到了訊息:"+message);
    }
channel1 收到了訊息:welcome to NBA.

  不過這裡需要注意的一點是,在普通訂閱的訊息中只有【MESSAGE,CHANNEL,CONTENT】三個值,而模式匹配的訂閱訊息中,有【PMESSAGE,PATTERN,CHANNEL,CONTENT】四個值,其中就多了一個 PATTERN,也就是上文中的news.*,所以稍微做一下區分就可以了

 private boolean handleMessage(byte[] resp, List<Object> reply) {
        boolean isMessage = Arrays.equals(MESSAGE.raw, resp);
        boolean isPMessage = Arrays.equals(PMESSAGE.raw, resp);
        if (isMessage || isPMessage) {
            final byte[] secondBytes = (byte[]) reply.get(1);
            final byte[] thirdBytes = (byte[]) reply.get(2);
            final String second = getString(secondBytes);
            final String third = getString(thirdBytes);
            if (isMessage) {
                onMessage(second, third);
            } else {
                final byte[] messageBytes = (byte[]) reply.get(3);
                final String message = getString(messageBytes);
                onPMessage(second, third, message);
            }
            return true;
        }

呼叫示例

news.country(news.*)收到了訊息:this is china news

總結

  本文簡單的對RedisPub/Sub模式做了介紹,並且在客戶端中做了相應的處理。當然其中也是大量參考了Jedis原始碼。本文就到這裡啦,88
原始碼連結

相關文章