基于SSD的Kafka应用层缓存架构设计与实现
Kafka在美團數據平臺承擔著統一的數據緩存和分發的角色,針對因PageCache互相污染,進而引發PageCache競爭導致實時作業被延遲作業影響的痛點,美團基于SSD自研了Kafka的應用層緩存架構。本文主要介紹了該架構的設計與實現,主要包括方案選型,與其他備選方案的比較以及方案的核心思考點等,最后介紹該方案與其他備選方案的性能對比。
Kafka在美團數據平臺的現狀
Kafka出色的I/O優化以及多處異步化設計,相比其他消息隊列系統具有更高的吞吐,同時能夠保證不錯的延遲,十分適合應用在整個大數據生態中。
目前在美團數據平臺中,Kafka承擔著數據緩沖和分發的角色。如下圖所示,業務日志、接入層Nginx日志或線上DB數據通過數據采集層發送到Kafka,后續數據被用戶的實時作業消費、計算,或經過數倉的ODS層用作數倉生產,還有一部分則會進入公司統一日志中心,幫助工程師排查線上問題。
目前美團線上Kafka規模:
-
集群規模:節點數達6000+,集群數100+。
-
集群承載:Topic數6萬+,Partition數41萬+。
-
處理的消息規模:目前每天處理消息總量達8萬億,峰值流量為1.8億條/秒
-
提供的服務規模:目前下游實時計算平臺運行了3萬+作業,而這其中絕大多數的數據源均來自Kafka。
Kafka線上痛點分析&核心目標
當前Kafka支撐的實時作業數量眾多,單機承載的Topic和Partition數量很大。這種場景下很容易出現的問題是:同一臺機器上不同Partition間競爭PageCache資源,相互影響,導致整個Broker的處理延遲上升、吞吐下降。
接下來,我們將結合Kafka讀寫請求的處理流程以及線上統計的數據來分析一下Kafka在線上的痛點。
原理分析
Kafka處理讀寫流程的示意圖
對于Produce請求:Server端的I/O線程統一將請求中的數據寫入到操作系統的PageCache后立即返回,當消息條數到達一定閾值后,Kafka應用本身或操作系統內核會觸發強制刷盤操作(如左側流程圖所示)。
對于Consume請求:主要利用了操作系統的ZeroCopy機制,當Kafka Broker接收到讀數據請求時,會向操作系統發送sendfile系統調用,操作系統接收后,首先試圖從PageCache中獲取數據(如中間流程圖所示);如果數據不存在,會觸發缺頁異常中斷將數據從磁盤讀入到臨時緩沖區中(如右側流程圖所示),隨后通過DMA操作直接將數據拷貝到網卡緩沖區中等待后續的TCP傳輸。
綜上所述,Kafka對于單一讀寫請求均擁有很好的吞吐和延遲。處理寫請求時,數據寫入PageCache后立即返回,數據通過異步方式批量刷入磁盤,既保證了多數寫請求都能有較低的延遲,同時批量順序刷盤對磁盤更加友好。處理讀請求時,實時消費的作業可以直接從PageCache讀取到數據,請求延遲較小,同時ZeroCopy機制能夠減少數據傳輸過程中用戶態與內核態的切換,大幅提升了數據傳輸的效率。
但當同一個Broker上同時存在多個Consumer時,就可能會由于多個Consumer競爭PageCache資源導致它們同時產生延遲。下面我們以兩個Consumer為例詳細說明:
如上圖所示,Producer將數據發送到Broker,PageCache會緩存這部分數據。當所有Consumer的消費能力充足時,所有的數據都會從PageCache讀取,全部Consumer實例的延遲都較低。此時如果其中一個Consumer出現消費延遲(圖中的Consumer Process2),根據讀請求處理流程可知,此時會觸發磁盤讀取,在從磁盤讀取數據的同時會預讀部分數據到PageCache中。當PageCache空間不足時,會按照LRU策略開始淘汰數據,此時延遲消費的Consumer讀取到的數據會替換PageCache中實時的緩存數據。后續當實時消費請求到達時,由于PageCache中的數據已被替換掉,會產生預期外的磁盤讀取。這樣會導致兩個后果:
消費能力充足的Consumer消費時會失去PageCache的性能紅利。
多個Consumer相互影響,預期外的磁盤讀增多,HDD負載升高。
我們針對HDD的性能和讀寫并發的影響做了梯度測試,如下圖所示:
可以看到,隨著讀并發的增加,HDD的IOPS和帶寬均會明顯下降,這會進一步影響整個Broker的吞吐以及處理延遲。
線上數據統計
目前Kafka集群TP99流量在170MB/s,TP95流量在100MB/s,TP50流量為50-60MB/s;單機的PageCache平均分配為80GB,取TP99的流量作為參考,在此流量以及PageCache分配情況下,PageCache最大可緩存數據時間跨度為80*1024/170/60 = 8min,可見當前Kafka服務整體對延遲消費作業的容忍性極低。該情況下,一旦部分作業消費延遲,實時消費作業就可能會受到影響。
同時,我們統計了線上實時作業的消費延遲分布情況,延遲范圍在0-8min(實時消費)的作業只占80%,說明目前存在線上存在20%的作業處于延遲消費的狀態。
痛點分析總結
總結上述的原理分析以及線上數據統計,目前線上Kafka存在如下問題:
實時消費與延遲消費的作業在PageCache層次產生競爭,導致實時消費產生非預期磁盤讀。
傳統HDD隨著讀并發升高性能急劇下降。
線上存在20%的延遲消費作業。
按目前的PageCache空間分配以及線上集群流量分析,Kafka無法對實時消費作業提供穩定的服務質量保障,該痛點亟待解決。
預期目標
根據上述痛點分析,我們的預期目標為保證實時消費作業不會由于PageCache競爭而被延遲消費作業影響,保證Kafka對實時消費作業提供穩定的服務質量保障。
解決方案
為什么選擇SSD
根據上述原因分析可知,解決目前痛點可從以下兩個方向來考慮:
消除實時消費與延遲消費間的PageCache競爭,如:讓延遲消費作業讀取的數據不回寫PageCache,或增大PageCache的分配量等。
在HDD與內存之間加入新的設備,該設備擁有比HDD更好的讀寫帶寬與IOPS。
對于第一個方向,由于PageCache由操作系統管理,若修改其淘汰策略,那么實現難度較為復雜,同時會破壞內核本身對外的語義。另外,內存資源成本較高,無法進行無限制的擴展,因此需要考慮第二個方向。
SSD目前發展日益成熟,相較于HDD,SSD的IOPS與帶寬擁有數量級級別的提升,很適合在上述場景中當PageCache出現競爭后承接部分讀流量。我們對SSD的性能也進行了測試,結果如下圖所示:
從圖中可以發現,隨著讀取并發的增加,SSD的IOPS與帶寬并不會顯著降低。通過該結論可知,我們可以使用SSD作為PageCache與HDD間的緩存層。
架構決策
在引入SSD作為緩存層后,下一步要解決的關鍵問題包括PageCache、SSD、HDD三者間的數據同步以及讀寫請求的數據路由等問題,同時我們的新緩存架構需要充分匹配Kafka引擎讀寫請求的特征。本小節將介紹新架構如何在選型與設計上解決上述提到的問題。
Kafka引擎在讀寫行為上具有如下特性:
-
數據的消費頻率隨時間變化,越久遠的數據消費頻率越低。
-
每個分區(Partition)只有Leader提供讀寫服務。
-
對于一個客戶端而言,消費行為是線性的,數據并不會重復消費。
下文給出了兩種備選方案,下面將對兩種方案給出我們的選取依據與架構決策。
備選方案一:基于操作系統內核層實現
目前開源的緩存技術有FlashCache、BCache、DM-Cache、OpenCAS等,其中BCache和DM-Cache已經集成到Linux中,但對內核版本有要求,受限于內核版本,我們僅能選用FlashCache/OpenCAS。
如下圖所示,FlashCache以及OpenCAS二者的核心設計思路類似,兩種架構的核心理論依據為“數據局部性”原理,將SSD與HDD按照相同的粒度拆成固定的管理單元,之后將SSD上的空間映射到多塊HDD層的設備上(邏輯映射or物理映射)。在訪問流程上,與CPU訪問高速緩存和主存的流程類似,首先嘗試訪問Cache層,如果出現CacheMiss,則會訪問HDD層,同時根據數據局部性原理,這部分數據將回寫到Cache層。如果Cache空間已滿,會通過LRU策略替換部分數據。
FlashCache/OpenCAS提供了四種緩存策略:WriteThrough、WriteBack、WriteAround、WriteOnly。由于第四種不做讀緩存,這里我們只看前三種。
寫入:
-
WriteThrough:數據寫操作在寫入SSD的同時會寫入到后端存儲。
-
WriteBack:數據寫操作僅寫入SSD即返回,由緩存策略flush到后臺存儲。
-
WriteAround:數據寫入操作直接寫入后端存儲,同時SSD對應的緩存會失效。
讀取:
-
WriteThrough/WriteBack/WriteAround:首先讀取SSD,命中不了的將再次讀取后端存儲,并數據會被刷入到SSD緩存中。
更多詳細實現細節,極大可參見這二者的官方文檔:
-
FlashCache
-
OpenCAS
備選方案二:Kafka應用內部實現
上文提到的第一類備選方案中,核心的理論依據“數據局部性”原理與Kafka的讀寫特性并不能完全吻合,“數據回刷”這一特性依然會引入緩存空間污染問題。同時,上述架構基于LRU的淘汰策略也與Kafka讀寫特性存在矛盾,在多Consumer并發消費時,LRU淘汰策略可能會誤淘汰掉一些近實時數據,導致實時消費作業出現性能抖動。
可見,備選方案一并不能完全解決當前Kafka的痛點,需要從應用內部進行改造。整體設計思路如下,將數據按照時間維度分布在不同的設備中,近實時部分的數據緩存在SSD中,這樣當出現PageCache競爭時,實時消費作業從SSD中讀取數據,保證實時作業不會受到延遲消費作業影響。下圖展示了基于應用層實現的架構處理讀請求的流程:
當消費請求到達Kafka Broker時,Kafka Broker直接根據其維護的消息偏移量(Offset)和設備的關系從對應的設備中獲取數據并返回,并且在讀請求中并不會將HDD中讀取的數據回刷到SSD,防止出現緩存污染。同時訪問路徑明確,不會由于Cache Miss而產生的額外訪問開銷。
下表對不同候選方案進行了更加詳細的對比:
最終,結合與Kafka讀寫特性的匹配度,整體工作量等因素綜合考慮,我們采用Kafka應用層實現這一方案,因為該方案更貼近Kafka本身讀寫特性,能更加徹底地解決Kafka的痛點。
新架構設計
概述
根據上文對Kafka讀寫特性的分析,我們給出應用層基于SSD的緩存架構的設計目標:
-
數據按時間維度分布在不同的設備上,近實時數據分布在SSD上,隨時間的推移淘汰到HDD上。
-
Leader分區中所有數據均寫入SSD中。
-
從HDD中讀取的數據不回刷到SSD中。
依據上述目標,我們給出應用層基于SSD的Kafka緩存架構實現:
Kafka中一個Partition由若干LogSegment構成,每個LogSegment包含兩個索引文件以及日志消息文件。一個Partition的若干LogSegment按Offset(相對時間)維度有序排列。
根據上一小節的設計思路,我們首先將不同的LogSegment標記為不同的狀態,如圖所示(圖中上半部分)按照時間維度分為OnlyCache、Cached以及WithoutCache三種常駐狀態。而三種狀態的轉換以及新架構對讀寫操作的處理如圖中下半部分所示,其中標記為OnlyCached狀態的LogSegment只存儲在SSD上,后臺線程會定期將Inactive(沒有寫流量)的LogSegment同步到SSD上,完成同步的LogSegment被標記為Cached狀態。
最后,后臺線程將會定期檢測SSD上的使用空間,當空間達到閾值時,后臺線程將會按照時間維度將距離現在最久的LogSegment從SSD中移除,這部分LogSegment會被標記為WithoutCache狀態。
對于寫請求而言,寫入請求依然首先將數據寫入到PageCache中,滿足閾值條件后將會刷入SSD。對于讀請求(當PageCache未獲取到數據時),如果讀取的offset對應的LogSegment的狀態為Cached或OnlyCache,則數據從SSD返回(圖中LC2-LC1以及RC1),如果狀態為WithoutCache,則從HDD返回(圖中LC1)。
對于Follower副本的數據同步,可根據Topic對延遲以及穩定性的要求,通過配置決定寫入到SSD還是HDD。
關鍵優化點
上文介紹了基于SSD的Kafka應用層緩存架構的設計概要以及核心設計思路,包括讀寫流程、內部狀態管理以及新增后臺線程功能等。本小節將介紹該方案的關鍵優化點,這些優化點均與服務的性能息息相關。主要包括LogSegment同步以及Append刷盤策略優化,下面將分別進行介紹。
LogSegment同步
LogSegment同步是指將SSD上的數據同步到HDD上的過程,該機制在設計時主要有以下兩個關鍵點:
同步的方式:同步方式決定了HDD上對SSD數據的可見時效性,從而會影響故障恢復以及LogSegment清理的及時性。
同步限速:LogSegment同步過程中通過限速機制來防止同步過程中對正常讀寫請求造成影響
同步方式
關于LogSegment的同步方式,我們給出了三種備選方案,下表列舉了三種方案的介紹以及各自的優缺點:
最終,我們對一致性維護代價、實現復雜度等因素綜合考慮,選擇了后臺同步Inactive的LogSegment的方式。
同步限速
LogSegment同步行為本質上是設備間的數據傳輸,會同時在兩個設備上產生額外的讀寫流量,占用對應設備的讀寫帶寬。同時,由于我們選擇了同步Inactive部分的數據,需要進行整段的同步。如果在同步過程中不加以限制會對服務整體延遲造成較大的影響,主要表現在下面兩個方面:
-
從單盤性能角度,由于SSD的性能遠高于HDD,因此在數據傳輸時,HDD寫入帶寬會被寫滿,此時其他的讀寫請求會出現毛刺,如果此時有延遲消費從HDD上讀取數據或者Follower正在同步數據到HDD上,會造成服務抖動。
-
從單機部署的角度,單機會部署2塊SSD與10塊HDD,因此在同步過程中,1塊SSD需要承受5塊HDD的寫入量,因此SSD同樣會在同步過程中出現性能毛刺,影響正常的請求響應延遲。
基于上述兩點,我們需要在LogSegment同步過程中增加限速機制,總體的限速原則為在不影響正常讀寫請求延遲的情況下盡可能快速地進行同步。因為同步速度過慢會導致SSD數據無法被及時清理而最終被寫滿。同時為了可以靈活調整,該配置也被設置為單Broker粒度的配置參數。
日志追加刷盤策略優化
除了同步問題,數據寫入過程中的刷盤機制同樣影響服務的讀寫延遲。該機制的設計不僅會影響新架構的性能,對原生Kafka同樣會產生影響。
下圖展示了單次寫入請求的處理流程:
在Produce請求處理流程中,首先根據當前LogSegment的位置與請求中的數據信息確定是否需要滾動日志段,隨后將請求中的數據寫入到PageCache中,更新LEO以及統計信息,最后根據統計信息確定是否需要觸發刷盤操作,如果需要則通過fileChannel.force強制刷盤,否則請求直接返回。
在整個流程中,除日志滾動與刷盤操作外,其他操作均為內存操作,不會帶來性能問題。日志滾動涉及文件系統的操作,目前,Kafka中提供了日志滾動的擾動參數,防止多個Segment同時觸發滾動操作給文件系統帶來壓力。針對日志刷盤操作,目前Kafka給出的機制是以固定消息條數觸發強制刷盤(目前線上為50000),該機制只能保證在入流量一定時,消息會以相同的頻率刷盤,但無法限制每次刷入磁盤的數據量,對磁盤的負載無法提供有效的限制。
如下圖所示,為某磁盤在午高峰時間段write_bytes的瞬時值,在午高峰時間段,由于寫入流量的上升,在刷盤過程中會產生大量的毛刺,而毛刺的值幾乎接近磁盤最大的寫入帶寬,這會使讀寫請求延遲發生抖動。
針對該問題,我們修改了刷盤的機制,將原本的按條數限制修改為按實際刷盤的速率限制,對于單個Segment,刷盤速率限制為2MB/s。該值考慮了線上實際的平均消息大小,如果設置過小,對于單條消息較大的Topic會過于頻繁的進行刷新,在流量較高時反而會加重平均延遲。目前該機制已在線上小范圍灰度,右圖展示了灰度后同時間段對應的write_bytes指標,可以看到相比左圖,數據刷盤速率較灰度前明顯平滑,最高速率僅為40MB/s左右。
對于SSD新緩存架構,同樣存在上述問題,因此在新架構中,在刷盤操作中同樣對刷盤速率進行了限制。
方案測試
測試目標
-
驗證基于應用層的SSD緩存架構能夠避免實時作業受到延遲作業的影響。
-
驗證相比基于操作系統內核層實現的緩存層架構,基于應用層的SSD架構在不同流量下讀寫延遲更低。
測試場景描述
-
構建4個集群:新架構集群、普通HDD集群、FlashCache集群、OpenCAS集群。
-
每個集群3個節點。
-
固定寫入流量,比較讀、寫耗時。
-
延遲消費設置:只消費相對當前時間10~150分鐘的數據(超過PageCache的承載區域,不超過SSD的承載區域)。
測試內容及重點關注指標
-
Case1: 僅有延遲消費時,觀察集群的生產和消費性能。
-
重點關注的指標:寫耗時、讀耗時,通過這2個指標體現出讀寫延遲。
-
命中率指標:HDD讀取量、HDD讀取占比(HDD讀取量/讀取總量)、SSD讀取命中率,通過這3個指標體現出SSD緩存的命中率。
-
Case2: 存在延遲消費時,觀察實時消費的性能。
-
重點指標:實時作業的SLA(服務質量)的5個不同時間區域的占比情況。
測試結果
從單Broker請求延遲角度看:
在刷盤機制優化前,SSD新緩存架構在所有場景下,較其他方案都具有明顯優勢。
刷盤機制優化后,其余方案在延遲上服務質量有提升,在較小流量下由于Flush機制的優化,新架構與其他方案的優勢變小。當單節點寫入流量較大時(大于170MB)優勢明顯。
從延遲作業對實時作業的影響方面看:
新緩存架構在測試所涉及的所有場景中,延遲作業都不會對實時作業產生影響,符合預期。
總結與未來展望
Kafka在美團數據平臺承擔統一的數據緩存和分發的角色,針對目前由于PageCache互相污染、進而引發PageCache競爭導致實時作業被延遲作業影響的痛點,我們基于SSD自研了Kafka的應用層緩存架構。本文主要介紹Kafka新架構的設計思路以及與其他開源方案的對比。與普通集群相比,新緩存架構具備非常明顯的優勢:
降低讀寫耗時:比起普通集群,新架構集群讀寫耗時降低80%。
實時消費不受延遲消費的影響:比起普通集群,新架構集群實時讀寫性能穩定,不受延時消費的影響。
目前,這套緩存架構優已經驗證完成,正在灰度階段,未來也優先部署到高優集群。其中涉及的代碼也將提交給Kafka社區,作為對社區的回饋,也歡迎大家跟我們一起交流。
總結
以上是生活随笔為你收集整理的基于SSD的Kafka应用层缓存架构设计与实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 建设研发运营一体化平台
- 下一篇: Flink 助力美团数仓增量生产