rabbitmq延時(shí)隊(duì)列(實(shí)現(xiàn)定時(shí)任務(wù))
場(chǎng)景
比如未付款訂單青柄,超過(guò)一定時(shí)間后长搀,系統(tǒng)自動(dòng)取消訂單并釋放占有物品绿淋。
常用解決方案
spring的schedule定時(shí)任務(wù)輪詢數(shù)據(jù)庫(kù)
缺點(diǎn)
消耗系統(tǒng)內(nèi)存石窑、增加了數(shù)據(jù)庫(kù)的壓力炫欺、存在較大的時(shí)間誤差
解決
rabbitmq的消息TTL和死信Exchange結(jié)合
消息的TTL(Time To Live)
- 消息的TTL就是消息的存活時(shí)間
- RabbitMQ可以對(duì)隊(duì)列和消息分別設(shè)置TTL。
- 對(duì)隊(duì)列設(shè)置就是隊(duì)列沒(méi)有消費(fèi)者連著的保留時(shí)間揉抵,也可以對(duì)每一個(gè)單獨(dú)的消息做單獨(dú)的設(shè)置亡容。超過(guò)了這個(gè)時(shí)間,我們認(rèn)為這個(gè)消息就死了功舀,稱之為死信萍倡。
- 如果隊(duì)列設(shè)置了,消息也設(shè)置了辟汰,那么會(huì)取小的列敲。所以一個(gè)消息如果被路由到不同的隊(duì)列中,這個(gè)消息死亡的時(shí)間有可能不一樣(不同隊(duì)列設(shè)置)帖汞。這里單講單個(gè)消息的TTL戴而,因?yàn)樗攀菍?shí)現(xiàn)延遲任務(wù)的關(guān)鍵◆嬲海可以通過(guò)設(shè)置消息的expiration字段或者x-message-ttl屬性來(lái)設(shè)置時(shí)間所意,兩者是一樣的效果。
Dead Letter Exchanges(DLX)
- 一個(gè)消息在滿足如下條件下催首,會(huì)進(jìn)死信路由隔披,記住這里是路由而不是隊(duì)列,一個(gè)路由可以對(duì)應(yīng)很多隊(duì)列叙甸。(什么是死信)
- 一個(gè)消息被Consumer拒收了番官,并且reject方法的參數(shù)里requeue是false。也就是說(shuō)不會(huì)被再次放在隊(duì)列里舶治,被其他消費(fèi)者使用分井。(basic.reject/ basic.nack) requeue=false
- 上面的消息的TTL到了,消息過(guò)期了霉猛。
- 隊(duì)列的長(zhǎng)度限制滿了尺锚。排在前面的消息會(huì)被丟棄或者扔到死信路由上
- Dead Letter Exchange其實(shí)就是一種普通的exchange,和創(chuàng)建其他exchange沒(méi)有兩樣惜浅。只是在某一個(gè)設(shè)置Dead Letter Exchange的隊(duì)列中有消息過(guò)期了瘫辩,會(huì)自動(dòng)觸發(fā)消息的轉(zhuǎn)發(fā),發(fā)送到Dead Letter Exchange中去坛悉。
- 我們既可以控制消息在一段時(shí)間后變成死信杭朱,又可以控制變成死信的消息被路由到某一個(gè)指定的交換機(jī),結(jié)合二者吹散,其實(shí)就可以實(shí)現(xiàn)一個(gè)延時(shí)隊(duì)列
延時(shí)隊(duì)列實(shí)現(xiàn)的二種方式
消息隊(duì)列流程(示例)
柔性事物--可靠消息+最終一致性方案(異步確保型)
實(shí)現(xiàn):業(yè)務(wù)處理服務(wù)在業(yè)務(wù)事務(wù)提交之前弧械,向?qū)崟r(shí)消息服務(wù)請(qǐng)求發(fā)送消息,實(shí)時(shí)消息服務(wù)只記錄消息數(shù)據(jù)空民,而不是真正的發(fā)送刃唐。業(yè)務(wù)處理服務(wù)在業(yè)務(wù)事務(wù)提交之后羞迷,向?qū)崟r(shí)消息服務(wù)確認(rèn)發(fā)送。只有在得到發(fā)送指令后画饥,實(shí)時(shí)消息服務(wù)才會(huì)真正發(fā)送衔瓮。
如何保證消息的可靠性
消息丟失
- 消息發(fā)送出去,由于網(wǎng)絡(luò)問(wèn)題沒(méi)有抵達(dá)服務(wù)器
- 做好容錯(cuò)方法(try-catch)抖甘,發(fā)送消息可能會(huì)網(wǎng)絡(luò)失敗热鞍,失敗后要有重試機(jī)制,可記錄到數(shù)據(jù)庫(kù)衔彻,采用定期掃描重發(fā)的方式
- 做好日志記錄薇宠,每個(gè)消息狀態(tài)是否都被服務(wù)器收到都應(yīng)該記錄
- 做好定期重發(fā),如果消息沒(méi)有發(fā)送成功艰额,定期去數(shù)據(jù)庫(kù)掃描未成功的消息進(jìn)行重發(fā)
- 消息抵達(dá)Broker澄港,Broker要將消息寫入磁盤(持久化)才算成功。此時(shí)Broker尚未持久化完成柄沮,宕機(jī)回梧。
- publish也必須加入確認(rèn)回調(diào)機(jī)制,確認(rèn)成功的消息祖搓,修改數(shù)據(jù)庫(kù)消息狀態(tài)狱意。
- 自動(dòng)ACK的狀態(tài)下。消費(fèi)者收到消息拯欧,但沒(méi)有來(lái)得及消費(fèi)然后宕機(jī)髓涯。
- 一定開啟手動(dòng)ACK,消費(fèi)成功才移除哈扮,失敗或者沒(méi)有來(lái)得及處理就noACK并重新入隊(duì)
Rabbit消息ACK配置
待完善
消息重復(fù)
- 消息消費(fèi)成功,事務(wù)已經(jīng)提交蚓再,ACK時(shí)滑肉,機(jī)器宕機(jī)。導(dǎo)致沒(méi)有ACK成功摘仅,Broker的消息重新由unack變?yōu)閞eady靶庙,并發(fā)送給其他消費(fèi)者
- 消息消費(fèi)失敗,由于重試機(jī)制娃属,自動(dòng)又將消息發(fā)送出去
- 成功消費(fèi)六荒,ACK時(shí)宕機(jī),消息由uncheck變?yōu)閞eady矾端,Broker又重新發(fā)送
- 消費(fèi)者的業(yè)務(wù)消費(fèi)接口應(yīng)該設(shè)計(jì)為冪等性的掏击。比如扣庫(kù)存有工作單的狀態(tài)標(biāo)志
- 使用防重表(redis/mysql),發(fā)送消息每一個(gè)都有業(yè)務(wù)的唯一標(biāo)識(shí)秩铆,處理過(guò)就不用處理
- rabbitMQ的每一個(gè)消息都有redelivered字段砚亭,可以獲取是否是被重新投遞過(guò)來(lái)的灯变,而不是第一次投遞過(guò)來(lái)的
消息積壓
- 消費(fèi)者宕機(jī)積壓
- 消費(fèi)者消費(fèi)能力不足積壓
- 發(fā)送者發(fā)送流量太大
- 上線更多的消費(fèi)者,進(jìn)行正常消費(fèi)
- 上線專門的隊(duì)列消費(fèi)服務(wù)捅膘,將消息先批量取出來(lái)添祸,記錄數(shù)據(jù)庫(kù),離線慢慢處理