redis stream java消息队列_Redis 异步消息队列与延时队列
消息中間件,大家都會(huì)想到 Rabbitmq 和 Kafka 作為消息隊(duì)列中間件,來(lái)給應(yīng)用程序之間增加異步消息傳遞功能。這兩個(gè)中間件都是專(zhuān)業(yè)的消息隊(duì)列中間件,特性之多超出了大多數(shù)人的理解能力。但是這種屬于重量級(jí)的應(yīng)用,使用比較麻煩點(diǎn)。如果是輕量級(jí)的,使用 Redis就可以。比如對(duì)于那些只有一組消費(fèi)者的消息隊(duì)列,使用 Redis 就可以非常輕松的搞定。Redis 的消息隊(duì)列不是專(zhuān)業(yè)的消息隊(duì)列,它沒(méi)有非常多的高級(jí)特性,沒(méi)有 ack 保證,如果對(duì)消息的可靠性沒(méi)有極致的要求,那么它可以拿來(lái)使用。
異步消息隊(duì)列
Redis 的 list(列表) 數(shù)據(jù)結(jié)構(gòu)常用來(lái)作為異步消息隊(duì)列使用,使用rpush/lpush操作入隊(duì)列,使用lpop 和 rpop來(lái)出隊(duì)列。rpush 和 lpop 結(jié)合 或者lpush 和rpop 結(jié)合;
客戶(hù)端是通過(guò)隊(duì)列的 pop 操作來(lái)獲取消息,然后進(jìn)行處理。處理完了再接著獲取消息,再進(jìn)行處理。如此循環(huán)往復(fù),這便是作為隊(duì)列消費(fèi)者的客戶(hù)端的生命周期。
問(wèn)題來(lái)了
可是如果隊(duì)列空了,客戶(hù)端就會(huì)陷入 pop 的死循環(huán),不停地 pop,沒(méi)有數(shù)據(jù),接著再 pop,又沒(méi)有數(shù)據(jù)。這就是浪費(fèi)生命的空輪詢(xún)。空輪詢(xún)不但拉高了客戶(hù)端的 CPU,redis 的 QPS 也會(huì)被拉高,如果這樣空輪詢(xún)的客戶(hù)端有幾十來(lái)個(gè),Redis 的慢查詢(xún)可能會(huì)顯著增多。 通常我們使用 sleep 來(lái)解決這個(gè)問(wèn)題,讓線(xiàn)程睡一會(huì),睡個(gè) 1s 鐘就可以了。不但客戶(hù)端的 CPU 能降下來(lái),Redis 的 QPS 也降下來(lái)了。
新的問(wèn)題:
用上面睡眠的辦法可以解決問(wèn)題。但是有個(gè)小問(wèn)題,那就是睡眠會(huì)導(dǎo)致消息的延遲增大。如果只有 1 個(gè)消費(fèi)者,那么這個(gè)延遲就是 1s。如果有多個(gè)消費(fèi)者,這個(gè)延遲會(huì)有所下降,因?yàn)槊總€(gè)消費(fèi)者的睡覺(jué)時(shí)間是岔開(kāi)來(lái)的。 有沒(méi)有什么辦法能顯著降低延遲呢?你當(dāng)然可以很快想到:那就把睡覺(jué)的時(shí)間縮短點(diǎn)。這種方式當(dāng)然可以,不過(guò)有沒(méi)有更好的解決方案呢?當(dāng)然也有,那就是 blpop/brpop。 這兩個(gè)指令的前綴字符b代表的是blocking,也就是阻塞讀。 阻塞讀在隊(duì)列沒(méi)有數(shù)據(jù)的時(shí)候,會(huì)立即進(jìn)入休眠狀態(tài),一旦數(shù)據(jù)到來(lái),則立刻醒過(guò)來(lái)。消息的延遲幾乎為零。用blpop/brpop替代前面的lpop/rpop,就完美解決了上面的問(wèn)題。
問(wèn)題喋喋不休:
空閑連接自動(dòng)斷開(kāi) 你以為上面的方案真的很完美么?先別急著開(kāi)心,其實(shí)他還有個(gè)問(wèn)題需要解決。 什么問(wèn)題?—— 空閑連接的問(wèn)題。 如果線(xiàn)程一直阻塞在那里,Redis 的客戶(hù)端連接就成了閑置連接,閑置過(guò)久,服務(wù)器一般會(huì)主動(dòng)斷開(kāi)連接,減少閑置資源占用。這個(gè)時(shí)候blpop/brpop會(huì)拋出異常來(lái)。 所以編寫(xiě)客戶(hù)端消費(fèi)者的時(shí)候要小心,注意捕獲異常,還要重視。
消息延時(shí)隊(duì)列
延時(shí)隊(duì)列可以通過(guò) Redis 的 zset(有序列表) 來(lái)實(shí)現(xiàn)。我們將消息序列化成一個(gè)字符串作為 zset 的value,這個(gè)消息的到期處理時(shí)間作為score,然后用多個(gè)線(xiàn)程輪詢(xún) zset 獲取到期的任務(wù)進(jìn)行處理,多個(gè)線(xiàn)程是為了保障可用性,萬(wàn)一掛了一個(gè)線(xiàn)程還有其它線(xiàn)程可以繼續(xù)處理。因?yàn)橛卸鄠€(gè)線(xiàn)程,所以需要考慮并發(fā)爭(zhēng)搶任務(wù),確保任務(wù)不能被多次執(zhí)行。 Redis 的 zrem 方法是多線(xiàn)程多進(jìn)程爭(zhēng)搶任務(wù)的關(guān)鍵,它的返回值決定了當(dāng)前實(shí)例有沒(méi)有搶到任務(wù),因?yàn)?loop 方法可能會(huì)被多個(gè)線(xiàn)程、多個(gè)進(jìn)程調(diào)用,同一個(gè)任務(wù)可能會(huì)被多個(gè)進(jìn)程線(xiàn)程搶到,通過(guò) zrem 來(lái)決定唯一的屬主。 同時(shí),我們要注意一定要對(duì) handle_msg 進(jìn)行異常捕獲,避免因?yàn)閭€(gè)別任務(wù)處理問(wèn)題導(dǎo)致循環(huán)異常退出。
問(wèn)題來(lái)了:
同一個(gè)任務(wù)可能會(huì)被多個(gè)進(jìn)程取到之后再使用 zrem 進(jìn)行爭(zhēng)搶,那些沒(méi)搶到的進(jìn)程都是白取了一次任務(wù),這是浪費(fèi)。解決辦法:Lua是Redis內(nèi)置腳本,執(zhí)行Lua腳本時(shí),Redis線(xiàn)程會(huì)依次執(zhí)行腳本中的語(yǔ)句,對(duì)于客戶(hù)端來(lái)說(shuō)操作是原子性的,將 zrangebyscore 和 zrem 一同挪到服務(wù)器端進(jìn)行原子化操作,這樣多個(gè)進(jìn)程之間爭(zhēng)搶任務(wù)時(shí)就不會(huì)出現(xiàn)這種浪費(fèi)了。
總結(jié)
以上是生活随笔為你收集整理的redis stream java消息队列_Redis 异步消息队列与延时队列的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: RTX5 | 线程管理01 - 创建线程
- 下一篇: STM32F103+CubeMX-Kei