• 您的位置:首頁 >聚焦 >

    3萬字聊聊什么是RocketMQ(三)

    2022-04-06 14:04:59    來源:程序員客棧

    大家好,我是Leo。

    上一篇我們介紹了

    MQ分布式事務的相關實現MQ消息丟失,一致性問題在生產,存儲,消費階段如何解決消息重發之后,如何避免重復消費

    繼上篇RocketMQ技術總結二,這篇主要介紹一下

    消息積壓問題如何處理閱讀源碼的小技巧異步方案提升系統性能MQ的緩存策略基礎知識系列

    3萬字聊聊什么是MySQL(初篇)

    3萬字聊聊什么是Redis(完結篇)

    3萬字聊聊什么是RocketMQ (二)

    2萬字聊聊什么是秒殺系統(中)

    大廠面試系列

    4萬字聊聊阿里二面,保證你看不完

    聊聊Redis面試題

    本章概括

    異步方案那里可以暫時一筆帶過,后續寫實戰的時候會詳細深入的

    入手源碼那里,根據自己對未來的發展規劃選擇性翻閱吧。

    消息積壓問題什么是消息積壓

    消息積壓是使用MQ消息隊列系統中,最常見的一種性能問題。如下圖所示,當生產端的生產效率大于消費端的消費效率就會造成消息處理不完的情況,也就叫 “消息積壓”。

    消息積壓應對方案(一)

    第一種方案主要是優化生產端,消費端的處理能力。(消息隊列你就別優化了,那么牛逼的團隊設計出那么好的產品不會出問題的)

    所以?我們只需要處理如何與消息隊列配合,達到一個最佳的性能就夠了。

    對于生產端來說,并不會影響性能。因為一般生產端都是先執行自己的業務程序之后,再生產消息到MQ的。如果說,你的代碼發送消息的性能上不去,你需要優先檢查一下,是不是發消息之前的業務邏輯耗時太多導致的。

    我們之前在講生產端發送消息的過程中,假設這一次交互的平均耗時1ms,我們把這1ms的時間分解開,主要包括下列幾項

    發送前的準備數據,序列化數據,構造請求等耗時。也就是生產端在發送網絡請求之間的耗時發送消息和返回響應在網絡傳輸中的耗時Broker處理消息的耗時

    如果是單線程發送,每次只發送 1 條消息,那么每秒只能發送 1000ms / 1ms * 1 條 /ms = 1000 條 消息,這種情況下并不能發揮出消息隊列的全部實力。

    無論是增加每次發送消息的批量大小,還是增加并發,都能成倍地提升發送性能。至于到底是選擇批量發送還是增加并發,主要取決于生產端程序的業務性質。

    復習鏈接?生產端發送消息過程


    對于消費端來說,大部分的問題應該都是出在這里的。主要的調優點也是在這個位置。

    如果消費端的性能延時只是暫時的,那問題不大,只要消費端的性能恢復之后,超過生產端的性能,那積壓的消息是可以逐漸被消化掉的。

    要是消費速度一直比生產速度慢,時間長了,整個系統就會出現問題,要么,消息隊列的存儲被填滿無法提供服務,要么消息丟失,這對于整個系統來說都是嚴重故障。所以,我們在設計系統的時候,一定要保證消費端的消費性能要高于生產端的發送性能,這樣的系統才能健康的持續運行。

    除了優化業務性能,也可以對消費端進行水平擴容,增加消費端的并發數從而達到總體的消費性能。在擴容 Consumer 的實例數量的同時, 必須同步擴容主題中的分區(也叫隊列)數量,確保 Consumer 的實例數和分區數量是相等的。?如下圖所示。

    如果 Consumer 的實例數量超過分區數量,這樣的擴容實際上是沒有效果的。原因 我們之前講過,因為對于消費者來說,在每個分區上實際上只能支持單線程消費

    消息積壓應對方案(二)

    第二種方案應對的業務場景是,系統正常運轉時,不會出現消息積壓問題。但是某一時刻,突然就開始積壓并且持續上漲。

    這種問題還是比較糾結的,擴容的話,一般情況下是不需要的,不擴容的話某一刻又擋不住。。。。

    遇到這樣的場景無需就是兩種原因,要不生產變快了,要不消費變慢了。我們可以通過監控程序觀察一下數據情況再定位某種原因。

    最后實在沒辦法可以將系統進行降級,通過關閉一些不重要的業務,減少生產方發送的數據量,最低限度的讓系統的熱賣業務還能正常運轉。

    重復消費也會拖慢整個系統的消費速度。

    關于重復消費可以參考?重復消費解決方案

    如何入手學習源碼

    最核心的一點就是查看?官方文檔

    官方文檔是所有技術中 最權威,最齊全的一個資料聚集地

    有些翻譯中文的網站,可能會做到更新不及時,所以還是建議直接看英文文檔,借助翻譯即可。也可以鍛煉英文水平

    首先要?掌握這個技術的整體結構,有哪些功能特性,涉及到的關鍵技術、實現原理和生態系統?等等。掌握了這些,對它的整體有了一定了解,然后再去看它的源代碼,就會非常通暢了。比如我之前做的MySQL技術分享如下圖。

    Redis的技術分享如下圖

    RocketMQ的技術分享還沒畫,可以暫時參考一下官方

    我覺得學習這個東西最好的老師是興趣。所以一定要帶著問題去讀源碼。比如

    RocketMQ的消息是怎么寫到文件中的?(肯定不會像我們業務程序那樣直接寫的)RocketMQ的重發機制是怎么實現的?RocketMQ的確認機制是怎么實現的?RocketMQ的分布式的實現源碼是怎么樣的?RocketMQ的消息不丟失是否和MySQL那樣有binlog,redo log,是否和Redis那樣有RDB,AOF呢?

    上述問題反正是我當下最想知道的,但是不要操之過急,學懂整體后再深入。

    一定不要直接從main里看,程序跟書是不一樣的,書是由人學懂之后,整理的一個先后學習順序。而程序是一個網狀結構,是一個功能一個功能的實現

    這上述也是我接下來學習源碼的方法。源碼分析的話,我的想法是等我學完RocketMQ的基礎原理,我會從第一篇文章涉及到的知識點,進入源碼閱讀分析整理

    MySQL,Redis就算了,打算發力MQ

    異步方案提升系統性能

    這里先簡單聊一下思想,后續會用代碼案例實現。

    異步執行

    簡單的說,異步思想就是,當我們要執行一項比較耗時的操作時,不去等待操作結束,而是給這個操作一個命令:“當操作完成后,接下來去執行什么。”

    使用異步編程模型,雖然并不能加快程序本身的速度,但可以減少或者避免線程等待,只用很少的線程就可以達到超高的吞吐能力。

    同時我們也需要注意到異步模型的問題:相比于同步實現,異步實現的復雜度要大很多,代 碼的可讀性和可維護性都會顯著的下降。雖然使用一些異步編程框架會在一定程度上簡化異 步開發,但是并不能解決異步模型高復雜度的問題。

    異步性能雖好,但一定不要濫用,只有類似在像消息隊列這種業務邏輯簡單并且需要超高吞 吐量的場景下,或者必須長時間等待資源的地方,才考慮使用異步模型。如果系統的業務邏 輯比較復雜,在性能足夠滿足業務需求的情況下,采用符合人類自然的思路且易于開發和維 護的同步模型是更加明智的選擇。

    異步網絡

    傳統的同步網絡 IO,一般采用的都是一個線程對應一個 Channel 接收數據,很難支持高并 發和高吞吐量。這個時候,我們需要使用異步的網絡 IO 框架來解決問題。

    Netty 和 NIO 是兩種異步網絡框架。

    Netty 自動地解決了線程控制、緩存管理、連接管理這些問題,用戶只需要實現對應的 Handler 來處理收到的數據即可。

    NIO 是更加底層的 API,它提供了 Selector 機制,用單個線程同時管理多個連接,解決了多路 復用這個異步網絡通信的核心問題。

    以上是 IO、BIO、NIO的發展,流程圖

    緩存策略

    緩存策略的出現,主要解決的就是如何減少與磁盤IO交互,提升系統性能。對于持久化來說,肯定還是要存磁盤的。所以我們必須保證最大的幾率命中緩存,同時也要減少與磁盤IO的交互。

    一般來說 SSD 每秒鐘可以讀寫幾千次,如果說我們的程序在處理業務請求的時候直接來讀寫磁盤,假設處理每次請求需要讀寫 3~5 次,即使每次請求的數據量不大,你的程序最多每秒也就 能處理 1000 次左右的請求。而內存的隨機讀寫速度是磁盤的 10 萬倍!所以,使用內存作為緩存來加速應用程序的訪問速度,是幾乎所有高性能系統都會采用的方法。

    只讀與讀寫緩存的抉擇

    使用緩存,首先你就會面臨選擇讀緩存還是讀寫緩存的問題。他們唯一的區別就是,在更新數據的時候,是否經過緩存

    讀寫緩存:?它是一種犧牲數據一致性換取性能的設計,天然是不可靠的。

    為什么說他是不可靠的呢?可以從上圖中看出,寫入數據到PageCache時,并不是同步寫入的,而是異步,如果在這段期間,還沒開始異步寫入時,服務器直接宕機了,就丟失了數據。

    如果說我們人工操作,執行一次sync。這樣也就失去了緩存的意義

    緩存的意義不就是減少與磁盤IO的交互嘛

    寫緩存的實現是非常復雜的。應用程序不停地更新 PageCache 中的數據,操作系統 需要記錄哪些數據有變化,同時還要在另外一個線程中,把緩存中變化的數據更新到磁盤文 件中。在提供并發讀寫的同時來異步更新數據,這個過程中要保證數據的一致性,并且有非常好的性能,實現這些真不是一件容易的事兒。

    所以一般?推薦使用只讀緩存。

    如何保持緩存數據新鮮

    上面提到了推薦使用只讀緩存,對于只讀緩存來說,緩存中的數據來源只有一個途徑,就是從磁盤上來。當數據需要更新的時候,磁盤中的數據和緩存中的副本都需要進行更新。我們知道在分布式系統中,除非是使用事務或者一些分布式一致性算法來保證數據一致性,否則,由于節點宕機、網絡傳輸故 障等情況的存在,我們是無法保證緩存中的數據和磁盤中的數據是完全一致的。

    我們要做的就是盡可能最大的保證數據同步。最省心,代價最大的就是采用分布式事務來解決了。

    另一種簡單的方式就是采用Redis那種過期key的方式實現,數據過期以后即使它還存在緩存中,我們也認為它不再有效,需要從 磁盤上再次加載這條數據,這樣就變相地實現了數據更新。

    還是根據業務吧。比如微信頭像修改后的實時顯示,郵件的延遲幾秒鐘接收都可以采用過期key的方式

    如果是那種交易類系統,對數據一致性比較敏感的可以采用犧牲性能來決定一致性。

    緩存的置換策略

    在使用緩存的過程中,除了要考慮數據一致性的問題,你還需要關注的另一個重要的問題是,在內存有限的情況下,要優先緩存哪些數據,讓緩存的命中率最高。

    當應用程序要訪問某些數據的時候,如果這些數據在緩存中,那直接訪問緩存中的數據就可以了,這次訪問的速度是很快的,這種情況我們稱為一次緩存命中;如果這些數據不在緩存中,那只能去磁盤中訪問數據,就會比較慢。這種情況我們稱為“緩存穿透”。顯然,緩存的命中率越高,應用程序的總體性能就越好。

    那用什么樣的策略來選擇緩存的數據,能使得緩存的命中率盡量高一些呢?

    如果你的系統是那種可以預測未來訪問哪些數據的系統,比如說,有的系統它會定期做數據同步,每次同步的數據范圍都是一樣的,像這樣的系統,緩存策略很簡單,就是你要訪問什么數據,就緩存什么數據,甚至可以做到百分之百的命中。

    但是,大部分系統,它并沒有辦法準確地預測未來會有哪些數據會被訪問到,所以只能使用一些策略來盡可能地提高緩存命中率。

    一般來說,我們都會在數據首次被訪問的時候,順便把這條數據放到緩存中。隨著訪問的數據越來越多,總有把緩存占滿的時刻,這個時候就需要把緩存中的一些數據刪除掉,以便存放新的數據,這個過程稱為緩存置換。

    到這里,問題就變成了:當緩存滿了的時候,刪除哪些數據,才能會使緩存的命中率更高一 些,也就是采用什么置換策略的問題。

    命中率最高的置換策略,一定是根據你的業務邏輯,定制化的策略。比如,你如果知道某些數據已經刪除了,永遠不會再被訪問到,那優先置換這些數據肯定是沒問題的。再比如,你的系統是一個有會話的系統,你知道現在哪些用戶是在線的,哪些用戶已經離線,那優先置換那些已經離線用戶的數據,盡量保留在線用戶的數據也是一個非常好的策略。

    另外一個選擇,就是?使用通用的置換算法。一個最經典也是最實用的算法就是 LRU 算法, 也叫最近最少使用算法。這個算法它的思想是,最近剛剛被訪問的數據,它在將來被訪問的 可能性也很大,而很久都沒被訪問過的數據,未來再被訪問的幾率也不大。

    基于這個思想,LRU 的算法原理非常簡單,它總是把最長時間未被訪問的數據置換出去。你別看這個 LRU 算法這么簡單,它的效果是非常非常好的。

    可以參考Redis的的LRU文章,實現原理都是差不多的?LRU算法的實現

    充電分享

    少年有夢,不應止于心動,更應付出行動,用自己的關,照亮自己的路,努力追上那個曾經被賦予厚望的自己

    —— 致自己,致各位追夢的你們

    結尾

    有些不懂的地方或者不對的地方,麻煩各位指出,一定修改優化!

    非常歡迎大家加我個人微信有關后端方面的問題我們在群內一起討論!?我們下l,。;期再見!

    長按上方掃碼二維碼,加我微信,拉你進群

    關鍵詞: 發送消息 應用程序 數據一致性

    相關閱讀

    BB电子