通俗易懂的redis發佈訂閱原理實現!

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"前言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可能小夥伴的工作年限大部分已經超過三年甚至四年五年,不知道是否有一種危機感,我們寫了那麼多的需求代碼沒有20w行也有個10w行了吧,但是出去找工作的時候不是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"筆試被pass","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"掉就是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"面試被pass","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":",你會發現好多你只是知道但是回答不上來。這個時候你才知道去補習知識點,其實這種做法對自身發展不太友好的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我去年疫情期間,在大家都不敢跳槽季節我義無反顧選擇跳槽,進入大家說的bat一線大廠。最近跟之前老東家的同事聊了聊技術棧、家常;說是面試了很多兩三年經驗開發者基礎太薄弱等等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我也從4月底跟隨之前的朋友一起開始了寫作之路,我基本上是以面對對象是小白講解方式開展自己的寫作模式,期間也有小夥伴讓我寫高級點的😭 😭 😭 ,但是確實不敢在那麼大佬面前造次;還是堅持從0到1的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"redis","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"講解之路,希望能得到小夥伴的支持。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/14d38f0c2894a835feb4598a1","title":"","type":null},"content":[{"type":"text","text":"我終於弄清楚了redis數據結構之string應用場景","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/64a487d0d84c9b9c2c25dc5ff","title":"","type":null},"content":[{"type":"text","text":"面試系列-2 redis列表場景分析實踐","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/c9f53febb066706789ad4801f","title":"","type":null},"content":[{"type":"text","text":"面試系列-3 限流場景實踐","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/87ddc90a76a3759da3ba46de3","title":"","type":null},"content":[{"type":"text","text":"面試系列-4 hash應用場景分析實踐","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/9b0827c018443828d2f0ebedf","title":null,"type":null},"content":[{"type":"text","text":"面試官嘲笑我,這你都不會?","attrs":{}}],"marks":[{"type":"strong"}]}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大部分是根據自己以往的面試和同事交流得出來的,有不對的地方還希望小夥伴們多多指正。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼開啓我們新的一輪面試知識點之旅.....","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"正文","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"今天要聊的知識點是redis的訂閱發佈功能,雖然說現在大廠都使用了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"kafka","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"RabbitMQ","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ActiveMQ","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":", ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"RocketMQ","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":";這幾種我大概用了三種,其實實現原理和內部使用方式都大同小異。爲什麼講redis的呢?因爲輕量、直接使用,而上面幾種適合大數據量,對數據準確性要求高的場景,作爲第三方組件,在小公司考慮到成本人力是不是太有好的,存在更多風險。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"爲什麼要用發佈訂閱","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實理論上我們之前的列表場景使用雙端鏈表就可以實現發佈與訂閱功能,但是這種通過鏈表來實現的發佈與訂閱功能有兩個侷限性:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1、基於鏈表實現的消息隊列,不能支持一對多的消息分發。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2、假如生產者生成的速率遠遠大於消費者消費消息的速率,可能會導致未消費消息佔用大量的內存(需要開啓足夠多的消費進程)。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我畫兩張圖進行對比,小夥伴們一眼就能看出來區別:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/56/560ca9c4e6d8669f0c546ef9b4452a46.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#595959","name":"user"}}],"text":"普通消息隊列結構圖","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/45/459a3f0903fd32bb12eeb7a999724123.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#595959","name":"user"}}],"text":"PubSub結構圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#595959","name":"user"}}],"text":"從上面的圖中可以看出普通消息隊列:只能有一個多個消費者去消費,卻不能將消息分發給其他消費者;redis訂閱發佈:生產者生產完消息通過頻道分發消息給訂閱該頻道的消費者,這樣就可以較少隊列數據的積攢,導致內存暴增。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#595959","name":"user"}}],"text":"所以爲了解決這兩個侷限性,Redis當中選擇了通過其他命令來實現發佈與訂閱模式。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"redis訂閱發佈的基本命令","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"psubscribe指令: psubscribe pattern [pattern ...] 訂閱一個或多個符合給定模式的頻道;時間複雜度O(n),n是訂閱的模式的數量。\n\n注意點:每個模式以 * 作爲匹配符;例如 mumu*匹配所有以 mumu 開頭的頻道:mumu.juejin、mumu.zhihu、mumu.csdn\n\npublist指令:publish channel message 把信息message發送到指定的頻道channel;時間複雜度O(n+m),n是頻道channel的訂閱者數量,m則是使用模式訂閱(subscribed patterns)的客戶端的數量。\n\n注意點:結果集返回是接收到message的訂閱者數量,沒有訂閱者返回0。\n\npubsub指令:pubsub channels [argument [argument ...]] 查看訂閱與發佈系統狀態;時間複雜度O(n),n爲活躍頻道的數量(對於長度較短的頻道和模式來說,將進行模式匹配的複雜度視爲常數)。\n\n注意:列出當前的活躍頻道(指的是那些至少有一個訂閱者的頻道, 訂閱模式的客戶端不計算在內),返回一個活躍頻道組成的列表。\n\npunsubscribe指令:punsubscribe [pattern [pattern ...]] 退訂所有給定模式的頻道;時間複雜度O(n+m),其中n是客戶端已訂閱的模式的數量, m則是系統中所有客戶端訂閱的模式的數量。\n\n注意:pattern未指定那麼訂閱的所有模式都會被退訂;否則只會退訂指定的訂閱的模式\n\nsubscribe指令:subscribe channel [channel ...] 訂閱給定的一個或多個頻道的信息;時間複雜度O(n),其中n是訂閱的頻道的數量。\n\nunsubscribe指令:unsubscribe channel [channel ...] 指退訂給定的頻道;時間複雜度O(n),其中n是訂閱的頻道的數量。\n\n注意:若沒有指定退訂channel,則默認退訂所有頻道;否則退訂指定頻道。BSCRIBE 命令訂閱的所有頻道都會被退訂。在這種情況下,命令會返回一個信息,告知客戶端所有被退訂的頻道。\n\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼在Redis中的發佈與訂閱也分爲兩種類型,一種是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"基於頻道","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"來實現,一種是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"基於模式","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"來實現。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"基於頻道實現講解","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"subscribe channe1 channel2 channel3 ... :訂閱一個或者多個頻道","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"unsubscribe channe1 channel2 channel3 ... :退訂訂閱的指定頻道(關閉客戶端終端沒用,需要命令退訂)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"publish channe1 message:對指定頻道發送消息","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"pubsub numsub channel1 channel2:查看指定頻道的訂閱數","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"好記性不如爛筆頭,光看不練假把戲:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"127.0.0.1:6379> SUBSCRIBE mumu_1 mumu_2\nReading messages... (press Ctrl-C to quit)\n1) \"subscribe\"    -- 返回值的類型:顯示訂閱成功\n2) \"mumu_1\"       -- 訂閱的頻道名字\n3) (integer) 1    -- 目前已訂閱的頻道數量\n\n1) \"subscribe\"\n2) \"mumu_2\"\n3) (integer) 2\n\n1) \"message\"      -- 返回值的類型:信息\n2) \"mumu_1\"       -- 來源(從那個頻道發送過來)\n3) \"\\xe6\\x88\\x91\\xe6\\x98\\xaf\\xe9\\x98\\xbf\\xe6\\xb2\\x90\\xe5\\x95\\x8a\" -- 消息內容\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b2/b2d43c3f21e613ea5ad4d121ec426074.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#595959","name":"user"}}],"text":"訂閱頻道發消息截圖","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"//獲取指定頻道的訂閱的客戶端數量\n127.0.0.1:6379> PUBSUB numsub mumu_1 mumu_2\n1) \"mumu_1\"    -- 頻道名稱\n2) (integer) 1 -- 訂閱該頻道的客戶端數量\n3) \"mumu_2\"\n4) (integer) 1\n127.0.0.1:6379> pubsub channels\n1) \"mumu_2\"  -- 頻道名稱\n2) \"mumu_1\"  -- 頻道名稱\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#595959","name":"user"}}],"text":"查看訂閱數頻道信息截圖","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"127.0.0.1:6379> UNSUBSCRIBE mumu_1\n1) \"unsubscribe\"  -- 返回值的類型:顯示取消訂閱成功\n2) \"mumu_1\"       -- 取消訂閱的頻道名字\n3) (integer) 0\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"我們看下基於頻道的實現原理:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"源碼路徑","attrs":{}},{"type":"text","text":":","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"redis-5.0.7/src/server.h","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"我把redis源碼下載到本地查看了;","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"大約1239行","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"struct redisServer {\n    /* General */\n    pid_t pid;   \n   \n    //省略百十行\n    \n    // 百度翻譯😜 😜 😜之後意思是: 將頻道映射到已訂閱客戶端的列表(就是保存客戶端和訂閱的頻道信息)\n    dict *pubsub_channels;  /* Map channels to list of subscribed clients */\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"pubsub_channels","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"定義的屬性是一個字典類型,保存着客戶端和頻道信息,","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"key值保存的就是頻道名","attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"value是一個鏈表","attrs":{}},{"type":"text","text":",鏈表中保存的是","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"客戶端id","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/76/76fdd0356ac1d3b296c86019539d0ff8.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#595959","name":"user"}}],"text":"訂閱頻道內部存儲結構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"頻道訂閱","attrs":{}},{"type":"text","text":":訂閱頻道時先檢查字段內部是否存在;不存在則爲當前頻道創建一個字典且創建一個鏈表存儲客戶端id;否則直接將客戶端id插入到鏈表中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"取消頻道訂閱","attrs":{}},{"type":"text","text":":取消時將客戶端id從對應的鏈表中刪除;如果刪除之後鏈表已經是空鏈表了,則將會把這個頻道從字典中刪除。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"基於模式實現講解","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"psubscribe pattern1 pattern2 pattern3 ... :訂閱一個或多個符合給定模式的頻道,每個模式以 * 作爲匹配符","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"punsubscribe pattern1 pattern2 pattern3 ... :取消模式的訂閱(關閉客戶端終端沒用,需要命令退訂)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"pubsub numpat pattern1 返回訂閱模式的數量,返回的不是訂閱模式的客戶端的數量,而是客戶端訂閱的所有模式的數量總和。時間複雜度O(1),(具體爲啥,請看下面原來解析結構)","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"說起時那時快,趕緊動手來實踐,眼見爲實:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"127.0.0.1:6379> PSUBSCRIBE mumu.*\nReading messages... (press Ctrl-C to quit)\n1) \"psubscribe\"       -- 返回值的類型:顯示訂閱成功\n2) \"mumu.*\"           -- 訂閱的模式\n3) (integer) 1        -- 目前已訂閱的模式的數量\n\n1) \"pmessage\"         -- 返回值的類型:信息\n2) \"mumu.*\"           -- 信息匹配的模式\n3) \"mumu.list\"        -- 信息本身的目標頻道\n4) \"i am a mumu\"      -- 信息的內容\n\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/97/973360f53d7f2ae25712d0bc01f73cab.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#595959","name":"user"}}],"text":"模式訂閱實踐操作圖","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"127.0.0.1:6379> PUNSUBSCRIBE mumu.*\n1) \"punsubscribe\"  -- 返回值的類型:顯示退訂成功\n2) \"mumu.*\"        -- 退訂的模式\n3) (integer) 1     -- 目前已退訂的模式的數量\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"我們看下基於模式的實現原理:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"源碼路徑","attrs":{}},{"type":"text","text":":","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"redis-5.0.7/src/server.h","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"我把redis源碼下載到本地查看了;","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"大約1240行","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"struct redisServer {\n    /* General */\n    pid_t pid;   \n   \n    //省略百十行\n    \n    // 百度翻譯😄 😄 😄之後意思是:pubsub訂閱的列表信息(大致就是存儲訂閱模式的信息)\n    list *pubsub_patterns;  /* A list of pubsub_patterns */\n}\n\n// 1303行訂閱模式列表結構:\ntypedef struct pubsubPattern {\n    client *client;  -- 訂閱模式客戶端\n    robj *pattern;   -- 被訂閱的模式\n} pubsubPattern;\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5d/5d2a49bd4e1228ad7de624e77a31a196.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#595959","name":"user"}}],"text":"模式訂閱內部結構圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"模式訂閱","attrs":{}},{"type":"text","text":":新增一個","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pubsub_pattern","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"數據結構添加到鏈表的最後尾部,同時保存","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"客戶端ID","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"取消模式訂閱","attrs":{}},{"type":"text","text":":從當前的鏈表","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pubsub_patterns","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"結構中刪除需要取消的模式訂閱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"從上面的一些實際實踐結果和結合圖形是不是對redis發佈訂閱進一步瞭解了呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"那麼我們使用redis發佈訂閱能做什麼?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"發佈訂閱(pub/sub)可以這麼理解:訂閱者(listener)負責訂閱頻道(channel);發送者(publisher)負責向頻道發送二進制的字符串消息,然後頻道收到消息時,推送給訂閱者。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"電商中,用戶下單成功之後向指定頻道發送消息,下游業務訂閱支付結果這個頻道處理自己相關業務邏輯","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"粉絲關注功能","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"文章推送","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"等等等等","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"實踐編碼","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"消費者訂閱Subscribe.php","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"php"},"content":[{"type":"text","text":"pconnect('127.0.0.1', 6379);\n    \n    //echo \"Server is running: \" . $redis->ping();\n    //阻塞獲取消息\n    while (true) {\n        // 阻塞獲取消息 $redis redis的實例  $channel_name 頻道名稱  $msg 生產者生成的消息體\n        $redis->subscribe($channel_names, function ($redis, $channel_name, $msg) {\n              switch ($channel_name) {\n                case 'mumu_test1':\n                    echo \"channel:\".$channel_name.\",message:\".$msg.\"\\n\";\n                    break;\n                case 'mumu_test2':\n\n                    break;\n                case 'mumu_test3':\n\n                    break;\n            }\n            if (!$msg) { //當沒有收到消息時 就休眠1s鍾\n                echo \"channel:\".$channel_name.\",message: not appoint channel name\".\"\\n\";\n                sleep(1);\n            }\n        });\n        // 本地測試 運行超過10分鐘 則自動結束 並關閉redis鏈接\n        if (time() - $cur_time > 10*60){\n            $redis -> close();\n            break;\n        }\n    }\n} catch (Exception $e) {\n    echo $e->getMessage();\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"生產者發送消息Publish.php","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"php"},"content":[{"type":"text","text":"connect('127.0.0.1', 6379);\n\n    for ($i = 0; $i  'key' . ($i+1), 'msg' => 'I am li a mu !');\n\n        $ret = $redis->publish($channel_name, json_encode($data));\n\n        print_r($ret);\n    }\n} catch (Exception $e) {\n    echo $e->getMessage();\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"🐧 執行結果集","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"終端執行消費者訂閱,開始阻塞獲取消息/usr/local/opt/[email protected]/bin/php Subscribe.php;結果集:\n\n➜  publish-subscribe git:(master) ✗ /usr/local/opt/[email protected]/bin/php Subscribe.php\nchannel:mumu_test1,message:{\"key\":\"key1\",\"msg\":\"I am li a mu !\"}\nchannel:mumu_test1,message:{\"key\":\"key2\",\"msg\":\"I am li a mu !\"}\nchannel:mumu_test1,message:{\"key\":\"key3\",\"msg\":\"I am li a mu !\"}\nchannel:mumu_test1,message:{\"key\":\"key4\",\"msg\":\"I am li a mu !\"}\nchannel:mumu_test1,message:{\"key\":\"key5\",\"msg\":\"I am li a mu !\"}\nchannel:mumu_test1,message:{\"key\":\"key6\",\"msg\":\"I am li a mu !\"}\nchannel:mumu_test1,message:{\"key\":\"key7\",\"msg\":\"I am li a mu !\"}\nchannel:mumu_test1,message:{\"key\":\"key8\",\"msg\":\"I am li a mu !\"}\nchannel:mumu_test1,message:{\"key\":\"key9\",\"msg\":\"I am li a mu !\"}\nchannel:mumu_test1,message:{\"key\":\"key10\",\"msg\":\"I am li a mu !\"}\n\n上面就是生產者生成的消息內容:msg字符串\n\n終端執行生產者生產數據,開始發送消息/usr/local/opt/[email protected]/bin/php Publish.php;結果集:\n➜  publish-subscribe git:(master) ✗ /usr/local/opt/[email protected]/bin/php Publish.php\n1111111111\n\n// 這種場景是 我模擬發送了沒有創建的頻道 mumu_test4 導致返回的結果集都是0 說明沒有被訂閱的channel,消息會被丟棄\n➜  publish-subscribe git:(master) ✗ /usr/local/opt/[email protected]/bin/php Publish.php\n0000000000\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"🐮 注意事項","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、訂閱的消費者需要一直執行,阻塞獲取消息,如果斷開則表示退訂了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、channel只接收publish發送的消息,自身是不存儲消息,假如channel沒有被訂閱,則消息會被丟棄掉。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、生產者生成消息時,只需要向頻道內丟入消息即可。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"🙈 當然還有這些命令可以玩耍","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"php"},"content":[{"type":"text","text":"$redis->pubsub('channels'); // 獲取所有頻道\n$redis->pubsub('channels', '*pattern*'); // 僅僅獲取指定頻道\n$redis->pubsub('numsub', ['channel1', 'channel2']); // 查看指定頻道的訂閱數\n$redis->pubsub('numpat'); // 返回訂閱模式的數量\n$redis->unsubscribe(['channel1', 'channel2']); // 客戶端退訂指定的頻道\n$redis->punsubscribe(['pattern1', 'pattern2']); // 客戶端退訂所有指定定模式\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"小夥伴們本地實踐操作起來~~~,千看不如寫一遍。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"redis發佈訂閱的優缺點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小夥伴們從上面的實踐操作來看,PubSub生產的消息,如果沒有對應的頻道或者消費者,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"消息會被丟棄","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":",直接投遞失敗","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"返回0狀態","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"。假如我們實際生產環境在消費的時候,突然網絡波動,導致其中一個消費者掛掉了一段時間,那麼當它重新連接上的時候,中間這一段時間產生的消息也將不會存在。也就是說Redis本身是不會存儲消息體信息的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼在我們生產環境數量不大且想節約成本的時候,redis的發佈訂閱功能可能比較適合我們公司;輕量級、方便使用配合","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"consul+supervisor+swool","attrs":{}}],"marks":[{"type":"color","attrs":{"color":"#9654B5","name":"user"}}],"attrs":{}},{"type":"text","text":"可以常駐內存,開多進程消費(消息隊列也可以用的)。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}}],"text":"🐣 總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"哇哇哇,能有幸看到這裏的小夥伴,我很服氣你們了,我花了兩天的時間去想去畫去構思寫好的文章;你們竟然也看到了這裏,阿沐心裏賊開心;阿沐確實很佩服小夥伴們,賊棒👍 、賊有毅力;同時也賊能包容阿沐的不足之處。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文主要通過整理PubSub的實際操作指令,然後結合底層的源碼分析它們之間的存儲結構;再通過實際的客戶端操作,來說明返回參數的具體意思;最最最後通過實踐寫代碼運行展示。同時也列出PubSub的優缺點,幫助大家在實際的工作中可以有更好的選擇。","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"最後好記性不如多親自動手實踐,唯有實踐,才知其本質。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,歡迎關注我的個人公衆號「我是阿沐」,會不定期的更新後端知識點和學習筆記。也歡迎直接公衆號私信或者郵箱聯繫我,我們可以一起學習,一起進步。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好了,我是阿沐,一個不想30歲就被淘汰的打工人 ⛽️ ⛽️ ⛽️ 。創作不易覺得「","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#773098","name":"user"}},{"type":"strong","attrs":{}}],"text":"阿沐","attrs":{}},{"type":"text","text":"」寫的有點料話:👍 關注一下,💖 分享一下,我們下期再見。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章