【由淺至深】redis 實現發佈訂閱的幾種方式

很是感謝依樂祝發表文章《.NET Core開發者的福音之玩轉Redis的又一傻瓜式神器推薦》,對csredis做了一次完整的詮釋。html

前言

提到消息隊列,最熟悉無疑是 rabbitmq,它基本是業界標準的解決方案。本文詳細介紹 redis 多種實現輕訂閱方法,做者認爲很是有趣並加以總結,但願對有須要的朋友學習 redis 功能有必定的帶入做用。git

方法一:SUBSCRIBE + PUBLISH

//程序1:使用代碼實現訂閱端
var sub = RedisHelper.Subscribe(("chan1", msg => Console.WriteLine(msg.Body)));
//sub.Disponse(); //中止訂閱

//程序2:使用代碼實現發佈端
RedisHelper.Publish("chan1", "111");

優點:支持多端訂閱、簡單、性能高;
缺點:數據會丟失;github

參考資料:http://doc.redisfans.com/pub_sub/subscribe.htmlredis

方法二:BLPOP + LPUSH(爭搶)

//程序1:使用代碼實現訂閱端
while (running) {
    try {
        var msg = RedisHelper.BLPop(5, "list1");
        if (string.IsNullOrEmpty(msg) == false) {
            Console.WriteLine(msg);
        }
    } catch (Exception ex) {
        Console.WriteLine(ex.Message);
    }
}

//程序2:使用代碼實現發佈端
RedisHelper.LPush("list1", "111");

優點:數據不會丟失、簡單、性能高;
缺點:不支持多端(存在資源爭搶);shell

總結:爲了解決方法一的痛點,咱們實現了本方法,而且很漂亮的製造了一個新問題(不支持多端訂閱)。工具

學習使用 BLPOP

BLPOP key [key ...] timeout性能

BLPOP 是列表的阻塞式(blocking)彈出原語。學習

它是 LPOP 命令的阻塞版本,當給定列表內沒有任何元素可供彈出的時候,鏈接將被 BLPOP 命令阻塞,直到等待超時或發現可彈出元素爲止。測試

當給定多個 key 參數時,按參數 key 的前後順序依次檢查各個列表,彈出第一個非空列表的頭元素。設計

非阻塞行爲

當 BLPOP 被調用時,若是給定 key 內至少有一個非空列表,那麼彈出遇到的第一個非空列表的頭元素,並和被彈出元素所屬的列表的名字一塊兒,組成結果返回給調用者。

當存在多個給定 key 時, BLPOP 按給定 key 參數排列的前後順序,依次檢查各個列表。

假設如今有 job 、 command 和 request 三個列表,其中 job 不存在, command 和 request 都持有非空列表。考慮如下命令:

BLPOP job command request 0

BLPOP 保證返回的元素來自 command ,由於它是按」查找 job -> 查找 command -> 查找 request 「這樣的順序,第一個找到的非空列表。

redis> DEL job command request           # 確保key都被刪除
(integer) 0

redis> LPUSH command "update system..."  # 爲command列表增長一個值
(integer) 1

redis> LPUSH request "visit page"        # 爲request列表增長一個值
(integer) 1

redis> BLPOP job command request 0       # job 列表爲空,被跳過,緊接着 command 列表的第一個元素被彈出。
1) "command"                             # 彈出元素所屬的列表
2) "update system..."                    # 彈出元素所屬的值

阻塞行爲

若是全部給定 key 都不存在或包含空列表,那麼 BLPOP 命令將阻塞鏈接,直到等待超時,或有另外一個客戶端對給定 key 的任意一個執行 LPUSH 或 RPUSH 命令爲止。

超時參數 timeout 接受一個以秒爲單位的數字做爲值。超時參數設爲 0 表示阻塞時間能夠無限期延長(block indefinitely) 。

redis> EXISTS job                # 確保兩個 key 都不存在
(integer) 0
redis> EXISTS command
(integer) 0

redis> BLPOP job command 300     # 由於key一開始不存在,因此操做會被阻塞,直到另外一客戶端對 job 或者 command 列表進行 PUSH 操做。
1) "job"                         # 這裏被 push 的是 job
2) "do my home work"             # 被彈出的值
(26.26s)                         # 等待的秒數

redis> BLPOP job command 5       # 等待超時的狀況
(nil)
(5.66s)                          # 等待的秒數

更多學習資料:http://doc.redisfans.com/list/blpop.html

方法三:BLPOP + LPUSH(非爭搶)

本方法根據方法二演變而來,設計圖以下:

如何實現三端訂閱,均可收到消息,三端分別爲 sub3, sub4, sub5:

一、sub3, sub4, sub5 使用【方法二】訂閱 listkey:list1_sub3,list1_sub4,list1_sub5;

二、總訂閱端訂閱 listkey:list1,總訂閱端收到消息後,執行 lpush list1_sub1 msg, lpush list1_sub2 msg, lpush list1_sub3 msg;

總訂閱端訂閱原始消息,隨後將消息分發給其餘訂閱端,從而解決【方法二】不支持多端同時訂閱的缺點。

最終實現的邏輯爲:多端先爭搶 list1 消息,搶到者再向其餘端轉發消息。

測試代碼

nuget Install-Package CSRedisCore

var rds = new CSRedis.CSRedisClient("127.0.0.1:6379,password=,poolsize=50,ssl=false,writeBuffer=10240");

//sub1, sub2 爭搶訂閱(只可一端收到消息)
var sub1 = rds.SubscribeList("list1", msg => Console.WriteLine($"sub1 -> list1 : {msg}"));
var sub2 = rds.SubscribeList("list1", msg => Console.WriteLine($"sub2 -> list1 : {msg}"));

//sub3, sub4, sub5 非爭搶訂閱(多端均可收到消息)
var sub3 = rds.SubscribeListBroadcast("list2", "sub3", msg => Console.WriteLine($"sub3 -> list2 : {msg}"));
var sub4 = rds.SubscribeListBroadcast("list2", "sub4", msg => Console.WriteLine($"sub4 -> list2 : {msg}"));
var sub5 = rds.SubscribeListBroadcast("list2", "sub5", msg => Console.WriteLine($"sub5 -> list2 : {msg}"));

//sub6 是redis自帶的普通訂閱
var sub6 = rds.Subscribe(("chan1", msg => Console.WriteLine(msg.Body)));

Console.ReadKey();
sub1.Dispose();
sub2.Dispose();
sub3.Dispose();
sub4.Dispose();
sub5.Dispose();
sub6.Dispose();

rds.Dispose();
return;

測試功能時,發佈端可使用 redis-cli 工具。

結語

redis 功能何其多且至關好玩有趣 ,你們應儘量多帶着興趣愛好去學習它。

若文中有很差的地方,請提出批評與改正方法,謝謝觀賞。

本文使用到 CSRedisCore 的開源地址:https://github.com/2881099/csredis

相關文章
相關標籤/搜索