<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          阿里一面:如何保障消息100%投遞成功、消息冪等性?

          共 3226字,需瀏覽 7分鐘

           ·

          2021-03-20 18:08

          來(lái)源:toutiao.com/i6672235084336071179

          前言

          我們小伙伴應(yīng)該都聽(tīng)說(shuō)夠消息中間件MQ,如:RabbitMQ,RocketMQ,Kafka等。引入中間件的好處可以起到抗高并發(fā),削峰,業(yè)務(wù)解耦的作用。
          如上圖:
          (1)訂單服務(wù)投遞消息給MQ中間件 (2)物流服務(wù)監(jiān)聽(tīng)MQ中間件消息,從而進(jìn)行消費(fèi)
          我們這篇文章討論一下,如何保障訂單服務(wù)把消息成功投遞給MQ中間件,以RabbitMQ舉例。

          分析問(wèn)題

          小伙伴們對(duì)此會(huì)有些疑問(wèn),訂單服務(wù)發(fā)起消息服務(wù),返回成功不就成功了嗎?如下面的偽代碼:
          上面代碼中,一般發(fā)送消息就是這么寫(xiě)的,小伙伴們覺(jué)得有什么問(wèn)題嗎?
          下邊說(shuō)一個(gè)場(chǎng)景,如果MQ服務(wù)器突然宕機(jī)了會(huì)出現(xiàn)什么情況?是不是我們訂單服務(wù)發(fā)過(guò)去的消息全部沒(méi)有了嗎?是的,一般MQ中間件為了提高系統(tǒng)的吞吐量會(huì)把消息保存在內(nèi)存中,如果不作其他處理,MQ服務(wù)器一旦宕機(jī),消息將全部丟失。這個(gè)是業(yè)務(wù)不允許的,造成很大的影響,關(guān)注公眾號(hào)碼猿技術(shù)專(zhuān)欄,獲取更多面試資源。

          持久化

          有經(jīng)驗(yàn)的小伙伴會(huì)說(shuō),我知道一個(gè)方法就是把消息持久化,RabbitMQ中發(fā)消息的時(shí)候會(huì)有個(gè)durable參數(shù)可以設(shè)置,設(shè)置為true,就會(huì)持久化。
          這樣的話MQ服務(wù)器即使宕機(jī),重啟后磁盤(pán)文件中有消息的存儲(chǔ),這樣就不會(huì)丟失了吧。是的這樣就一定概率的保障了消息不丟失。
          但還會(huì)有個(gè)場(chǎng)景,就是消息剛剛保存到MQ內(nèi)存中,但還沒(méi)有來(lái)得及更新到磁盤(pán)文件中,突然宕機(jī)了。(我靠,這個(gè)時(shí)間這么短,也會(huì)出現(xiàn),概率太低了吧),這個(gè)場(chǎng)景在持續(xù)的大量消息投遞的過(guò)程中,會(huì)很常見(jiàn),關(guān)注公眾號(hào)碼猿技術(shù)專(zhuān)欄,獲取更多面試資源
          那怎么辦?我們?nèi)绾巫霾拍鼙U弦欢〞?huì)持久化到磁盤(pán)上面呢?

          confirm機(jī)制

          上面問(wèn)題出現(xiàn)在,沒(méi)有人告訴我們持久化是否成功。好在很多MQ有回調(diào)通知的特性,RabbitMQ就有confirm機(jī)制來(lái)通知我們是否持久化成功?
          confirm機(jī)制的原理:
          (1)消息生產(chǎn)者把消息發(fā)送給MQ,如果接收成功,MQ會(huì)返回一個(gè)ack消息給生產(chǎn)者;
          (2)如果消息接收不成功,MQ會(huì)返回一個(gè)nack消息給生產(chǎn)者;
          上面的偽代碼,有兩個(gè)處理消息方式,就是ack回調(diào)和nack回調(diào)。
          這樣是不是就可以保障100%消息不丟失了呢?
          我們看一下confirm的機(jī)制,試想一下,如果我們生產(chǎn)者每發(fā)一條消息,都要MQ持久化到磁盤(pán)中,然后再發(fā)起ack或nack的回調(diào)。這樣的話是不是我們MQ的吞吐量很不高,因?yàn)槊看味家严⒊志没酱疟P(pán)中。寫(xiě)入磁盤(pán)這個(gè)動(dòng)作是很慢的,關(guān)注公眾號(hào)碼猿技術(shù)專(zhuān)欄,獲取更多面試資源。這個(gè)在高并發(fā)場(chǎng)景下是不能夠接受的,吞吐量太低了。
          所以MQ持久化磁盤(pán)真實(shí)的實(shí)現(xiàn),是通過(guò)異步調(diào)用處理的,他是有一定的機(jī)制,如:等到有幾千條消息的時(shí)候,會(huì)一次性的刷盤(pán)到磁盤(pán)上面。而不是每來(lái)一條消息,就刷盤(pán)一次。
          所以comfirm機(jī)制其實(shí)是一個(gè)異步監(jiān)聽(tīng)的機(jī)制,是為了保證系統(tǒng)的高吞吐量,這樣就導(dǎo)致了還是不能夠100%保障消息不丟失,因?yàn)榧词辜由狭薱onfirm機(jī)制,消息在MQ內(nèi)存中還沒(méi)有刷盤(pán)到磁盤(pán)就宕機(jī)了,還是沒(méi)法處理。
          說(shuō)了這么多,還是沒(méi)法確保,那怎么辦呢???

          消息提前持久化 + 定時(shí)任務(wù)

          其實(shí)本質(zhì)的原因是無(wú)法確定是否持久化?那我們是不是可以自己讓消息持久化呢?答案是可以的,我們的方案再一步的演化。
          上圖流程:
          (1)訂單服務(wù)生產(chǎn)者投遞消息之前,先把消息持久化到Redis或DB中,建議Redis,高性能。消息的狀態(tài)為發(fā)送中。
          (2)confirm機(jī)制監(jiān)聽(tīng)消息是否發(fā)送成功?如ack成功消息,刪除Redis中此消息。
          (3)如果nack不成功的消息,這個(gè)可以根據(jù)自身的業(yè)務(wù)選擇是否重發(fā)此消息。也可以刪除此消息,由自己的業(yè)務(wù)決定。
          (4)這邊加了個(gè)定時(shí)任務(wù),來(lái)拉取隔一定時(shí)間了,消息狀態(tài)還是為發(fā)送中的,這個(gè)狀態(tài)就表明,訂單服務(wù)是沒(méi)有收到ack成功消息。
          (5)定時(shí)任務(wù)會(huì)作補(bǔ)償性的投遞消息。這個(gè)時(shí)候如果MQ回調(diào)ack成功接收了,再把Redis中此消息刪除。
          這樣的機(jī)制其實(shí)就是一個(gè)補(bǔ)償機(jī)制,我不管MQ有沒(méi)有真正的接收到,只要我的Redis中的消息狀態(tài)也是為【發(fā)送中】,就表示此消息沒(méi)有正確成功投遞。再啟動(dòng)定時(shí)任務(wù)去監(jiān)控,發(fā)起補(bǔ)償投遞。
          當(dāng)然定時(shí)任務(wù)那邊我們還可以加上一個(gè)補(bǔ)償?shù)拇螖?shù),如果大于3次,還是沒(méi)有收到ack消息,那就直接把消息的狀態(tài)設(shè)置為【失敗】,由人工去排查到底是為什么?
          這樣的話方案就比較完美了,保障了100%的消息不丟失(當(dāng)然不包含磁盤(pán)也壞了,可以做主從方案)。
          不過(guò)這樣的方案,就會(huì)有可能發(fā)送多次相同的消息,很有可能MQ已經(jīng)收到了消息,就是ack消息回調(diào)時(shí)出現(xiàn)網(wǎng)絡(luò)故障,沒(méi)有讓生產(chǎn)者收到。
          那就要要求消費(fèi)者一定在消費(fèi)的時(shí)候保障冪等性!

          冪等含義

          我們先了解一下什么叫冪等?在分布式應(yīng)用中,冪等是非常重要的,也就是相同條件下對(duì)一個(gè)業(yè)務(wù)的操作,不管操作多少次,結(jié)果都是一樣。

          為什么要有冪等這種場(chǎng)景?

          為什么要有冪等這種場(chǎng)景?因?yàn)樵诖蟮南到y(tǒng)中,都是分布式部署,如:訂單業(yè)務(wù) 和 庫(kù)存業(yè)務(wù)有可能都是獨(dú)立部署的,都是單獨(dú)的服務(wù)。用戶(hù)下訂單,會(huì)調(diào)用到訂單服務(wù)和庫(kù)存服務(wù)。
          因?yàn)榉植际讲渴穑苡锌赡茉谡{(diào)用庫(kù)存服務(wù)時(shí),因?yàn)榫W(wǎng)絡(luò)等原因,訂單服務(wù)調(diào)用失敗,但其實(shí)庫(kù)存服務(wù)已經(jīng)處理完成,只是返回給訂單服務(wù)處理結(jié)果時(shí)出現(xiàn)了異常。這個(gè)時(shí)候一般系統(tǒng)會(huì)作補(bǔ)償方案,也就是訂單服務(wù)再次放起庫(kù)存服務(wù)的調(diào)用,庫(kù)存減1。
          這樣就出現(xiàn)了問(wèn)題,其實(shí)上一次調(diào)用已經(jīng)減了1,只是訂單服務(wù)沒(méi)有收到處理結(jié)果?,F(xiàn)在又調(diào)用一次,又要減1,這樣就不符合業(yè)務(wù)了,多扣了。
          冪等這個(gè)概念就是,不管庫(kù)存服務(wù)在相同條件下調(diào)用幾次,處理結(jié)果都一樣。這樣才能保證補(bǔ)償方案的可行性。

          樂(lè)觀鎖方案

          借鑒數(shù)據(jù)庫(kù)的樂(lè)觀鎖機(jī)制,如:
          根據(jù)version版本,也就是在操作庫(kù)存前先獲取當(dāng)前商品的version版本號(hào),然后操作的時(shí)候帶上此version號(hào)。我們梳理下,我們第一次操作庫(kù)存時(shí),得到version為1,調(diào)用庫(kù)存服務(wù)version變成了2;但返回給訂單服務(wù)出現(xiàn)了問(wèn)題,訂單服務(wù)又一次發(fā)起調(diào)用庫(kù)存服務(wù),當(dāng)訂單服務(wù)傳遞的version還是1,再執(zhí)行上面的sql語(yǔ)句時(shí),就不會(huì)執(zhí)行;因?yàn)関ersion已經(jīng)變?yōu)?了,where條件就不成立。這樣就保證了不管調(diào)用幾次,只會(huì)真正的處理一次。

          唯一ID + 指紋碼

          原理就是利用數(shù)據(jù)庫(kù)主鍵去重,業(yè)務(wù)完成后插入主鍵標(biāo)識(shí)
          • 唯一ID就是業(yè)務(wù)表的唯一的主鍵,如商品ID

          • 指紋碼就是為了區(qū)別每次正常操作的碼,每次操作時(shí)生成指紋碼;可以用時(shí)間戳+業(yè)務(wù)編號(hào)的方式。

          上面的sql語(yǔ)句:
          • 返回如果為0 表示沒(méi)有操作過(guò),那業(yè)務(wù)操作后就可以insert into t_check(唯一ID+指紋碼)

          • 返回如果大于0 表示操作過(guò),就直接返回

          好處:實(shí)現(xiàn)簡(jiǎn)單
          壞處:高并發(fā)下數(shù)據(jù)庫(kù)瓶頸
          解決方案:根據(jù)ID進(jìn)行分庫(kù)分表進(jìn)行算法路由

          Redis原子操作

          利用redis的原子操作,做個(gè)操作完成的標(biāo)記。這個(gè)性能就比較好。但會(huì)遇到一些問(wèn)題。
          第一:我們是否需要把業(yè)務(wù)結(jié)果進(jìn)行數(shù)據(jù)落庫(kù),如果落庫(kù),關(guān)鍵解決的問(wèn)題時(shí)數(shù)據(jù)庫(kù)和redis操作如何做到原子性?
          這個(gè)意思就是庫(kù)存減1了,但redis進(jìn)行操作完成標(biāo)記時(shí),失敗了怎么辦?也就是一定要保證落庫(kù)和redis 要么一起成功,要么一起失敗
          第二:如果不進(jìn)行落庫(kù),那么都存儲(chǔ)到緩存中,如何設(shè)置定時(shí)同步策略?
          這個(gè)意思就是庫(kù)存減1,不落庫(kù),直接先操作redis操作完成標(biāo)記,然后由另外的同步服務(wù)進(jìn)行庫(kù)存落庫(kù),這個(gè)就是增加了系統(tǒng)復(fù)雜性,而且同步策略如何設(shè)置
          ——————END——————

          歡迎關(guān)注“Java引導(dǎo)者”,我們分享最有價(jià)值的Java的干貨文章,助力您成為有思想的Java開(kāi)發(fā)工程師!

          瀏覽 35
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美三级试看视频 | 亚洲视频在线观看网站 | 水蜜桃精品在线 | 操比网址| 欧美黄色一级A片 |