1. 程式人生 > >【由淺至深】redis 現實發布訂閱的幾種方式

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

前言

提到訊息佇列,最熟悉無疑是 rabbitmq,它基本是業界標準的解決方案。本文詳細介紹 redis 多種現實輕訂閱方法,作者認為非常有趣並加以總結,希望對有需要的朋友學習 redis 功能有一定的帶入作用。

方法一:SUBSCRIBE + PUBLISH

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

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

優勢:支援多端訂閱、簡單、效能高; 缺點:資料會丟失;

方法二: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");

優勢:資料不會丟失、簡單、效能高; 缺點:不支援多端(存在資源爭搶);

總結:為了解決方法一的痛點,我們現實了本方法,並且很漂亮的製造了一個新問題(不支援多端訂閱)。

學習使用 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)                          # 等待的秒數

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

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

如何現實三端訂閱,都可收到訊息,三端分別為 sub3, sub4, sub5:

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

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

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

測試程式碼

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 功能何其多且相當好玩有趣 ,大家應儘可能多帶著興趣愛好去學習它。

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