百度信息流和搜索業(yè)務中的KV存儲實踐
時間:2021-10-12 15:12:07
手機看文章
掃描二維碼
隨時隨地手機看文章
[導讀]導讀:近年來,云原生化、全用戶態(tài)、軟硬協(xié)同等技術對KV存儲服務產生了巨大的影響,上述技術在極大提升了服務的性能和降低服務成本的同時,也對系統(tǒng)的架構和實現(xiàn)提出了新的要求。百度在信息流和搜索業(yè)務中大量使用了KV存儲服務,服務每天響應近千億次各類訪問請求,如何運用上述技術提升系統(tǒng)的性能...
導讀:近年來,云原生化、全用戶態(tài)、軟硬協(xié)同等技術對KV存儲服務產生了巨大的影響,上述技術在極大提升了服務的性能和降低服務成本的同時,也對系統(tǒng)的架構和實現(xiàn)提出了新的要求。百度在信息流和搜索業(yè)務中大量使用了KV存儲服務,服務每天響應近千億次各類訪問請求,如何運用上述技術提升系統(tǒng)的性能、穩(wěn)定性和運維人效是我們重點考慮的問題。本文通過介紹我們應用上述技術打造高性能KV存儲系統(tǒng)的實踐過程,為大家分享了我們在單機性能優(yōu)化,大規(guī)模集群設計、管理等方面的思路和實踐經驗。
全文7854字,預計閱讀時間21分鐘。
自2016年起,百度進入『搜索 信息流』雙引擎驅動構建內容生態(tài)的“信息分發(fā)2.0時代”,搜索、推薦的內容也不再局限于網頁,而是引入越來越多的視頻、圖片、音頻等多媒體資源。KV存儲作為在搜索和推薦中臺中被廣泛使用的在線存儲服務,更是受到了存儲規(guī)模和訪問流量的雙重考驗。
至2018年初,我們投入各類KV存儲服務的服務器數量便超過萬臺,數據規(guī)模超過百PB,承接了每天近千億次的各類訪問請求。集群規(guī)模的增長除了資源成本的提升,還加劇了運維管理的難度。作為有狀態(tài)服務,集群的故障機處理、服務器升級、資源擴縮容都需要專人跟進,運維人力隨集群規(guī)模呈正比增長。彼時又逢推薦業(yè)務完成了微服務化改造,業(yè)務資源交付和上線都能當天完成,存儲資源動輒周級的交付能力也成了業(yè)務上線效率的瓶頸。
這些都促使我們對原來的系統(tǒng)架構進行徹底升級,通過提升單機引擎性能和云原生化有效降低資源成本和運維人力成本。同時我們還要滿足業(yè)務對服務的敏捷性要求,通過云基礎設施提供的資源編排能力,使系統(tǒng)具備小時級服務交付能力。
一、問題與挑戰(zhàn)
1)性能挑戰(zhàn)
單機引擎性能是KV系統(tǒng)的關鍵指標,一般我們通過讀寫性能(OPS、延時(latency))和空間利用率來衡量引擎的性能,由于當前引擎架構和存儲設備硬件性能的制約,我們在引擎中往往只能選擇讀性能、寫性能和空間利用率中某個方向進行重點優(yōu)化,比如犧牲空間利用率提升寫吞吐,或者犧牲寫吞吐、提升空間利用率和讀吞吐。這類優(yōu)化對于業(yè)務單一的場景比較有效,之前我們的系統(tǒng)就針對大業(yè)務場景進行逐一調參優(yōu)化,針對其他業(yè)務則使用讀寫性能和空間利用率均衡的『均衡型』引擎接入。
但是百度的信息流和搜索業(yè)務規(guī)模都非常龐大、業(yè)務場景極其復雜,我們接入的數千個業(yè)務中有每天更新PB級數據的業(yè)務,也有讀寫比超過100:1的業(yè)務,還有要求強一致性、存儲規(guī)模數十PB的業(yè)務,不少業(yè)務還會體現(xiàn)出潮汐特性,全天流量都集中在某個特定時段。
因此我們通過引擎優(yōu)化,既要解決如何在降低讀寫放大的同時,盡可能平衡空間放大的問題;又要在引擎內實現(xiàn)自適應機制,解決業(yè)務充分混布場景下,吞吐模式多變的問題。
2)云原生化挑戰(zhàn)
云原生架構的主要價值在于效率的提升,包括資源利用效率和研發(fā)效率兩個方面。百度信息流和搜索業(yè)務對其業(yè)務模塊制定了統(tǒng)一的云原生化標準:- 微服務化:每個服務粒度應該在限定的范圍內。
- 容器化封裝:一個服務的部署,應該只依賴基礎架構以及本容器內的組件,而不應該依賴其他業(yè)務服務。
- 動態(tài)管理:每個服務應該可以動態(tài)調整部署,而不影響自身對外承諾的SLA。
KV服務在對齊上述標準的過程中,主要難點在于容器化改造和動態(tài)管理兩個方面。容器化改造方面,單機時代的KV服務以用滿整機資源為目標,對內存資源和存儲介質IO的使用往往不加任何限制。引擎的容器化改造,要求我們精細化控制對上述資源的使用:
- 內存資源:存儲引擎除了顯式使用系統(tǒng)內存,更多的是對page cache的使用,文件系統(tǒng)中諸如Buffered I/O和文件預讀都會產生page cache。我們需要在引擎中對其加以控制,避免超過容器配額觸發(fā)硬限。
- 存儲介質I/O:KV服務的主要介質是SSD,我們不少業(yè)務也需要使用SSD提升讀寫性能,這些業(yè)務本身往往只需要不到100GB,因此為了提升SSD的使用率,KV服務需要和這些業(yè)務進行混布。這也要求我們不但能有效利用SSD I/O,還要能對I/O加以控制,避免影響混布業(yè)務。
動態(tài)管理則要求業(yè)務具有一定的容錯能力和彈性伸縮能力,由于KV是典型的有狀態(tài)服務,兼具了數據持久化、多分片、多副本等特點,我們在動態(tài)管理中需要關注:
- 服務可用性:與無狀態(tài)服務不同,多分片服務不能看服務的整體可用性,而要確保每個分片都可用。比如一個服務有10個分片,每個分片有3個容器,服務整體可用度為90%。從無狀態(tài)服務視角2個容器故障不會影響服務可用性,但是從KV服務視角,如果這兩個容器正好服務同一個分片的兩個數據副本,那么該服務就會出現(xiàn)數據不可用問題。因此服務在持續(xù)部署時,需要確保每個數據分片的可用性。
- 數據可靠性:云原生化后為了提升資源利用率,在線數據遷移和動態(tài)伸縮頻率將遠高于物理機時代,數據的動態(tài)伸縮過程需要對業(yè)務透明,也不能出現(xiàn)數據丟失或一致性問題。
- 管理效率:確保管理服務能即時響應管理操作,對系統(tǒng)的穩(wěn)定性起著關鍵作用。隨著集群規(guī)模的增加,并發(fā)的管理操作數量也會增加,如果響應操作的時間也逐漸增加,最終將導致系統(tǒng)雪崩,因此我們需要系統(tǒng)響應管理操作的能力也能水平伸縮。
- 部署效率:KV服務的部署包括部署應用和完成數據遷移,因此我們不但要在功能上做到可遷移,還要確保數據遷移的效率。比如一個有100個實例的服務,如果遷移一個實例要12個小時,且每輪只允許遷移1個實例,遇到內核升級、操作系統(tǒng)升級等需要重啟的操作,全服務遷移一輪就需要1個半月,這樣的效率還不如人工操作。這樣的服務就不能算支持動態(tài)管理,因為沒有在線操作可以容忍這樣低的遷移效率。
3)滿足業(yè)務的特化需求
之前也提到百度的信息流和搜索業(yè)務規(guī)模都非常龐大,一些大業(yè)務根據自身場景已經做了單機引擎的特化,有些特化還涉及修改linux內核,通用引擎在性能上無法超越這些特化引擎,這就要求能從業(yè)務中提取共性,同時允許其保留特性,使業(yè)務團隊能在資源利用效率和研發(fā)效率兩個方面都獲得收益。為此我們提出了UNDB - NoSQL聯(lián)合存儲系統(tǒng)(United NoSQL Database)的概念,兼顧統(tǒng)一通用能力與保持業(yè)務特性:
- 統(tǒng)一框架:使用統(tǒng)一的云原生存儲框架,打平各系統(tǒng)的運維、集群管理差異,同時打破原有的資源屏障。
- 通用接口:各KV服務剝離業(yè)務協(xié)議,對齊基本KV接口協(xié)議,對用戶提供基于基本KV接口封裝的SDK,使業(yè)務可以在各服務間平滑遷移,降低學習成本。
- 通用引擎:自研高性能通用KV引擎,便于其他NoSQL服務在此之上實現(xiàn)高性能的存儲服務。
- 分層可插拔架構:通過可插拔的接口層、數據模型層、數據同步層、引擎層設計,使接入系統(tǒng)能快速實現(xiàn)業(yè)務特性化差異。
二、引擎優(yōu)化
引擎是KV系統(tǒng)的核心組件,鑒于RocksDB在開源社區(qū)和工業(yè)界的廣泛應用,我們一開始便直接使用RocksDB作為單機引擎?;贚SM-Tree實現(xiàn),RocksDB在HDD介質上有良好的性能,但在SSD介質上的性能表現(xiàn)卻并不出眾,主要問題在于:
- RocksDB在設計實現(xiàn)中,為了避免隨機I/O,增加了大量順序I/O開銷(讀放大、寫放大),而SSD介質的隨機I/O尤其是隨機讀性能和順序I/O差距不大,因此針對HDD介質的優(yōu)化在SSD介質中反而造成了讀寫帶寬浪費。
- 上述額外的I/O開銷所產生的高寫放大,還增加了SSD介質的壽命損耗,在高吞吐環(huán)境中,新盤的平均使用壽命不足2年。
- LSM-Tree結構對業(yè)務數據長度也十分敏感,由于每層SST文件大小是固定的,數據長度越大,越容易觸發(fā)Compaction,從而造成寫放大;同時數據長度越大,每層SST文件能記錄的數據條目數就越少,讀請求向下層訪問概率就越高,從而造成了讀放大。
我們業(yè)務場景中的value一般在KB ~ 百KB級別,為了降低LSM-Tree的寫放大,我們在RocksDB基礎上實現(xiàn)了Key-Value分離的單機存儲引擎,如下圖左側引擎結構所示:
圖1:普通引擎與基于OpenChannel SSD的軟硬協(xié)同引擎的架構對比
- 在RocksDB中存儲Key和Value的地址索引(BlockID Offset),RocksDB的Compaction只影響到索引,不會引起Value的變動。
- Value單獨持久化,我們稱之Data Block文件,Data Block文件單獨進行Compaction。
為了進一部提升引擎的I/O效率,我們又對Compaction策略和壓縮方式進行了優(yōu)化:
- 自適應Compaction機制:綜合引擎I/O和剩余存儲空間,調節(jié)對Block文件空洞率的選擇閾值,實現(xiàn)流量規(guī)避、動態(tài)調節(jié)空間放大能力。
- 冷熱分層:減少冷熱混合導致的不必要Compaction,并選擇流量空閑時段對冷數據進行Compaction,降低觸發(fā)SSD靜態(tài)磨損均衡頻率。
- 全局壓縮:在Block File粒度支持了zstd-dict壓縮方式,進一步提升了Block文件的壓縮率。
此外,我們在引擎中為同步框架封裝了Log View,實現(xiàn)數據同步與引擎復用,WAL降低了數據同步造成的寫放大。通過上述優(yōu)化,在軟件層面,我們在空間放大 <1.6x的情況下,將寫放大控制到了 < 1.5x。在業(yè)務持續(xù)以30MB/s更新數據的場景下,單盤壽命由之前的半年內提升至3年左右。
但是,SSD的寫放大并不限于軟件層,物理特性決定其不支持覆蓋寫,只能先擦除舊數據再寫入新數據,大部分SSD按4KB(Page)寫入、按256KB ~ 4MB(Block)擦除數據。SSD在擦除一個Block時,需要遷移Block中仍然有效的Page,這個遷移動作導致了SSD硬件層的寫放大,硬件層寫放大又與SSD剩余空間密切相關,一般情況下,當SSD容量使用達90%時,寫放大會超過3.5x。
細心的同學或許會發(fā)現(xiàn),我們之前在計算SSD壽命時并沒有提到這部分放大,這里其實包含了SSD廠商的優(yōu)化:SSD介質的實際容量單位是GiB(Gibibyte),1GiB = 230bit,提供給用戶的指標則是GB(Gigabyte),1GB = 109 bit,前者比后者多了7.374%的空間,被廠商用作了內部操作空間。加之我們在實際使用時,也會保持磁盤用量不超過80%,因此可以控制寫放大。但是這些策略其實犧牲了SSD的容量。
為了進一步發(fā)掘設備潛能,我們和百度的基礎架構部門合作,基于Open Channel SSD實現(xiàn)了一款軟硬協(xié)同設計的引擎,如上圖右側引擎結構所示與傳統(tǒng)用法相比:
- 實現(xiàn)了全用戶態(tài)I/O操作,降低了引擎讀寫延遲;?
- 引擎直接管理Flash物理地址,避免了文件系統(tǒng)、LBA、PBA三層映射造成的性能損失和空間浪費;
- 將FTL中Wear Leveling、GC、PLP、Error Handling上移至引擎,和KV原有的Compaction,Crash Recovery邏輯合并,合并了軟、硬兩層操作空間。
軟硬協(xié)同引擎在性能上超過軟件引擎 > 30%,軟硬整體放大率 < 1.1x。
圖2:引擎性能對比,依次為:數據加載性能、讀寫吞吐(讀寫1:1)、99分位寫延時、99分位讀延時
上圖是我們實現(xiàn)的KV分離引擎(Quantum)、軟硬協(xié)同引擎(kvnvme)和開源Rocksdb在數據加載、隨機讀寫場景下的性能對比。測試中我們選用:NVME 1TB SSD(硬件指標:4KB隨機寫7萬IOPS,隨機讀46.5萬IOPS)。數據集選用:1KB、4KB、16KB和32KB共4組,每組數據集都隨機預構建320GB初始數據,再采用齊夫分布(Zipf)進行讀寫測試,讀寫測試時保持讀寫比為1:1。
Value Size | key數量 | 原始數據集 |
1KB | 3.2億 | 320GB |
4KB | 8千萬 | 320GB |
16KB | 2千萬 | 320GB |
32KB | 4千萬 | 320GB |
從測試結果可以發(fā)現(xiàn):
- Value越大,KV分離引擎和軟硬協(xié)同引擎的讀寫優(yōu)勢就越為明顯,在32KB時軟硬協(xié)同引擎的吞吐是RocksDB的近5x。
- 軟硬協(xié)同引擎在讀寫延時控制上,尤其是寫延時控制上也明顯優(yōu)于其他引擎。
三、云原生實踐
上節(jié)中我們通過引擎優(yōu)化重構解決了業(yè)務混布性能和容器化問題,這節(jié)將介紹一下我們是如何解決動態(tài)管理問題。UNDB服務整體框架如下圖所示:
圖3:UNDB系統(tǒng)框架
架構上我們將服務分成了Operator(數據調度)、控制面和數據面三部分:
- Operator:負責向PaaS傳遞控制面和數據面中所有容器的狀態(tài)信息和進行用戶數據調度
- 控制面:包括元信息服務、集群控制服務、接入管理服務、路由服務和用戶數據中心,負責管理一個IDC(Internet Data Center)中的所有存儲集群(Store Clusters)和用戶發(fā)現(xiàn)、接入存儲集群的路由映射關系,不同IDC間部署不同的控制面服務。
- 數據面:IDC中KV服務管理的所有存儲集群的集合。存儲集群按存儲設備類型(比如:NVMe SSD、SATA SSD、OpenChannel SSD、HDD、……),存儲機制的容量,引擎類型,以及引擎使用的CPU、內存資源數量進行劃分。
我們在系統(tǒng)設計、實現(xiàn)中主要考慮如何實現(xiàn)數據全局調度能力和海量存儲實例的動態(tài)管理能力。
3.1 數據全局調度能力數據全局調度指:
- 用戶數據可以在數據面中的不同存儲集群間任意遷移、擴縮容。
- 用戶數據可以在不同數據中心間任意遷移、復制。
這種能力的意義在于:
- 當業(yè)務形態(tài)、引擎技術和存儲設備發(fā)生變化時,我們能用對業(yè)務無感的方法將數據遷移到合適的集群中。
- 當部門新建數據中心,業(yè)務新增、切換數據中心,或是數據中心數據恢復時,我們能以最低的運維成本實施數據遷移、恢復。
圖4:Table在UNDB集群間遷移示意
如上圖(圖4)所示,全局調度由Operator發(fā)起,通過控制面協(xié)調數據面中的存儲實例完成操作,其中:
- 元信息服務和集群控制服務,為Operator對用戶數據進行全局調度從底層機制上提供了無損遷移和無損伸縮能力。
- 數據中心:通過分析存儲集群的流量、容量分布和業(yè)務數據的流量、容量增長趨勢,向Operator提供了全局調度方案。
- Operator:基于K8S Operator框架實現(xiàn),通過聲明式原語,引導控制面完成數據遷移。
基于上述能力,我們除了支持即時集群容量均衡和即時業(yè)務容量調整,還實現(xiàn)了周級機房建設、搬遷。
3.2?海量存儲實例的動態(tài)管理能力之前提到我們在動態(tài)管理中需要關注服務可用性、數據可靠性、管理效率和部署效率,上節(jié)中我們通過引擎優(yōu)化實現(xiàn)了小時級完成TB數據的遷移、恢復。這里我們將關注如何在動態(tài)管理中確保服務的可用性、數據可靠性和管理效率。
業(yè)務訪問KV服務的過程可以簡單概括為:
- 業(yè)務通過路由發(fā)現(xiàn)數據所在的存儲節(jié)點。
- 訪問存儲節(jié)點獲取數據。
- 數據和存儲節(jié)點映射關系發(fā)生變更時,通知業(yè)務更新路由。
我們在數據面中:
- 按3-2-1原則確保數據安全。
- 使用Multi-Raft確保數據一致性。
- 采用多地多活確保重要業(yè)務的可用性。
由于數據中心間的元信息不盡相同,尤其是拓撲信息完全不同,且拓撲信息具有極強的時效性,冷備效果并不好,因此對于控制面我們采用了利用數據面,集群控制服務多級兜底的思路,如下圖(圖5)所示:
- 冷備機制:對于低頻更新的元信息,諸如業(yè)務數據屬性、配額實時備份SQL數據庫。
- 反向恢復機制:利用集群控制服務和存儲集群都有拓撲鏡像的特點,支持通過上述服務反向恢復拓撲信息。
- 異變攔截機制:在控制面、數據面及業(yè)務端(Client)攔截異常變更,避免拓撲異常時,服務迅速異常雪崩。
圖5:元信息多級攔截、反向恢復
另外我們還通過獨立的路由服務向業(yè)務屏蔽了元信息服務,業(yè)務間通過多組路由服務進行物理隔離。并通過接入管理服務管理業(yè)務和路由服務間的映射關系,這樣可以有效防止由于某個業(yè)務的異常訪問(比如:連接泄露)影響其他業(yè)務的路由訪問。
在提升管理效率方面,我們主要采用了元信息和集群控制分離的設計,由于元信息服務需要確保節(jié)點間數據一致性,我們的數據修改操作只能在Leader節(jié)點上進行,如果不采用分離設計,所有控制操作只有Leader節(jié)點才能進行,當集群規(guī)模和數量增加后,無法通過水平擴容節(jié)點增加控制面算力,因此我們選用了兩種模塊分離的方法,使控制面具備水平伸縮控制算力的能力以應對超大規(guī)模集群。
四、多模型存儲架構
NoSQL概念發(fā)展至今,業(yè)界已經出現(xiàn)了數百種不同的開源和商業(yè)NoSQL數據庫,當業(yè)務發(fā)展到一定程度,對標準數據庫進行改造,使其更適合業(yè)務模型的需求也變得越來越普遍。因此我們在整合KV系統(tǒng)時,提出了整合通用功能保留業(yè)務特性的設計思路。在這個思路指導下,我們統(tǒng)一了控制面,用于實現(xiàn)統(tǒng)一的運維管理,數據面則分成了3個功能模塊、6個分層向業(yè)務開放了對服務接口、數據模型、存儲模式、以及對同步框架的定制化能力,如下圖所示:
圖6:UNDB多模型存儲架構
- DbProxy模塊:整個架構的第一層,是一個獨立部署的模塊,DBProxy是對所有代理存儲節(jié)點(Store Node)的Proxy服務的總稱,按功能不同還有諸如KvProxy,GraphProxy等,該模塊主要用于:
- 分擔存儲節(jié)點(Store Node)訪問壓力:當業(yè)務存在大量的Fanout讀,或離線任務(map reduce任務)存在大量寫連接時,通過proxy可以減少對存儲節(jié)點的壓力;
- 減少上下游連接開銷:主要用于超大規(guī)模業(yè)務,比如搜索業(yè)務的一張索引表就分布在上萬個實例中,這也意味訪問該表的每個下游都要維護上萬個連接,因此需要通過proxy減少客戶端的連接開銷。
- 存算分離:當前通過NVMe-OF、RDMA技術網絡已經不是系統(tǒng)的主要瓶頸,像圖數據庫這樣的服務,一次請求需要訪問多個存儲節(jié)點才能完成,還需要通過本地緩存構建子圖加速業(yè)務查詢速度,整合在存儲節(jié)點中幾乎無法帶來的性能收益,還容易出現(xiàn)熱點查詢,因此我們也通過Proxy實現(xiàn)存算分離。
- libNode模塊:該模塊承擔了存儲節(jié)點的業(yè)務邏輯、節(jié)點管理和數據同步:
- 分布式管理層:通過CtrlService向業(yè)務提供了一組響應分布式狀態(tài)管理的核心原語,并提供了默認原語實現(xiàn)。
- 接口協(xié)議層