Redis 發佈訂閱,小功能大用處,真沒那麼廢材!

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8e/8e6e4f54c2d2a78a606ce1eefb948be3.jpeg","alt":"jae-park-7GX5aICb5i4-unsplash","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"今天小黑哥來跟大家介紹一下 Redis 發佈/訂閱功能。"}]},{"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":"也許有的小夥伴對這個功能比較陌生,不太清楚這個功能是幹什麼的,沒關係小黑哥先來舉個例子。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fe/fe0b63e7bb5bae453d454d143d8cda10.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"假設我們有這麼一個業務場景,在網站下單支付以後,需要通知庫存服務進行發貨處理。"}]},{"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":"上面業務實現不難,我們只要讓庫存服務提供給相關的給口,下單支付之後只要調用庫存服務即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/58/58a2054b89e457171beff19af4dad03d.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"後面如果又有新的業務,比如說積分服務,他需要獲取下單支付的結果,然後增加用戶的積分。"}]},{"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":"這個實現也不難,讓積分服務同樣提供一個接口,下單支付之後只要調用庫存服務即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a9/a9d10bb8f077e54353365c1d33c48d3d.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"如果就兩個業務需要獲取下單支付的結果,那也還好,程序改造也快。可是隨着業務不斷的發展,越來越多的新業務說是要下單支付的結果。"}]},{"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":"這時我們會發現上面這樣的系統架構存在很多問題:"}]},{"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":"第一,下單支付業務與其他業務重度耦合,每當有個新業務需要支付結果,就需要改動下單支付的業務。"}]},{"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":"第二,如果調用業務過多,會導致下單支付接口響應時間變長。另外,如果有任一下游接口響應變慢,就會同步導致下單支付接口響應也變長。"}]},{"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":"第三,如果任一下游接口失敗,可能導致數據不一致的情況。比如說下圖,先調用 A,成功之後再調用 B,最後再調用 C。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b5/b5e99bfcff7478954d0b8e0aadd0733a.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"如果在調用 B 接口的發生異常,此時可能就導致下單支付接口返回失敗,但是此時 A 接口其實已經調用成功,這就代表它內部已經處理下單支付成功的結果。"}]},{"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":"這樣就會導致 A,B,C 三個下游接口,A 獲取成功獲取支付結果,但是 B,C 沒有拿到,導致三者系統數據不一致的情況。"}]},{"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":"其實我們仔細想一下,對於下單支付業務來講,它其實不需要關心下游調用結果,只要有某種機制通知能通知到他們就可以了。"}]},{"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":"講到這裏,這就需要引入今天需要介紹發佈訂閱機制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Redis 發佈與訂閱"}]},{"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":"Redis 提供了基於「發佈/訂閱」模式的消息機制,在這種模式下,消息發佈者與訂閱者不需要進行直接通信。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/df/df1f4ce7a9b32b256167bd034f56ab05.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"如上圖所示,消息發佈者只需要想指定的頻道發佈消息,訂閱該頻道的每個客戶端都可以接受到到這個消息。"}]},{"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":"使用 Redis 發佈訂閱這種機制,對於上面業務,下單支付業務只需要向"},{"type":"text","marks":[{"type":"strong"}],"text":"支付結果"},{"type":"text","text":"這個頻道發送消息,其他下游業務訂閱"},{"type":"text","marks":[{"type":"strong"}],"text":"支付結果"},{"type":"text","text":"這個頻道,就能收相應消息,然後做出業務處理即可。"}]},{"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":"這樣就可以解耦系統上下游之間調用關係。"}]},{"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":"接下來我們來看下,我們來看下如何使用 Redis 發佈訂閱功能。"}]},{"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":"Redis 中提供了一組命令,可以用於發佈消息,訂閱頻道,取消訂閱以及按照模式訂閱。"}]},{"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":"首先我們來看下如何發佈一條消息,其實很簡單隻要使用 "},{"type":"text","marks":[{"type":"strong"}],"text":"publish"},{"type":"text","text":" 指令:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"publish channel message"}]},{"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}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/20/203db897656d4d6cddff7c13bfe5fa2f.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"上圖中,我們使用 "},{"type":"text","marks":[{"type":"strong"}],"text":"publish"},{"type":"text","text":" 指令向 "},{"type":"text","marks":[{"type":"strong"}],"text":"pay_result"},{"type":"text","text":" 這個頻道發送了一條消息。我們可以看到 redis 向我們返回 0 ,這其實代表當前訂閱者個數,由於此時沒有訂閱,所以返回結果爲 0 。"}]},{"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":"接下來我們使用 "},{"type":"text","marks":[{"type":"strong"}],"text":"subscribe"},{"type":"text","text":" 訂閱一個或多個頻道"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"subscribe channel [channel ...]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2e/2efd360daea8068eb1e00bd2bee90d36.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"如上圖所示,我們訂閱 "},{"type":"text","marks":[{"type":"strong"}],"text":"pay_result"},{"type":"text","text":" 這個頻道,當有其他客戶端往這個頻道發送消息,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/49/49fbc2da13daf150e3217344087aac94.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"當前訂閱者就會收到消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1d/1d6304b76d8f5976b9bfc47ec49beff6.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"我們子在使用訂閱命令,需要主要幾點:"}]},{"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":"第一,客戶端執行訂閱指令之後,就會進入訂閱狀態,之後就只能接收 "},{"type":"text","marks":[{"type":"strong"}],"text":"subscribe"},{"type":"text","text":"、*"},{"type":"text","marks":[{"type":"italic"}],"text":"psubscribe"},{"type":"text","text":"*、"},{"type":"text","marks":[{"type":"strong"}],"text":"unsubscribe"},{"type":"text","text":"、*"},{"type":"text","marks":[{"type":"italic"}],"text":"punsubscribe"},{"type":"text","text":"* 這四個命令。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/82/8253e0f20e9738539afc856bce8374ca.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"第二,新訂閱的客戶端,是"},{"type":"text","marks":[{"type":"strong"}],"text":"無法收到這個頻道之前的消息"},{"type":"text","text":",這是因爲 Redis 並不會對發佈的消息持久化的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 相比於很多專業 MQ,比如 kafka、rocketmq 來說, redis 發佈訂閱功能就顯得有點簡陋了。不過 redis 發佈訂閱功能勝在簡單,如果當前場景可以容忍這些缺點,還是可以選擇使用的。"}]}]},{"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":"除了上面的功能以外的,Redis 還支持模式匹配的訂閱方式。簡單來說,客戶端可以訂閱一個帶 "},{"type":"codeinline","content":[{"type":"text","text":"*"}]},{"type":"text","text":" 號的模式,如果某些頻道的名字與這個模式匹配,那麼當其他客戶端發送給消息給這些頻道時,訂閱這個模式的客戶端也將會到收到消息。"}]},{"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":"使用 Redis 訂閱模式,我們需要使用一個新的指令 "},{"type":"text","marks":[{"type":"strong"}],"text":"psubscribe"},{"type":"text","text":"。 "}]},{"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":"我們執行下面這個指令:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"psubscribe pay.*"}]},{"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":"那麼一旦有其他客戶端往 "},{"type":"text","marks":[{"type":"strong"}],"text":"pay"},{"type":"text","text":" 開頭的頻道,比如 "},{"type":"codeinline","content":[{"type":"text","text":"pay_result"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"pay_xxx"}]},{"type":"text","text":",我們都可以收到消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5d/5d392feb5496245f9db46c2b08dac3f7.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"如果需要取消訂閱模式,我們需要使用相應"},{"type":"codeinline","content":[{"type":"text","text":"punsubscribe"}]},{"type":"text","text":" 指令,比如取消上面訂閱的模式: "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"punsubscribe pay.*"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":" Redis 客戶端發佈訂閱使用方式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"基於 Jedis 開發發佈/訂閱"}]},{"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":"聊完 Redis 發佈訂閱指令,我們來看下 Java Redis 客戶端如何使用發佈訂閱。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面的例子主要基於 Jedis,maven 版本爲:"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"```xml"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" redis.clients"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" jedis"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 3.1.0"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"```"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其他 Redis 客戶端大同小異。"}]}]},{"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":"jedis 發佈代碼比較簡單,只需要調用 "},{"type":"codeinline","content":[{"type":"text","text":"Jedis"}]},{"type":"text","text":" 類的 "},{"type":"codeinline","content":[{"type":"text","text":"publish"}]},{"type":"text","text":" 方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 生產環境千萬不要這麼使用哦,推薦使用 JedisPool 線程池的方式 \nJedis jedis = new Jedis(\"localhost\", 6379);\njedis.auth(\"xxxxx\");\njedis.publish(\"pay_result\", \"hello world\");"}]},{"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":"訂閱的代碼就相對複雜了,我們需要繼承 "},{"type":"codeinline","content":[{"type":"text","text":"JedisPubSub "}]},{"type":"text","text":"實現裏面的相關方法,一旦有其他客戶端往訂閱的頻道上發送消息,將會調用 "},{"type":"codeinline","content":[{"type":"text","text":"JedisPubSub "}]},{"type":"text","text":" 相應的方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private static class MyListener extends JedisPubSub {\n @Override\n public void onMessage(String channel, String message) {\n System.out.println(\"收到訂閱頻道:\" + channel + \" 消息:\" + message);\n\n }\n\n @Override\n public void onPMessage(String pattern, String channel, String message) {\n System.out.println(\"收到具體訂閱頻道:\" + channel + \"訂閱模式:\" + pattern + \" 消息:\" + message);\n }\n\n}"}]},{"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":"其次我們需要調用 "},{"type":"codeinline","content":[{"type":"text","text":"Jedis"}]},{"type":"text","text":" 類的 "},{"type":"codeinline","content":[{"type":"text","text":"subscribe"}]},{"type":"text","text":" 方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Jedis jedis = new Jedis(\"localhost\", 6379);\njedis.auth(\"xxx\");\njedis.subscribe(new MyListener(), \"pay_result\");"}]},{"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":"當有其他客戶端往 "},{"type":"codeinline","content":[{"type":"text","text":"pay_result"}]},{"type":"text","text":"頻道發送消息時,訂閱將會收到消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ba/ba693f595981d212fc9366545ca3237a.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"不過需要注意的是,"},{"type":"codeinline","content":[{"type":"text","text":"jedis#subscribe"}]},{"type":"text","text":" 是一個阻塞方法,調用之後將會阻塞主線程的,所以如果需要在正式項目使用需要使用異步線程運行,這裏就不演示具體的代碼了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"基於 Spring-Data-Redis 開發發佈訂閱"}]},{"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":"原生 jedis 發佈訂閱操作,相對來說還是有點複雜。現在我們很多應用已經基於 SpringBoot 開發,使用 "},{"type":"codeinline","content":[{"type":"text","text":"spring-boot-starter-data-redis"}]},{"type":"text","text":" ,可以簡化發佈訂閱開發。"}]},{"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":"首先我們需要引入相應的 startter 依賴:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"xml"},"content":[{"type":"text","text":"\n org.springframework.boot\n spring-boot-starter-data-redis\n \n \n lettuce-core\n io.lettuce\n \n \n\n\n redis.clients\n jedis\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏我們使用 Jedis 當做底層連接客戶端,所以需要排除 lettuce,然後引入 Jedis 依賴。"}]}]},{"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":"然後我們需要創建一個消息接收類,裏面需要有方法消費消息:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Slf4j\npublic class Receiver {\n private AtomicInteger counter = new AtomicInteger();\n\n public void receiveMessage(String message) {\n log.info(\"Received \");\n counter.incrementAndGet();\n }\n\n public int getCount() {\n return counter.get();\n }\n}"}]},{"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":"接着我們只需要注入 Spring- Redis 相關 Bean,比如:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"StringRedisTemplate"}]},{"type":"text","text":",用來操作 Redis 命令"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"MessageListenerAdapter"}]},{"type":"text","text":" ,消息監聽器,可以在這個類注入我們上面創建消息接受類"},{"type":"codeinline","content":[{"type":"text","text":" Receiver"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"RedisConnectionFactory"}]},{"type":"text","text":", 創建 Redis 底層連接"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Configuration\npublic class MessageConfiguration {\n\n @Bean\n RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,\n MessageListenerAdapter listenerAdapter) {\n\n RedisMessageListenerContainer container = new RedisMessageListenerContainer();\n container.setConnectionFactory(connectionFactory);\n // 訂閱指定頻道使用 ChannelTopic\n // 訂閱模式使用 PatternTopic\n container.addMessageListener(listenerAdapter, new ChannelTopic(\"pay_result\"));\n\n return container;\n }\n\n @Bean\n MessageListenerAdapter listenerAdapter(Receiver receiver) {\n // 注入 Receiver,指定類中的接受方法\n return new MessageListenerAdapter(receiver, \"receiveMessage\");\n }\n\n @Bean\n Receiver receiver() {\n return new Receiver();\n }\n\n @Bean\n StringRedisTemplate template(RedisConnectionFactory connectionFactory) {\n return new StringRedisTemplate(connectionFactory);\n }\n\n}"}]},{"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":"最後我們使用 "},{"type":"codeinline","content":[{"type":"text","text":"StringRedisTemplate#convertAndSend"}]},{"type":"text","text":" 發送消息,同時 "},{"type":"codeinline","content":[{"type":"text","text":"Receiver"}]},{"type":"text","text":" 將會收到一條消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@SpringBootApplication\npublic class MessagingRedisApplication {\n public static void main(String[] args) throws InterruptedException {\n\n ApplicationContext ctx = SpringApplication.run(MessagingRedisApplication.class, args);\n\n StringRedisTemplate template = ctx.getBean(StringRedisTemplate.class);\n Receiver receiver = ctx.getBean(Receiver.class);\n\n while (receiver.getCount() == 0) {\n template.convertAndSend(\"pay_result\", \"Hello from Redis!\");\n Thread.sleep(500L);\n }\n\n System.exit(0);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/95/95f2ba92dc183871167985bdf6c5c7ab.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":" Redis 發佈訂閱實際應用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Redis Sentinel 節點發現"}]},{"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":"strong"}],"text":"Redis Sentinel"},{"type":"text","text":" 是 Redis 一套高可用方案,可以在主節點故障的時候,自動將從節點提升爲主節點,從而轉移故障。"}]},{"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":"今天這裏我們不詳細解釋 "},{"type":"text","marks":[{"type":"strong"}],"text":"Redis Sentinel"},{"type":"text","text":" 詳細原理,主要來看下 "},{"type":"text","marks":[{"type":"strong"}],"text":"Redis Sentinel"},{"type":"text","text":" 如何使用發佈訂閱機制。"}]},{"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":" "},{"type":"text","marks":[{"type":"strong"}],"text":"Redis Sentinel"},{"type":"text","text":" 節點主要使用發佈訂閱機制,實現新節點的發現,以及交換主節點的之間的狀態。"}]},{"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":"如下所示,每一個 "},{"type":"text","marks":[{"type":"strong"}],"text":"Sentinel"},{"type":"text","text":" 節點將會定時向 "},{"type":"codeinline","content":[{"type":"text","text":"_sentinel_:hello"}]},{"type":"text","text":" 頻道發送消息,並且每個 "},{"type":"text","marks":[{"type":"strong"}],"text":"Sentinel"},{"type":"text","text":" 都會訂閱這個節點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6c/6c494313fdb37ac6304ab1b4fb754760.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"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":"這樣一旦有節點往這個頻道發送消息,其他節點就可以立刻收到消息。"}]},{"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":"這樣一旦有的新節點加入,它往這個頻道發送消息,其他節點收到之後,判斷本地列表並沒有這個節點,於是就可以當做新的節點加入本地節點列表。"}]},{"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":"除此之外,每次往這個頻道發送消息內容可以包含節點的狀態信息,這樣可以作爲後面 "},{"type":"text","marks":[{"type":"strong"}],"text":"Sentinel"},{"type":"text","text":" 領導者選舉的依據。"}]},{"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":"以上都是對於 Redis 服務端來講,對於客戶端來講,我們也可以用到發佈訂閱機制。"}]},{"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":"當 "},{"type":"text","marks":[{"type":"strong"}],"text":"Redis Sentinel"},{"type":"text","text":" 進行主節點故障轉移,這個過程各個階段會通過發佈訂閱對外提供。"}]},{"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":"對於我們客戶端來講,比較關心切換之後的主節點,這樣我們及時切換主節點的連接(舊節點此時已故障,不能再接受操作指令),"}]},{"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":"客戶端可以訂閱 "},{"type":"codeinline","content":[{"type":"text","text":"+switch-master"}]},{"type":"text","text":"頻道,一旦 "},{"type":"text","marks":[{"type":"strong"}],"text":"Redis Sentinel"},{"type":"text","text":" 結束了對主節點的故障轉移就會發布主節點的的消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"redission 分佈式鎖"}]},{"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":"redission 開源框架提供一些便捷操作 Redis 的方法,其中比較出名的 redission 基於 Redis 的實現分佈式鎖。"}]},{"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":"今天我們來看下 Redis 的實現分佈式鎖中如何使用 Redis 發佈訂閱機制,提高加鎖的性能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PS:redission 分佈式鎖實現原理,可以參考之前寫過的文章:"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. "},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/3sJ0TfYG3tXLPwBa2AAJBg","title":""},"content":[{"type":"text","text":"可重入分佈式鎖的實現方式"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. "},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/HlD46m-OP-HDdKJFxgqFYA","title":""},"content":[{"type":"text","text":"Redis 分佈式鎖,看似簡單,其實真不簡單"}]}]}]},{"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":"首先我們來看下 redission 加鎖的方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Redisson redisson = ....\nRLock redissonLock = redisson.getLock(\"xxxx\");\nredissonLock.lock();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"RLock"}]},{"type":"text","text":" 繼承自 Java 標準的 "},{"type":"codeinline","content":[{"type":"text","text":"Lock"}]},{"type":"text","text":" 接口,調用 "},{"type":"codeinline","content":[{"type":"text","text":"lock"}]},{"type":"text","text":" 方法,如果當前鎖已被其他客戶端獲取,那麼當前加鎖的線程將會被阻塞,直到其他客戶端釋放這把鎖。"}]},{"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":"這裏其實有個問題,當前阻塞的線程如何感知分佈式鎖已被釋放呢?"}]},{"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":"這裏其實有兩種實現方法:"}]},{"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":"第一鍾,定時查詢分佈時鎖的狀態,一旦查到鎖已被釋放("},{"type":"text","marks":[{"type":"italic"}],"text":"Redis 中不存在這個鍵值"},{"type":"text","text":"),那麼就去加鎖。"}]},{"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":"實現僞碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"while (true) {\n boolean result=lock();\n if (!result) {\n Thread.sleep(N);\n }\n}"}]},{"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":"這種方式實現起來起來簡單,不過缺點也比較多。"}]},{"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":"如果定時任務時間過短,將會導致查詢次數過多,其實這些都是無效查詢。"}]},{"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":"如果定時任務休眠時間過長,那又會導致加鎖時間過長,導致加鎖性能不好。"}]},{"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":"那麼第二種實現方案,就是採用服務通知的機制,當分佈式鎖被釋放之後,客戶端可以收到鎖釋放的消息,然後第一時間再去加鎖。"}]},{"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":"這個服務通知的機制我們可以使用 Redis 發佈訂閱模式。"}]},{"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":"當線程加鎖失敗之後,線程將會訂閱 "},{"type":"codeinline","content":[{"type":"text","text":"redisson_lock__channel_xxx"}]},{"type":"text","text":"(xx 代表鎖的名稱) 頻道,使用異步線程監聽消息,然後利用 Java 中 "},{"type":"codeinline","content":[{"type":"text","text":"Semaphore"}]},{"type":"text","text":" 使當前線程進入阻塞。"}]},{"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":"一旦其他客戶端進行解鎖,redission 就會往這個"},{"type":"codeinline","content":[{"type":"text","text":"redisson_lock__channel_xxx"}]},{"type":"text","text":" 發送解鎖消息。"}]},{"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":"等異步線程收到消息,將會調用 "},{"type":"codeinline","content":[{"type":"text","text":"Semaphore"}]},{"type":"text","text":" 釋放信號量,從而讓當前被阻塞的線程喚醒去加鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ps:這裏只是簡單描述了 redission 加鎖部分原理,出於篇幅,這裏就不再消息解析源碼。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"感興趣的小夥伴可以自己看下 redission 加鎖的源碼。"}]}]},{"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":"通過發佈訂閱機制,被阻塞的線程可以及時被喚醒,減少無效的空轉的查詢,有效的提高的加鎖的效率。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ps: 這種方式,性能確實提高,但是實現起來的複雜度也很高,這部分源碼有點東西,快看暈了。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"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":"今天我們主要介紹 Redis 發佈訂閱功能,主要對應的 Redis 命令爲:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"subscribe channel [channel ...]"},{"type":"text","text":" 訂閱一個或多個頻道"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"unsubscribe channel"},{"type":"text","text":" 退訂指定頻道"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"publish channel message"},{"type":"text","text":" 發送消息"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"psubscribe pattern"},{"type":"text","text":" 訂閱指定模式"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"punsubscribe pattern"},{"type":"text","text":" 退訂指定模式"}]}]}]},{"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":"我們可以利用 Redis 發佈訂閱功能,實現的簡單 MQ 功能,實現上下游的解耦。"}]},{"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":"不過需要注意了,由於 Redis 發佈的消息不會被持久化,這就會導致新訂閱的客戶端將不會收到歷史消息。"}]},{"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":"所以,如果當前的業務場景不能容忍這些缺點,那還是用專業 MQ 吧。"}]},{"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":"最後介紹了兩個使用 Redis 發佈訂閱功能使用場景供大家參考。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎關注我的公衆號:程序通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:"},{"type":"link","attrs":{"href":"https://studyidea.cn","title":""},"content":[{"type":"text","text":"studyidea.cn"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ba/ba163d45a6678a4749fb956e3464a1c6.gif","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章