mq使用場景、不丟不重、時序性

 mq使用場景、不丟不重、時序性、削峯前端

 

參考:web

http://zhuanlan.51cto.com/art/201704/536407.htm數據庫

http://zhuanlan.51cto.com/art/201703/535090.htm後端

http://zhuanlan.51cto.com/art/201704/536306.htm數組

http://zhuanlan.51cto.com/art/201611/521602.htm緩存

http://zhuanlan.51cto.com/art/201611/521602.htm安全

http://zhuanlan.51cto.com/art/201703/534752.htm服務器

http://zhuanlan.51cto.com/art/201703/534475.htm微信

微信公衆號:架構師之路markdown

 

 

 

到底何時該使用MQ?

1、緣起

一切脫離業務的架構設計與新技術引入都是耍流氓。

 

引入一個技術以前,首先應該解答的問題是,這個技術解決什麼問題。

 

就像微服務分層架構以前,應該首先回答,爲何要引入微服務,微服務究竟解決什麼問題(詳見《互聯網架構爲何要作微服務?》)。

 

最近分享了幾篇MQ相關的文章:

MQ如何實現延時消息

MQ如何實現消息必達

MQ如何實現冪等性

很多網友詢問,究竟何時使用MQ,MQ究竟適合什麼場景,故有了此文。

 

2、MQ是幹嗎的

消息總線(Message Queue),後文稱MQ,是一種跨進程的通訊機制,用於上下游傳遞消息。

 

在互聯網架構中,MQ是一種很是常見的上下游「邏輯解耦+物理解耦」的消息通訊服務。

使用了MQ以後,消息發送上游只須要依賴MQ,邏輯上和物理上都不用依賴其餘服務。

 

3、何時不使用消息總線

既然MQ是互聯網分層架構中的解耦利器,那全部通信都使用MQ豈不是很好?這是一個嚴重的誤區,調用與被調用的關係,是沒法被MQ取代的。

 

MQ的不足是:

1)系統更復雜,多了一個MQ組件

2)消息傳遞路徑更長,延時會增長

3)消息可靠性和重複性互爲矛盾,消息不丟不重難以同時保證

4)上游沒法知道下游的執行結果,這一點是很致命的

 

舉個栗子:用戶登陸場景,登陸頁面調用passport服務,passport服務的執行結果直接影響登陸結果,此處的「登陸頁面」與「passport服務」就必須使用調用關係,而不能使用MQ通訊。

 

不管如何,記住這個結論調用方實時依賴執行結果的業務場景,請使用調用,而不是MQ

 

4、何時使用MQ

【典型場景一:數據驅動的任務依賴】

 什麼是任務依賴,舉個栗子,互聯網公司常常在凌晨進行一些數據統計任務,這些任務之間有必定的依賴關係,好比:

1)task3須要使用task2的輸出做爲輸入

2)task2須要使用task1的輸出做爲輸入

這樣的話,tast1, task2, task3之間就有任務依賴關係,必須task1先執行,再task2執行,載task3執行。

對於這類需求,常見的實現方式是,使用cron人工排執行時間表:

1)task1,0:00執行,經驗執行時間爲50分鐘

2)task2,1:00執行(爲task1預留10分鐘buffer),經驗執行時間也是50分鐘

3)task3,2:00執行(爲task2預留10分鐘buffer)

 

這種方法的壞處是:

1)若是有一個任務執行時間超過了預留buffer的時間,將會獲得錯誤的結果,由於後置任務不清楚前置任務是否執行成功,此時要手動重跑任務,還有可能要調整排班表

2)總任務的執行時間很長,老是要預留不少buffer,若是前置任務提早完成,後置任務不會提早開始

3)若是一個任務被多個任務依賴,這個任務將會稱爲關鍵路徑,排班表很難體現依賴關係,容易出錯

4)若是有一個任務的執行時間要調整,將會有多個任務的執行時間要調整

 

不管如何,採用「cron排班表」的方法,各任務耦合,誰用過誰痛誰知道(採用此法的請評論留言)

 

優化方案是,採用MQ解耦:

1)task1準時開始,結束後發一個「task1 done」的消息

2)task2訂閱「task1 done」的消息,收到消息後第一時間啓動執行,結束後發一個「task2 done」的消息

3)task3同理

 

採用MQ的優勢是:

1)不須要預留buffer,上游任務執行完,下游任務總會在第一時間被執行

2)依賴多個任務,被多個任務依賴都很好處理,只須要訂閱相關消息便可

3)有任務執行時間變化,下游任務都不須要調整執行時間

 

須要特別說明的是,MQ只用來傳遞上游任務執行完成的消息,並不用於傳遞真正的輸入輸出數據。

 

【典型場景二:上游不關心執行結果】

上游須要關注執行結果時要用「調用」,上游不關注執行結果時,就可使用MQ了。

 

舉個栗子,58同城的不少下游須要關注「用戶發佈帖子」這個事件,好比招聘用戶發佈帖子後,招聘業務要獎勵58豆,房產用戶發佈帖子後,房產業務要送2個置頂,二手用戶發佈帖子後,二手業務要修改用戶統計數據。

 

對於這類需求,常見的實現方式是,使用調用關係:

帖子發佈服務執行完成以後,調用下游招聘業務、房產業務、二手業務,來完成消息的通知,但事實上,這個通知是否正常正確的執行,帖子發佈服務根本不關注。

 

這種方法的壞處是:

1)帖子發佈流程的執行時間增長了

2)下游服務當機,可能致使帖子發佈服務受影響,上下游邏輯+物理依賴嚴重

3)每當增長一個須要知道「帖子發佈成功」信息的下游,修改代碼的是帖子發佈服務,這一點是最噁心的,屬於架構設計中典型的依賴倒轉,誰用過誰痛誰知道(採用此法的請評論留言)

 

優化方案是,採用MQ解耦:

1)帖子發佈成功後,向MQ發一個消息

2)哪一個下游關注「帖子發佈成功」的消息,主動去MQ訂閱

 

採用MQ的優勢是:

1)上游執行時間短

2)上下游邏輯+物理解耦,除了與MQ有物理鏈接,模塊之間都不相互依賴

3)新增一個下游消息關注方,上游不須要修改任何代碼

 

典型場景三:上游關注執行結果,但執行時間很長

 有時候上游須要關注執行結果,但執行結果時間很長(典型的是調用離線處理,或者跨公網調用),也常用回調網關+MQ來解耦。

 

舉個栗子,微信支付,跨公網調用微信的接口,執行時間會比較長,但調用方又很是關注執行結果,此時通常怎麼玩呢?

通常採用「回調網關+MQ」方案來解耦:

1)調用方直接跨公網調用微信接口

2)微信返回調用成功,此時並不表明返回成功

3)微信執行完成後,回調統一網關

4)網關將返回結果通知MQ

5)請求方收到結果通知

 

這裏須要注意的是,不該該由回調網關來調用上游來通知結果,若是是這樣的話,每次新增調用方,回調網關都須要修改代碼,仍然會反向依賴,使用回調網關+MQ的方案,新增任何對微信支付的調用,都不須要修改代碼啦。

 

5、總結

MQ是一個互聯網架構中常見的解耦利器。

 

何時不使用MQ?

上游實時關注執行結果

 

何時使用MQ?

1)數據驅動的任務依賴

2)上游不關心多下游執行結果

3)異步返回執行時間長

 

==【完】==

相關閱讀:

MQ如何實現延時消息

MQ如何實現消息必達

MQ如何實現冪等性

 

 

 

 

消息總線可否實現消息必達?

1、緣起

上週討論了兩期環形隊列的業務應用:

《高效定時任務的觸發》

《延遲消息的快速實現》

兩期的均有大量讀者提問:

  • 任務、延遲消息都放在內存裏,萬一重啓了怎麼辦?
  • 可否保證消息必達?

今天就簡單聊聊消息隊列(MsgQueue)的消息必達性架構與流程。

2、架構方向

MQ要想盡可能消息必達,架構上有兩個核心設計點:

(1)消息落地

(2)消息超時、重傳、確認

3、MQ核心架構

MQ的核心架構圖

上圖是一個MQ的核心架構圖,基本能夠分爲三大塊:

(1)發送方 -> 左側粉色部分

(2)MQ核心集羣 -> 中間藍色部分

(3)接收方 -> 右側黃色部分

粉色發送方又由兩部分構成:業務調用方與MQ-client-sender

其中後者向前者提供了兩個核心API:

  • SendMsg(bytes[] msg)
  • SendCallback()

藍色MQ核心集羣又分爲四個部分:MQ-server,zk,db,管理後臺web

黃色接收方也由兩部分構成:業務接收方與MQ-client-receiver

其中後者向前者提供了兩個核心API:

  • RecvCallback(bytes[] msg)
  • SendAck()

MQ是一個系統間解耦的利器,它可以很好的解除發佈訂閱者之間的耦合,它將上下游的消息投遞解耦成兩個部分,如上述架構圖中的1箭頭和2箭頭:

(1)發送方將消息投遞給MQ,上半場

(2)MQ將消息投遞給接收方,下半場

4、MQ消息可靠投遞核心流程

MQ既然將消息投遞拆成了上下半場,爲了保證消息的可靠投遞,上下半場都必須儘可能保證消息必達。

MQ消息可靠投遞核心流程

MQ消息投遞上半場,MQ-client-sender到MQ-server流程見上圖1-3:

  • MQ-client將消息發送給MQ-server(此時業務方調用的是API:SendMsg)
  • MQ-server將消息落地,落地後即爲發送成功
  • MQ-server將應答發送給MQ-client(此時回調業務方是API:SendCallback)

MQ消息投遞下半場,MQ-server到MQ-client-receiver流程見上圖4-6:

  • MQ-server將消息發送給MQ-client(此時回調業務方是API:RecvCallback)
  • MQ-client回覆應答給MQ-server(此時業務方主動調用API:SendAck)
  • MQ-server收到ack,將以前已經落地的消息刪除,完成消息的可靠投遞

1. 若是消息丟了怎麼辦?

MQ消息投遞的上下半場,均可以出現消息丟失,爲了下降消息丟失的機率,MQ須要進行超時和重傳。

2. 上半場的超時與重傳

MQ上半場的1或者2或者3若是丟失或者超時,MQ-client-sender內的timer會重發消息,直到指望收到3,若是重傳N次後還未收到,則SendCallback回調發送失敗,須要注意的是,這個過程當中MQ-server可能會收到同一條消息的屢次重發。

3. 下半場的超時與重傳

MQ下半場的4或者5或者6若是丟失或者超時,MQ-server內的timer會重發消息,直到收到5而且成功執行6,這個過程可能會重發不少次消息,通常採用指數退避的策略,先隔x秒重發,2x秒重發,4x秒重發,以此類推,須要注意的是,這個過程當中MQ-client-receiver也可能會收到同一條消息的屢次重發。

MQ-client與MQ-server如何進行消息去重,如何進行架構冪等性設計,下一次撰文另述,此處暫且認爲爲了保證消息必達,可能收到重複的消息。

5、總結

消息總線是系統之間的解耦利器,但切勿濫用,將來也會撰文細究MQ的使用場景,消息總線爲了儘可能保證消息必達,架構設計方向爲:

  • 消息收到先落地
  • 消息超時、重傳、確認保證消息必達

 

 

消息總線真的能保證冪等?

1、緣起

如《消息總線消息必達》所述,MQ消息必達,架構上有兩個核心設計點:

(1)消息落地

(2)消息超時、重傳、確認

消息總線核心架構

再次回顧消息總線核心架構,它由發送端、服務端、固化存儲、接收端四大部分組成。

爲保證消息的可達性,超時、重傳、確認機制可能致使消息總線、或者業務方收到重複的消息,從而對業務產生影響。

舉個栗子:

購買會員卡,上游支付系統負責給用戶扣款,下游系統負責給用戶髮卡,經過MQ異步通知。無論是上半場的ACK丟失,致使MQ收到重複的消息,仍是下半場ACK丟失,致使購卡系統收到重複的購卡通知,均可能出現,上游扣了一次錢,下游發了多張卡。

消息總線的冪等性設計相當重要,是本文將要討論的重點。

2、上半場的冪等性設計

上半場的冪等性設計

MQ消息發送上半場,即上圖中的1-3

  • 1,發送端MQ-client將消息發給服務端MQ-server
  • 2,服務端MQ-server將消息落地
  • 3,服務端MQ-server回ACK給發送端MQ-client

若是3丟失,發送端MQ-client超時後會重發消息,可能致使服務端MQ-server收到重複消息。

此時重發是MQ-client發起的,消息的處理是MQ-server,爲了不步驟2落地重複的消息,對每條消息,MQ系統內部必須生成一個inner-msg-id,做爲去重和冪等的依據,這個內部消息ID的特性是:

(1)全局惟一

(2)MQ生成,具有業務無關性,對消息發送方和消息接收方屏蔽

有了這個inner-msg-id,就能保證上半場重發,也只有1條消息落到MQ-server的DB中,實現上半場冪等。

3、下半場的冪等性設計

下半場的冪等性設計

MQ消息發送下半場,即上圖中的4-6

  • 4,服務端MQ-server將消息發給接收端MQ-client
  • 5,接收端MQ-client回ACK給服務端
  • 6,服務端MQ-server將落地消息刪除

須要強調的是,接收端MQ-client回ACK給服務端MQ-server,是消息消費業務方的主動調用行爲,不能由MQ-client自動發起,由於MQ系統不知道消費方何時真正消費成功。

若是5丟失,服務端MQ-server超時後會重發消息,可能致使MQ-client收到重複的消息。

此時重發是MQ-server發起的,消息的處理是消息消費業務方,消息重發勢必致使業務方重複消費(上例中的一次付款,重複髮卡),爲了保證業務冪等性,業務消息體中,必須有一個biz-id,做爲去重和冪等的依據,這個業務ID的特性是:

(1)對於同一個業務場景,全局惟一

(2)由業務消息發送方生成,業務相關,對MQ透明

(3)由業務消息消費方負責判重,以保證冪等

最多見的業務ID有:支付ID,訂單ID,帖子ID等。

具體到支付購卡場景,發送方必須將支付ID放到消息體中,消費方必須對同一個支付ID進行判重,保證購卡的冪等。

有了這個業務ID,纔可以保證下半場消息消費業務方即便收到重複消息,也只有1條消息被消費,保證了冪等。

3、總結

MQ爲了保證消息必達,消息上下半場都可能發送重複消息,如何保證消息的冪等性呢?

上半場

  • MQ-client生成inner-msg-id,保證上半場冪等。
  • 這個ID全局惟一,業務無關,由MQ保證。

下半場

  • 業務發送方帶入biz-id,業務接收方去重保證冪等。
  • 這個ID對單業務惟一,業務相關,對MQ透明。

結論:冪等性,不只對MQ有要求,對業務上下游也有要求。

 

 

 

消息「時序」與「一致性」爲什麼這麼難?

分佈式系統中,不少業務場景都須要考慮消息投遞的時序,例如:

(1)單聊消息投遞,保證發送方發送順序與接收方展示順序一致

(2)羣聊消息投遞,保證全部接收方展示順序一致

(3)充值支付消息,保證同一個用戶發起的請求在服務端執行序列一致

消息時序是分佈式系統架構設計中很是難的問題,ta爲何難,有什麼常見優化實踐,是本文要討論的問題。

1、爲何時序難以保證,消息一致性難?

爲何分佈式環境下,消息的時序難以保證,這邊簡要分析了幾點緣由:

【時鐘不一致】

分佈式環境下,有多個客戶端、有web集羣、service集羣、db集羣,他們都分佈在不一樣的機器上,機器之間都是使用的本地時鐘,而沒有一個所謂的「全局時鐘」,因此不能用「本地時間」來徹底決定消息的時序。

【多客戶端(發送方)】

多服務器不能用「本地時間」進行比較,假設只有一個接收方,可否用接收方本地時間表示時序呢?遺憾的是,因爲多個客戶端的存在,即便是一臺服務器的本地時間,也沒法表示「絕對時序」。

如上圖,絕對時序上,APP1先發出msg1,APP2後發出msg2,都發往服務器web1,網絡傳輸是不能保證msg1必定先於msg2到達的,因此即便以一臺服務器web1的時間爲準,也不能精準描述msg1與msg2的絕對時序。

【服務集羣(多接收方)】

多發送方不能保證時序,假設只有一個發送方,可否用發送方的本地時間表示時序呢?遺憾的是,因爲多個接收方的存在,沒法用發送方的本地時間,表示「絕對時序」。

如上圖,絕對時序上,web1先發出msg1,後發出msg2,因爲網絡傳輸及多接收方的存在,沒法保證msg1先被接收到先被處理,故也沒法保證msg1與msg2的處理時序。

【網絡傳輸與多線程】

多發送方與多接收方都難以保證絕對時序,假設只有單一的發送方與單一的接收方,可否保證消息的絕對時序呢?結論是悲觀的,因爲網絡傳輸與多線程的存在,仍然不行。

如上圖,web1先發出msg1,後發出msg2,即便msg1先到達(網絡傳輸其實還不能保證msg1先到達),因爲多線程的存在,也不能保證msg1先被處理完。

【怎麼保證絕對時序】

經過上面的分析,假設只有一個發送方,一個接收方,上下游鏈接只有一條鏈接池,經過阻塞的方式通信,難道不能保證先發出的消息msg1先處理麼?

回答:能夠,但吞吐量會很是低,並且單發送方單接收方單鏈接池的假設不太成立,高併發高可用的架構不會容許這樣的設計出現。

2、優化實踐

【以客戶端或者服務端的時序爲準】

多客戶端、多服務端致使「時序」的標準難以界定,須要一個標尺來衡量時序的前後順序,能夠根據業務場景,以客戶端或者服務端的時間爲準,例如:

(1)郵件展現順序,實際上是以客戶端發送時間爲準的,潛臺詞是,發送方只要將郵件協議裏的時間調整爲1970年或者2970年,就能夠在接收方收到郵件後一直「置頂」或者「置底」

(2)秒殺活動時間判斷,確定得以服務器的時間爲準,不可能讓客戶端修改本地時間,就可以提早秒殺

【服務端可以生成單調遞增的id】

這個是毋庸置疑的,不展開討論,例如利用單點寫db的seq/auto_inc_id確定能生成單調遞增的id,只是說性能及擴展性會成爲潛在瓶頸。對於嚴格時序的業務場景,能夠利用服務器的單調遞增id來保證時序。

【大部分業務能接受偏差不大的趨勢遞增id】

消息發送、帖子發佈時間、甚至秒殺時間都沒有這麼精準時序的要求:

(1)同1s內發佈的聊天消息時序亂了

(2)同1s內發佈的帖子排序不對

(3)用1s內發起的秒殺,因爲服務器多臺之間時間有偏差,落到A服務器的秒殺成功了,落到B服務器的秒殺還沒開始,業務上也是能夠接受的(用戶感知不到)

因此,大部分業務,長時間趨勢遞增的時序就可以知足業務需求,很是短期的時序偏差必定程度上可以接受。

關於絕對遞增id,趨勢遞增id的生成架構,詳見文章《細聊分佈式ID生成方法》,此處不展開。

【利用單點序列化,能夠保證多機相同時序】

數據爲了保證高可用,須要作到進行數據冗餘,同一份數據存儲在多個地方,怎麼保證這些數據的修改消息是一致的呢?利用的就是「單點序列化」:

(1)先在一臺機器上序列化操做

(2)再將操做序列分發到全部的機器,以保證多機的操做序列是一致的,最終數據是一致的

典型場景一:數據庫主從同步

數據庫的主從架構,上游分別發起了op1,op2,op3三個操做,主庫master來序列化全部的SQL寫操做op3,op1,op2,而後把相同的序列發送給從庫slave執行,以保證全部數據庫數據的一致性,就是利用「單點序列化」這個思路。

典型場景二:GFS中文件的一致性

GFS(Google File System)爲了保證文件的可用性,一份文件要存儲多份,在多個上游對同一個文件進行寫操做時,也是由一個主chunk-server先序列化寫操做,再將序列化後的操做發送給其餘chunk-server,來保證冗餘文件的數據一致性的。

【單對單聊天,怎麼保證發送順序與接收順序一致】

單人聊天的需求,發送方A依次發出了msg1,msg2,msg3三個消息給接收方B,這三條消息可否保證顯示時序的一致性(發送與顯示的順序一致)?

回答:

(1)若是利用服務器單點序列化時序,可能出現服務端收到消息的時序爲msg3,msg1,msg2,與發出序列不一致

(2)業務上不須要全局消息一致,只須要對於同一個發送方A,ta發給B的消息時序一致就行,常見優化方案,在A往B發出的消息中,加上發送方A本地的一個絕對時序,來表示接收方B的展示時序

msg1{seq:10, receiver:B,msg:content1 }

msg2{seq:20, receiver:B,msg:content2 }

msg3{seq:30, receiver:B,msg:content3 }

潛在問題:若是接收方B先收到msg3,msg3會先展示,後收到msg1和msg2後,會展示在msg3的前面。

不管如何,是按照接收方收到時序展示,仍是按照服務端收到的時序展示,仍是按照發送方發送時序展示,是pm須要思考的點,技術上都可以實現(接收方按照發送時序展示是更合理的)。

總之,須要一杆標尺來衡量這個時序。

【羣聊消息,怎麼保證各接收方收到順序一致】

羣聊消息的需求,N個羣友在一個羣裏聊,怎麼保證全部羣友收到的消息顯示時序一致?

回答:

(1)不能再利用發送方的seq來保證時序,由於發送方不單點,時間也不一致

(2)能夠利用服務器的單點作序列化

此時羣聊的發送流程爲:

(1)sender1發出msg1,sender2發出msg2

(2)msg1和msg2通過接入集羣,服務集羣

(3)service層到底層拿一個惟一seq,來肯定接收方展現時序

(4)service拿到msg2的seq是20,msg1的seq是30

(5)經過投遞服務講消息給多個羣友,羣友即便接收到msg1和msg2的時間不一樣,但能夠統一按照seq來展示

這個方法能實現,全部羣友的消息展現時序相同。

缺點是,這個生成全局遞增序列號的服務很容易成爲系統瓶頸,還有沒有進一步的優化方法呢?

思路:羣消息其實也不用保證全局消息序列有序,而只要保證一個羣內的消息有序便可,這樣的話,「id串行化」就成了一個很好的思路。

這個方案中,service層再也不須要去一個統一的後端拿全局seq,而是在service鏈接池層面作細小的改造,保證一個羣的消息落在同一個service上,這個service就能夠用本地seq來序列化同一個羣的全部消息,保證全部羣友看到消息的時序是相同的。

關於id串行化的細節,可詳見《利用id串行化解決緩存與數據庫一致性問題》,此處不展開。

3、總結

(1)分佈式環境下,消息的有序性是很難的,緣由多種多樣:時鐘不一致,多發送方,多接收方,多線程,網絡傳輸不肯定性等

(2)要「有序」,先得有衡量「有序」的標尺,能夠是客戶端標尺,能夠是服務端標尺

(3)大部分業務可以接受大範圍趨勢有序,小範圍偏差;絕對有序的業務,能夠藉助服務器絕對時序的能力

(4)單點序列化,是一種常見的保證多機時序統一的方法,典型場景有db主從一致,gfs多文件一致

(5)單對單聊天,只需保證發出的時序與接收的時序一致,能夠利用客戶端seq

(6)羣聊,只需保證全部接收方消息時序一致,須要利用服務端seq,方法有兩種,一種單點絕對時序,另外一種id串行化

 

 

 

1分鐘實現「延遲消息」功能

1、緣起

不少時候,業務有「在一段時間以後,完成一個工做任務」的需求。

例如:滴滴打車訂單完成後,若是用戶一直不評價,48小時後會將自動評價爲5星。

通常來講怎麼實現這類「48小時後自動評價爲5星」需求呢?

1. 常見方案:

啓動一個cron定時任務,每小時跑一次,將完成時間超過48小時的訂單取出,置爲5星,並把評價狀態置爲已評價。

假設訂單表的結構爲:t_order(oid, finish_time, stars, status, …),更具體的,定時任務每隔一個小時會這麼作一次:

  1. select oid from t_order where finish_time > 48hours and status=0; 
  2. update t_order set stars=5 and status=1 where oid in[…]; 

若是數據量很大,須要分頁查詢,分頁update,這將會是一個for循環。

2. 方案的不足:

(1)輪詢效率比較低

(2)每次掃庫,已經被執行過記錄,仍然會被掃描(只是不會出如今結果集中),有重複計算的嫌疑

(3)時效性不夠好,若是每小時輪詢一次,最差的狀況下,時間偏差會達到1小時

(4)若是經過增長cron輪詢頻率來減小(3)中的時間偏差,(1)中輪詢低效和(2)中重複計算的問題會進一步凸顯

如何利用「延時消息」,對於每一個任務只觸發一次,保證效率的同時保證明時性,是今天要討論的問題。

2、高效延時消息設計與實現

高效延時消息,包含兩個重要的數據結構:

  • 環形隊列,例如能夠建立一個包含3600個slot的環形隊列(本質是個數組)
  • 任務集合,環上每個slot是一個Set

同時,啓動一個timer,這個timer每隔1s,在上述環形隊列中移動一格,有一個Current Index指針來標識正在檢測的slot。

Task結構中有兩個很重要的屬性:

  • Cycle-Num:當Current Index第幾圈掃描到這個Slot時,執行任務
  • Task-Function:須要執行的任務指針

假設當前Current Index指向第一格,當有延時消息到達以後,例如但願3610秒以後,觸發一個延時消息任務,只需:

  • 計算這個Task應該放在哪個slot,如今指向1,3610秒以後,應該是第11格,因此這個Task應該放在第11個slot的Set中
  • 計算這個Task的Cycle-Num,因爲環形隊列是3600格(每秒移動一格,正好1小時),這個任務是3610秒後執行,因此應該繞3610/3600=1圈以後再執行,因而Cycle-Num=1

Current Index不停的移動,每秒移動到一個新slot,這個slot中對應的Set,每一個Task看Cycle-Num是否是0:

  • 若是不是0,說明還須要多移動幾圈,將Cycle-Num減1
  • 若是是0,說明立刻要執行這個Task了,取出Task-Funciton執行(能夠用單獨的線程來執行Task),並把這個Task從Set中刪除

使用了「延時消息」方案以後,「訂單48小時後關閉評價」的需求,只需將在訂單關閉時,觸發一個48小時以後的延時消息便可:

  • 無需再輪詢所有訂單,效率高
  • 一個訂單,任務只執行一次
  • 時效性好,精確到秒(控制timer移動頻率能夠控制精度)

3、總結

環形隊列是一個實現「延時消息」的好方法,開源的MQ好像都不支持延遲消息,不妨本身實現一個簡易的「延時消息隊列」,能解決不少業務問題,並減小不少低效掃庫的cron任務。

 

 

 

 

1、緣起

不少時候,業務有定時任務或者定時超時的需求,當任務量很大時,可能須要維護大量的timer,或者進行低效的掃描。

例如:58到家APP實時消息通道系統,對每一個用戶會維護一個APP到服務器的TCP鏈接,用來實時收發消息,對這個TCP鏈接,有這樣一個需求:「若是連續30s沒有請求包(例如登陸,消息,keepalive包),服務端就要將這個用戶的狀態置爲離線」。

其中,單機TCP同時在線量約在10w級別,keepalive請求包大概30s一次,吞吐量約在3000qps。

通常來講怎麼實現這類需求呢?

1. 「輪詢掃描法」

1)用一個Map

2)當某個用戶uid有請求包來到,實時更新這個Map

3)啓動一個timer,當Map中不爲空時,輪詢掃描這個Map,看每一個uid的last_packet_time是否超過30s,若是超過則進行超時處理

2. 「多timer觸發法」

1)用一個Map

2)當某個用戶uid有請求包來到,實時更新這個Map,並同時對這個uid請求包啓動一個timer,30s以後觸發

3)每一個uid請求包對應的timer觸發後,看Map中,查看這個uid的last_packet_time是否超過30s,若是超過則進行超時處理

  • 方案一:只啓動一個timer,但須要輪詢,效率較低
  • 方案二:不須要輪詢,但每一個請求包要啓動一個timer,比較耗資源

特別在同時在線量很大時,很容易CPU100%,如何高效維護和觸發大量的定時/超時任務,是本文要討論的問題。

2、環形隊列法

廢話很少說,三個重要的數據結構:

1)30s超時,就建立一個index從0到30的環形隊列(本質是個數組)

2)環上每個slot是一個Set,任務集合

3)同時還有一個Map

環形隊列法

同時:

1)啓動一個timer,每隔1s,在上述環形隊列中移動一格,0->1->2->3…->29->30->0…

2)有一個Current Index指針來標識剛檢測過的slot

1. 當有某用戶uid有請求包到達時:

1)從Map結構中,查找出這個uid存儲在哪個slot裏

2)從這個slot的Set結構中,刪除這個uid

3)將uid從新加入到新的slot中,具體是哪個slot呢 =>Current Index指針所指向的上一個slot,由於這個slot,會被timer在30s以後掃描到

(4)更新Map,這個uid對應slot的index值

2. 哪些元素會被超時掉呢?

Current Index每秒種移動一個slot,這個slot對應的Set中全部uid都應該被集體超時!若是最近30s有請求包來到,必定被放到Current Index的前一個slot了,Current Index所在的slot對應Set中全部元素,都是最近30s沒有請求包來到的。

因此,當沒有超時時,Current Index掃到的每個slot的Set中應該都沒有元素。

3. 優點:

(1)只須要1個timer

(2)timer每1s只須要一次觸發,消耗CPU很低

(3)批量超時,Current Index掃到的slot,Set中全部元素都應該被超時掉

3、總結

這個環形隊列法是一個通用的方法,Set和Map中能夠是任何task,本文的uid是一個最簡單的舉例。

 

 

問:爲何會有本文?

答:上一篇文章《到底何時該使用MQ?》引發了普遍的討論,有朋友回覆說,MQ的還有一個典型應用場景是緩衝流量,削峯填谷,本文將簡單介紹下,MQ要實現什麼細節,才能緩衝流量,削峯填谷。

 

問:站點與服務,服務與服務上下游之間,通常如何通信?

答:有兩種常見的方式

一種是「直接調用」,經過RPC框架,上游直接調用下游。

 

在某些業務場景之下(具體哪些業務場景,見《到底何時該使用MQ?》),能夠採用「MQ推送」,上游將消息發給MQ,MQ將消息推送給下游。

 

問:爲何會有流量衝擊?

答:無論採用「直接調用」仍是「MQ推送」,都有一個缺點,下游消息接收方沒法控制到達本身的流量,若是調用方不限速,頗有可能把下游壓垮。

 

舉個栗子,秒殺業務:

上游發起下單操做

下游完成秒殺業務邏輯(庫存檢查,庫存凍結,餘額檢查,餘額凍結,訂單生成,餘額扣減,庫存扣減,生成流水,餘額解凍,庫存解凍)

 

上游下單業務簡單,每秒發起了10000個請求,下游秒殺業務複雜,每秒只能處理2000個請求,頗有可能上游不限速的下單,致使下游系統被壓垮,引起雪崩。

 

爲了不雪崩,常見的優化方案有兩種:

1)業務上游隊列緩衝,限速發送

2)業務下游隊列緩衝,限速執行

 

無論哪一種方案,都會引入業務的複雜性,有「緩衝流量」需求的系統都須要加入相似的機制(具體怎麼保證消息可達,見《消息總線可否實現消息必達?》),正所謂「通用痛點統一解決」,須要一個通用的機制解決這個問題。

 

問:如何緩衝流量?

答:明明中間有了MQ,而且MQ有消息落地的機制,爲什麼不能利用MQ來作緩衝呢?顯然是能夠的。

 

問:MQ怎麼改能緩衝流量?

答:由MQ-server推模式,升級爲MQ-client拉模式。

MQ-client根據本身的處理能力,每隔必定時間,或者每次拉取若干條消息,實施流控,達到保護自身的效果。而且這是MQ提供的通用功能,無需上下游修改代碼。

 

問:若是上游發送流量過大,MQ提供拉模式確實能夠起到下游自我保護的做用,會不會致使消息在MQ中堆積?

答:下游MQ-client拉取消息,消息接收方可以批量獲取消息,須要下游消息接收方進行優化,方可以提高總體吞吐量,例如:批量寫。

 

結論

1)MQ-client提供拉模式,定時或者批量拉取,能夠起到削平流量,下游自我保護的做用(MQ須要作的)

2)要想提高總體吞吐量,須要下游優化,例如批量處理等方式(消息接收方須要作的)

 

58到家架構優化具有總體性,須要通用服務和業務方一塊兒優化升級。

 

 

 

 

 

 

 

1、消息隊列概述
消息隊列中間件是分佈式系統中重要的組件,主要解決應用解耦,異步消息,流量削鋒等問題,實現高性能,高可用,可伸縮和最終一致性架構。目前使用較多的消息隊列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ

2、消息隊列應用場景
如下介紹消息隊列在實際應用中經常使用的使用場景。異步處理,應用解耦,流量削鋒和消息通信四個場景。

2.1異步處理
場景說明:用戶註冊後,須要發註冊郵件和註冊短信。傳統的作法有兩種 1.串行的方式;2.並行方式
a、串行方式:將註冊信息寫入數據庫成功後,發送註冊郵件,再發送註冊短信。以上三個任務所有完成後,返回給客戶端。

b、並行方式:將註冊信息寫入數據庫成功後,發送註冊郵件的同時,發送註冊短信。以上三個任務完成後,返回給客戶端。與串行的差異是,並行的方式能夠提升處理的時間

假設三個業務節點每一個使用50毫秒鐘,不考慮網絡等其餘開銷,則串行方式的時間是150毫秒,並行的時間多是100毫秒。
由於CPU在單位時間內處理的請求數是必定的,假設CPU1秒內吞吐量是100次。則串行方式1秒內CPU可處理的請求量是7次(1000/150)。並行方式處理的請求量是10次(1000/100)
小結:如以上案例描述,傳統的方式系統的性能(併發量,吞吐量,響應時間)會有瓶頸。如何解決這個問題呢?

引入消息隊列,將不是必須的業務邏輯,異步處理。改造後的架構以下:

按照以上約定,用戶的響應時間至關因而註冊信息寫入數據庫的時間,也就是50毫秒。註冊郵件,發送短信寫入消息隊列後,直接返回,所以寫入消息隊列的速度很快,基本能夠忽略,所以用戶的響應時間多是50毫秒。所以架構改變後,系統的吞吐量提升到每秒20 QPS。比串行提升了3倍,比並行提升了兩倍。

2.2應用解耦
場景說明:用戶下單後,訂單系統須要通知庫存系統。傳統的作法是,訂單系統調用庫存系統的接口。以下圖:

傳統模式的缺點:假如庫存系統沒法訪問,則訂單減庫存將失敗,從而致使訂單失敗,訂單系統與庫存系統耦合

如何解決以上問題呢?引入應用消息隊列後的方案,以下圖:

訂單系統:用戶下單後,訂單系統完成持久化處理,將消息寫入消息隊列,返回用戶訂單下單成功
庫存系統:訂閱下單的消息,採用拉/推的方式,獲取下單信息,庫存系統根據下單信息,進行庫存操做
假如:在下單時庫存系統不能正常使用。也不影響正常下單,由於下單後,訂單系統寫入消息隊列就再也不關心其餘的後續操做了。實現訂單系統與庫存系統的應用解耦

2.3流量削鋒
流量削鋒也是消息隊列中的經常使用場景,通常在秒殺或團搶活動中使用普遍。
應用場景:秒殺活動,通常會由於流量過大,致使流量暴增,應用掛掉。爲解決這個問題,通常須要在應用前端加入消息隊列。
a、能夠控制活動的人數
b、能夠緩解短期內高流量壓垮應用

用戶的請求,服務器接收後,首先寫入消息隊列。假如消息隊列長度超過最大數量,則直接拋棄用戶請求或跳轉到錯誤頁面。
秒殺業務根據消息隊列中的請求信息,再作後續處理

2.4日誌處理
日誌處理是指將消息隊列用在日誌處理中,好比Kafka的應用,解決大量日誌傳輸的問題。架構簡化以下

日誌採集客戶端,負責日誌數據採集,定時寫受寫入Kafka隊列
Kafka消息隊列,負責日誌數據的接收,存儲和轉發
日誌處理應用:訂閱並消費kafka隊列中的日誌數據 

2.5消息通信
消息通信是指,消息隊列通常都內置了高效的通訊機制,所以也能夠用在純的消息通信。好比實現點對點消息隊列,或者聊天室等
點對點通信:

客戶端A和客戶端B使用同一隊列,進行消息通信。

聊天室通信:

客戶端A,客戶端B,客戶端N訂閱同一主題,進行消息發佈和接收。實現相似聊天室效果。

以上實際是消息隊列的兩種消息模式,點對點或發佈訂閱模式。模型爲示意圖,供參考。

3、消息中間件示例 
3.1電商系統

消息隊列採用高可用,可持久化的消息中間件。好比Active MQ,Rabbit MQ,Rocket Mq。
(1)應用將主幹邏輯處理完成後,寫入消息隊列。消息發送是否成功能夠開啓消息的確認模式。(消息隊列返回消息接收成功狀態後,應用再返回,這樣保障消息的完整性)
(2)擴展流程(發短信,配送處理)訂閱隊列消息。採用推或拉的方式獲取消息並處理。
(3)消息將應用解耦的同時,帶來了數據一致性問題,能夠採用最終一致性方式解決。好比主數據寫入數據庫,擴展應用根據消息隊列,並結合數據庫方式實現基於消息隊列的後續處理。

3.2日誌收集系統

分爲Zookeeper註冊中心,日誌收集客戶端,Kafka集羣和Storm集羣(OtherApp)四部分組成。
Zookeeper註冊中心,提出負載均衡和地址查找服務
日誌收集客戶端,用於採集應用系統的日誌,並將數據推送到kafka隊列
Kafka集羣:接收,路由,存儲,轉發等消息處理
Storm集羣:與OtherApp處於同一級別,採用拉的方式消費隊列中的數據

 

 

 

MQ選型對比文檔

這裏寫圖片描述 
綜合選擇RabbitMq

 

 

Kafka是linkedin開源的MQ系統,主要特色是基於Pull的模式來處理消息消費,追求高吞吐量,一開始的目的就是用於日誌收集和傳輸,0.8開始支持複製,不支持事務,適合產生大量數據的互聯網服務的數據收集業務。

RabbitMQ是使用Erlang語言開發的開源消息隊列系統,基於AMQP協議來實現。AMQP的主要特徵是面向消息、隊列、路由(包括點對點和發佈/訂閱)、可靠性、安全。AMQP協議更多用在企業系統內,對數據一致性、穩定性和可靠性要求很高的場景,對性能和吞吐量的要求還在其次。

RocketMQ是阿里開源的消息中間件,它是純Java開發,具備高吞吐量、高可用性、適合大規模分佈式系統應用的特色。RocketMQ思路起源於Kafka,但並非Kafka的一個Copy,它對消息的可靠傳輸及事務性作了優化,目前在阿里集團被普遍應用於交易、充值、流計算、消息推送、日誌流式處理、binglog分發等場景。

相關文章
相關標籤/搜索