淺談高可用分佈式流數據存儲設
當數據規模發展到必定階段,數據治理儼然已經是企業系統建設的內在要求。伴隨着業務的快速發展,多種多樣結構複雜的數據給數據治理帶來了巨大的考驗。算法
早期的小規模業務,單體服務配合單個數據庫便可知足業務需求。而當下,數據庫分庫分表,並採用讀寫分離和分佈式的架構模型,同一份數據被轉換成各類特定的數據格式,存放在各類各樣的數據庫中,會消耗大量的存儲和計算資源。爲解決這一數據治理亂象,分佈式流數據存儲應運而生。數據庫
數據存儲的進化史
起初,單體服務應用只需一個數據庫存儲數據就足夠了。隨着業務需求的增多,服務從1個增加到N個,數據也須要分庫分表來存儲,若基於容災等方面考慮,還須要作多個副本。此外不一樣的業務場景須要用到不一樣結構的數據存儲,好比搜索須要用到ElasticSearch,存儲分析須要用到Hive集羣,在線業務須要用到K-V(鍵-值,NoSQL)存儲和MySQL存儲,同時這些數據還要在必定的業務場景下作到實時同步。
在這種狀況下,數據就存在諸多問題:後端
- 當數據在各類場景下ETL(Extract-Transform-Load,數據抽取、轉換和加載)會形成嚴重的資源浪費;
- 每份數據都有快照備份,佔用極大的存儲空間;
- 當某一份數據不止服務於一個微服務時,一旦業務調整,一份數據的變更將會影響下游的數據變更,就會出現嚴重的耦合問題。
冗雜數據隨業務擴張而呈指數增加,數據治理顯得尤其重要。分佈式流數據存儲平臺,即可應對上述問題。緩存
流數據存儲平臺的設計
先後端數據在一個時間點產生,當咱們對某個特定的數據作變動,並將其放入到流存儲平臺裏面,其餘服務調用數據時,無需通過ETL便可直接使用。須要注意的是,流數據不能服務於終端業務,由於流數據在平臺中是查詢不友好的,沒法按照業務場景去查詢。網絡
對於以上的應用背景分析,那麼其存儲特性也就很顯而易見了:架構
- 有序性:數據必須是有序的,由於數據的讀取和處理是按照寫入的順序進行的;
- 擴展性:數據只能在尾部寫入,寫入則沒法變化;
- 性能:具備高性能、可靠性等分佈式系統特性;
- 一致性:不需強一致性,要求順序一致性便可;
- 容量:因存儲全部數據,要求近乎無限的容量。
基於上述存儲特性,能夠有針對性地設計流數據存儲平臺的核心組件。併發
1.存儲結構設計
存儲結構設計決定着每個存儲產品的性能。下圖爲流數據存儲的結構設計:
因數據長度不一,一個索引記錄一條數據的位置。在這裏,每一個索引都是十六進制Long型的數值,也即索引的大小是固定的,指向的數據是不固定的。如上圖,每個索引都指向了每條數據的起始位置。
對應到文件系統,數據分爲N個小文件,文件名對應文件裏面第一條數據的位置,相應的索引也是同樣的。根據以上設計,總結存儲結構對應的時間複雜度以下:異步
- 寫入:O(1)
寫入數據時,是直接在尾部追加的,所以爲O(1); - 查找:O(logn)+O(logn)≈O(1)
在讀取時,使用索引值去讀取。因每一個文件的名字就是文件裏面第一條數據的位置,且每個索引都是16字節,把數據的索引值乘上16便可定位索引裏的全局位置。在存儲時,索引文件和數據文件都存放在內存跳錶中,在跳錶裏查找索引所在文件的時間複雜度爲O(logn),其中n爲跳錶裏面文件的數量,換算相對位置進而定位數據的索引位置。再在數據文件裏作一次O(logn)的搜索找到數據所在文件,根據相對位置就可找到數據了。通常狀況下,文件的數量和數據的數量相比差了不少個數量級,所以上述的搜索時間複雜度能夠約等於O(1)。
2.緩存設計
緩存的設計優化主要有如下幾個方面:分佈式
- PageCache(緩存頁)緩存文件:流數據的一個特色是順序讀寫,其隨機讀取的狀況不多出現。所以將每個文件對應內存的一個PageCache,直接把整個文件緩存到內存裏面。這種設計的優點在於,不須要爲緩存頁編寫單獨的查找算法,只需複用文件的查找算法便可,而且緩存頁和文件的對應關係也變得很是簡單;
- 使用堆外內存:Java的GC機制對高併發來講並不友好,當併發上來時,很難預測GC的時間。堆外內存可防止過多的GC操做,但不合理的使用堆外內存會形成內存泄漏,因此合理的使用堆外內存可提升高併發能力;
- 異步預加載:流數據都是連續讀取,當讀取文件接近尾部時,就會大機率讀下一個文件,所以,在文件讀到接近結尾時加載下一個文件,能有效預防卡頓;
- 讀寫共頁:寫數據的時候將數據直接緩存起來,直接從緩存讀取,而不是去磁盤讀取文件;
- PLRU淘汰策略:當內存將滿時,須進行釋放,LRU能夠把最近不經常使用的數據淘汰掉。此外,根據流數據的特色,越接近尾部的數據被訪問的可能性越大,將數據根據其與尾部的距離加上權值,就能將不經常使用且相對老的數據淘汰掉,能很好地避免「挖墳」的狀況。(備註:當某應用忽然讀取很古老的數據時,讀取過的數據會加載至緩存裏面,此時形成緩存的命中率急劇降低,即爲「挖墳」)
3.寫入流程優化
寫入流程總共靠6組線程來完成。當數據寫入,IOThreads將數據推至請求隊列,喚醒WriteThreads,從隊列中取出數據、產生索引、寫入內存緩存,生成響應並放至隊列,通知ReplicationThreads發送複製請求和FlushThreads進行數據的異步刷盤。須要注意的是,此時並不會將響應發回客戶端。ReplicationThreads從緩存中讀取數據,向節點發送複製的包,由IOThreads等待響應。當IOThreads收到超半數的響應以後,通知ResponseThreads發送以前生成的響應。微服務
根據這種寫入流程的優化,會發現每一組線程之間並不會相互等待,也並不須要鎖來共享任何的數據,這樣就實現了全異步化,從而達到系統的最大性能。
4.集羣架構的設計
若是說小的技巧和方法更適用於單節點的優化,則集羣層面更多的是取捨問題。咱們需立足實用角度,全面考慮一致性、可用性和分區容錯性,且放棄一個或多個方面。
好比,使用Redis和MySQL作緩存,則意味放棄了一致性;再好比,作大促限流時,數據加載的慢,則放棄了性能,保留了一致性和可用性。
所以,在作架構設計時,應儘可能將服務作成無狀態的,進而更容易地實現水平擴展,且沒必要考慮一致性問題。固然,若是業務必須是有狀態的、計算是無狀態的,則可將計算和存儲分離,存儲可放到MySQL或者ZooKeeper裏面。
思惟沉澱
經過高性能分佈式流數據存儲平臺的設計分析,能夠發現,不少思想值得借鑑。
系統的設計必定要結合實際需求。在設計存儲數據的文件結構時,根據流數據的特色來優化存儲方法,可達到讀寫都是O(1)的時間複雜度;
使用異步模型提高系統性能。同步模式下,若系統涉及到數據讀取或其餘阻塞操做時,大量線程處於等待狀態,會出現資源利用率低且性能沒法提高的狀況。而在異步模型下,系統可以協調線程之間的運行時間,減小或避免線程等待,用不多的線程就可達到超高的吞吐能力,從而使系統工做效率的最大化。須要注意的是,異步並不會加快程序自己的運行速度。
使用緩存加速數據的讀寫。衆所周知,緩存的I/O性能是磁盤沒法比擬的,所以可以使用緩存減小磁盤的I/O,加速應用程序的訪問速度。可是在構建緩存時,須要注意兩個問題:緩存數據的命中率的保持和緩存的置換策略。其實,這兩個問題本質上是一個問題,緩存置換的目的就是爲了保持緩存數據的命中率。對於系統來講,只有緩存命中率的提高,使用緩存纔會有顯著的效果。
結合實際充分考慮CAP理論。在分佈式系統架構設計時,必須根據業務特色在一致性©、可用性(A)和分區容錯性§之間作出取捨。好比一個分佈式系統中不存在數據副本,此時該系統必然知足強一致性和分區容錯性,但若是發生網絡分區或者宕機,就會致使部分數據沒法訪問,此時可用性就會下降。所以,世界上本沒有最好的分佈式架構,只有適合當前業務需求的架構,纔是最優秀的架構。
參考書目資料清單:
- Qcon北京2019大會,李玥《高可用分佈式流數據存儲設計》演講;
- 極客時間,李玥《從源碼角度全面解析MQ的設計與實現》;
- 鍾林森,《分佈式中間件技術實戰》。
做者:白小迪,應用開發中心技術平臺組
指導老師:王東、馬姿
版權歸做者全部,任何形式轉載請聯繫做者。 it_hr@zybank.com.cn