百度信息流和搜索業務中的 KV 存儲實踐

自 2016 年起,百度進入『搜索 + 信息流』雙引擎驅動構建內容生態的 “信息分發 2.0 時代”,搜索、推薦的內容也不再侷限於網頁,而是引入越來越多的視頻、圖片、音頻等多媒體資源。KV 存儲作爲在搜索和推薦中臺中被廣泛使用的在線存儲服務,更是受到了存儲規模和訪問流量的雙重考驗。

至 2018 年初,我們投入各類 KV 存儲服務的服務器數量便超過萬臺,數據規模超過百 PB,承接了每天近千億次的各類訪問請求。集羣規模的增長除了資源成本的提升,還加劇了運維管理的難度。作爲有狀態服務,集羣的故障機處理、服務器升級、資源擴縮容都需要專人跟進,運維人力隨集羣規模呈正比增長。彼時又逢推薦業務完成了微服務化改造,業務資源交付和上線都能當天完成,存儲資源動輒周級的交付能力也成了業務上線效率的瓶頸。

這些都促使我們對原來的系統架構進行徹底升級,通過提升單機引擎性能和雲原生化有效降低資源成本和運維人力成本。同時我們還要滿足業務對服務的敏捷性要求,通過雲基礎設施提供的資源編排能力,使系統具備小時級服務交付能力。

一、問題與挑戰

1)性能挑戰

單機引擎性能是 KV 系統的關鍵指標,一般我們通過讀寫性能(OPS、延時(latency))和空間利用率來衡量引擎的性能,由於當前引擎架構和存儲設備硬件性能的制約,我們在引擎中往往只能選擇讀性能、寫性能和空間利用率中某個方向進行重點優化,比如犧牲空間利用率提升寫吞吐,或者犧牲寫吞吐、提升空間利用率和讀吞吐。

這類優化對於業務單一的場景比較有效,之前我們的系統就針對大業務場景進行逐一調參優化,針對其他業務則使用讀寫性能和空間利用率均衡的『均衡型』引擎接入。

但是百度的信息流和搜索業務規模都非常龐大、業務場景極其複雜,我們接入的數千個業務中有每天更新 PB 級數據的業務,也有讀寫比超過 100:1 的業務,還有要求強一致性、存儲規模數十 PB 的業務,不少業務還會體現出潮汐特性,全天流量都集中在某個特定時段。

因此我們通過引擎優化,既要解決如何在降低讀寫放大的同時,儘可能平衡空間放大的問題;又要在引擎內實現自適應機制,解決業務充分混布場景下,吞吐模式多變的問題。

2)雲原生化挑戰

雲原生架構的主要價值在於效率的提升,包括資源利用效率和研發效率兩個方面。

百度信息流和搜索業務對其業務模塊制定了統一的雲原生化標準:

  1. **微服務化:**每個服務粒度應該在限定的範圍內。

  2. **容器化封裝:**一個服務的部署,應該只依賴基礎架構以及本容器內的組件,而不應該依賴其他業務服務。

  3. **動態管理:**每個服務應該可以動態調整部署,而不影響自身對外承諾的 SLA。

KV 服務在對齊上述標準的過程中,主要難點在於容器化改造和動態管理兩個方面。

容器化改造方面,單機時代的 KV 服務以用滿整機資源爲目標,對內存資源和存儲介質 IO 的使用往往不加任何限制。引擎的容器化改造,要求我們精細化控制對上述資源的使用:

  1. 內存資源:存儲引擎除了顯式使用系統內存,更多的是對 page cache 的使用,文件系統中諸如 Buffered I/O 和文件預讀都會產生 page cache。我們需要在引擎中對其加以控制,避免超過容器配額觸發硬限。

  2. 存儲介質 I/O:KV 服務的主要介質是 SSD,我們不少業務也需要使用 SSD 提升讀寫性能,這些業務本身往往只需要不到 100GB,因此爲了提升 SSD 的使用率,KV 服務需要和這些業務進行混布。這也要求我們不但能有效利用 SSD I/O,還要能對 I/O 加以控制,避免影響混布業務。

動態管理則要求業務具有一定的容錯能力和彈性伸縮能力,由於 KV 是典型的有狀態服務,兼具了數據持久化、多分片、多副本等特點,我們在動態管理中需要關注:

  1. 服務可用性:與無狀態服務不同,多分片服務不能看服務的整體可用性,而要確保每個分片都可用。比如一個服務有 10 個分片,每個分片有 3 個容器,服務整體可用度爲 90%。從無狀態服務視角 2 個容器故障不會影響服務可用性,但是從 KV 服務視角,如果這兩個容器正好服務同一個分片的兩個數據副本,那麼該服務就會出現數據不可用問題。因此服務在持續部署時,需要確保每個數據分片的可用性。

  2. 數據可靠性:雲原生化後爲了提升資源利用率,在線數據遷移和動態伸縮頻率將遠高於物理機時代,數據的動態伸縮過程需要對業務透明,也不能出現數據丟失或一致性問題。

  3. 管理效率:確保管理服務能即時響應管理操作,對系統的穩定性起着關鍵作用。隨着集羣規模的增加,併發的管理操作數量也會增加,如果響應操作的時間也逐漸增加,最終將導致系統雪崩,因此我們需要系統響應管理操作的能力也能水平伸縮。

  4. 部署效率:KV 服務的部署包括部署應用和完成數據遷移,因此我們不但要在功能上做到可遷移,還要確保數據遷移的效率。比如一個有 100 個實例的服務,如果遷移一個實例要 12 個小時,且每輪只允許遷移 1 個實例,遇到內核升級、操作系統升級等需要重啓的操作,全服務遷移一輪就需要 1 個半月,這樣的效率還不如人工操作。這樣的服務就不能算支持動態管理,因爲沒有在線操作可以容忍這樣低的遷移效率。

3)滿足業務的特化需求

之前也提到百度的信息流和搜索業務規模都非常龐大,一些大業務根據自身場景已經做了單機引擎的特化,有些特化還涉及修改 linux 內核,通用引擎在性能上無法超越這些特化引擎,這就要求能從業務中提取共性,同時允許其保留特性,使業務團隊能在資源利用效率和研發效率兩個方面都獲得收益

爲此我們提出了 UNDB - NoSQL 聯合存儲系統(United NoSQL Database)的概念,兼顧統一通用能力與保持業務特性:

二、引擎優化

引擎是 KV 系統的核心組件,鑑於 RocksDB 在開源社區和工業界的廣泛應用,我們一開始便直接使用 RocksDB 作爲單機引擎。基於 LSM-Tree 實現,RocksDB 在 HDD 介質上有良好的性能,但在 SSD 介質上的性能表現卻並不出衆,主要問題在於:

我們業務場景中的 value 一般在 KB ~ 百 KB 級別,爲了降低 LSM-Tree 的寫放大,我們在 RocksDB 基礎上實現了 Key-Value 分離的單機存儲引擎,如下圖左側引擎結構所示:

圖 1:普通引擎與基於 OpenChannel SSD 的軟硬協同引擎的架構對比

爲了進一部提升引擎的 I/O 效率,我們又對 Compaction 策略和壓縮方式進行了優化:

此外,我們在引擎中爲同步框架封裝了 Log View,實現數據同步與引擎複用,WAL 降低了數據同步造成的寫放大。

通過上述優化,在軟件層面,我們在空間放大 <1.6x 的情況下,將寫放大控制到了 < 1.5x。在業務持續以 30MB/s 更新數據的場景下,單盤壽命由之前的半年內提升至 3 年左右。

但是,SSD 的寫放大並不限於軟件層,物理特性決定其不支持覆蓋寫,只能先擦除舊數據再寫入新數據,大部分 SSD 按 4KB(Page)寫入、按 256KB ~ 4MB(Block)擦除數據。SSD 在擦除一個 Block 時,需要遷移 Block 中仍然有效的 Page,這個遷移動作導致了 SSD 硬件層的寫放大,硬件層寫放大又與 SSD 剩餘空間密切相關,一般情況下,當 SSD 容量使用達 90% 時,寫放大會超過 3.5x。

細心的同學或許會發現,我們之前在計算 SSD 壽命時並沒有提到這部分放大,這裏其實包含了 SSD 廠商的優化:SSD 介質的實際容量單位是 GiB(Gibibyte),1GiB = 230bit,提供給用戶的指標則是 GB(Gigabyte),1GB = 109 bit,前者比後者多了 7.374% 的空間,被廠商用作了內部操作空間。加之我們在實際使用時,也會保持磁盤用量不超過 80%,因此可以控制寫放大。但是這些策略其實犧牲了 SSD 的容量。

爲了進一步發掘設備潛能,我們和百度的基礎架構部門合作,基於 Open Channel SSD 實現了一款軟硬協同設計的引擎,如上圖右側引擎結構所示與傳統用法相比:

軟硬協同引擎在性能上超過軟件引擎 > 30%,軟硬整體放大率 < 1.1x。

圖 2:引擎性能對比,依次爲:數據加載性能、讀寫吞吐(讀寫 1:1)、99 分位寫延時、99 分位讀延時

上圖是我們實現的 KV 分離引擎(Quantum)、軟硬協同引擎(kvnvme)和開源 Rocksdb 在數據加載、隨機讀寫場景下的性能對比。

測試中我們選用:NVME 1TB SSD(硬件指標:4KB 隨機寫 7 萬 IOPS,隨機讀 46.5 萬 IOPS)。數據集選用:1KB、4KB、16KB 和 32KB 共 4 組,每組數據集都隨機預構建 320GB 初始數據,再採用齊夫分佈(Zipf)進行讀寫測試,讀寫測試時保持讀寫比爲 1:1。

2mx5ap

從測試結果可以發現:

三、雲原生實踐

上節中我們通過引擎優化重構解決了業務混布性能和容器化問題,這節將介紹一下我們是如何解決動態管理問題。

UNDB 服務整體框架如下圖所示:

圖 3:UNDB 系統框架

架構上我們將服務分成了 Operator(數據調度)、控制面和數據面三部分:

我們在系統設計、實現中主要考慮如何實現數據全局調度能力和海量存儲實例的動態管理能力。

3.1 數據全局調度能力

數據全局調度指:

這種能力的意義在於:

圖 4:Table 在 UNDB 集羣間遷移示意

如上圖(圖 4)所示,全局調度由 Operator 發起,通過控制面協調數據面中的存儲實例完成操作,其中:

基於上述能力,我們除了支持即時集羣容量均衡和即時業務容量調整,還實現了周級機房建設、搬遷。

3.2 海量存儲實例的動態管理能力

之前提到我們在動態管理中需要關注服務可用性、數據可靠性、管理效率和部署效率,上節中我們通過引擎優化實現了小時級完成 TB 數據的遷移、恢復。這裏我們將關注如何在動態管理中確保服務的可用性、數據可靠性和管理效率。

業務訪問 KV 服務的過程可以簡單概括爲:

  1. 業務通過路由發現數據所在的存儲節點。

  2. 訪問存儲節點獲取數據。

  3. 數據和存儲節點映射關係發生變更時,通知業務更新路由。

我們在數據面中:

由於數據中心間的元信息不盡相同,尤其是拓撲信息完全不同,且拓撲信息具有極強的時效性,冷備效果並不好,因此對於控制面我們採用了利用數據面,集羣控制服務多級兜底的思路,如下圖(圖 5)所示:

圖 5:元信息多級攔截、反向恢復

另外我們還通過獨立的路由服務向業務屏蔽了元信息服務,業務間通過多組路由服務進行物理隔離。並通過接入管理服務管理業務和路由服務間的映射關係,這樣可以有效防止由於某個業務的異常訪問(比如:連接泄露)影響其他業務的路由訪問。

在提升管理效率方面,我們主要採用了元信息和集羣控制分離的設計,由於元信息服務需要確保節點間數據一致性,我們的數據修改操作只能在 Leader 節點上進行,如果不採用分離設計,所有控制操作只有 Leader 節點才能進行,當集羣規模和數量增加後,無法通過水平擴容節點增加控制面算力,因此我們選用了兩種模塊分離的方法,使控制面具備水平伸縮控制算力的能力以應對超大規模集羣。

四、多模型存儲架構

NoSQL 概念發展至今,業界已經出現了數百種不同的開源和商業 NoSQL 數據庫,當業務發展到一定程度,對標準數據庫進行改造,使其更適合業務模型的需求也變得越來越普遍。因此我們在整合 KV 系統時,提出了整合通用功能保留業務特性的設計思路。在這個思路指導下,我們統一了控制面,用於實現統一的運維管理,數據面則分成了 3 個功能模塊、6 個分層向業務開放了對服務接口、數據模型、存儲模式、以及對同步框架的定製化能力,如下圖所示:

圖 6:UNDB 多模型存儲架構

  1. **分擔存儲節點(Store Node)訪問壓力:**當業務存在大量的 Fanout 讀,或離線任務(map reduce 任務)存在大量寫連接時,通過 proxy 可以減少對存儲節點的壓力;

  2. **減少上下游連接開銷:**主要用於超大規模業務,比如搜索業務的一張索引表就分佈在上萬個實例中,這也意味訪問該表的每個下游都要維護上萬個連接,因此需要通過 proxy 減少客戶端的連接開銷。

  3. **存算分離:**當前通過 NVMe-OF、RDMA 技術網絡已經不是系統的主要瓶頸,像圖數據庫這樣的服務,一次請求需要訪問多個存儲節點才能完成,還需要通過本地緩存構建子圖加速業務查詢速度,整合在存儲節點中幾乎無法帶來的性能收益,還容易出現熱點查詢,因此我們也通過 Proxy 實現存算分離。

  1. **分佈式管理層:**通過 CtrlService 向業務提供了一組響應分佈式狀態管理的核心原語,並提供了默認原語實現。

  2. **接口協議層 & 數據模型層:**提供 KvService 作爲所有服務共同遵守的最簡 KV 協議,開放服務註冊和模型層(Module)供業務組織管理自己的定製化服務接口以及數據結構。爲了能讓模型使用適應不同引擎,我們統一 KV 接口作爲模型層的序列化反序列化接口。

  3. 同步框架層:不同業務對服務的可用性、數據一致性的要求並不相同,比如用戶模型關注可用性和吞吐能力,內容模型則關注讀寫延時和數據的強一致性。我們也針對業務的不同需求提供了最終一致性(高可用、高吞吐)、順序一致(braft 實現的一致性,介於最終一致和強一致性之間)和線性一致(強一致)三個層級的一致性保證,其中線性一致是我們在 braft 基礎上,通過 ReadIndex 和 LeaseRead 算法滿足的一致性語義。

五、總結

UNDB 系統自落地以來,已經覆蓋了百度信息流和搜索主要業務場景,涉及集羣規模數十萬實例,每天承接了超過萬億次的各類訪問流量,成本相較原先降低近 50%。同時系統還保持着月級頻率的全集羣業務無感更新。在運維方面則實現了高度自動化,集羣無專職運維,全部由研發團隊自主管理。目前團隊還在打造多模型數據庫、單機性能優化、存儲架構優化等方向持續努力,力求使 UNDB 具備更完善的業務生態。

參考閱讀:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/cXg1J7nUHjkO2srYOpK99w