『購物車升級』是今年雙十一的重要體驗提升項目,體現瞭大淘寶技術人“用技術突破消費者和商傢體驗天花板”的態度。這是一種敢於不斷重新自我審視,然後做出更好選擇的存在主義態度。
「體驗提升」通常表現在以前需要降級的功能不降級,以前不夠實時的數據逐漸實時,以前調用鏈路的長耗時逐步降低——這通常是龐大的系統工程,需要涉及到的每一個環節(客戶端、應用、中間件、數據庫、網絡、容器、系統內核等組件)提供最強的產品能力來支撐。到數據庫這個環節,挑戰通常是訪問量和連接數暴漲的前提下,仍要保持延時穩定和成本可控。
低延時是這些挑戰裡面的核心,是內存數據庫 Tair 提供的服務本質。在高吞吐、大連接數、熱點請求、異常流量、復雜計算邏輯、彈性伸縮這些真實場景下保持穩定的低延時,是 Tair 能夠在低延時場景被選擇的關鍵因素。 作為今年支撐購物車升級的核心產品,Tair 使用的內存/SCM 混合存儲、水平擴展分區無鎖和 SQL 引擎等技術是在支撐十四次雙十一的過程中逐漸打磨完善的,在這些技術的基礎上 Tair 使用 Fast Path 執行 SQL 、執行器模式及算子適配等技術持續進行服務端優化。本文將圍繞 Tair 低延時這一本質特征在構建時所采用的系統手段,藉此提出更多問題來探討,進一步打造更強大的內存數據庫。
Tair 在低延時場景下的服務能力
存儲引擎的性能是數據庫低延時的基石。從功能上看,我們會關心存儲引擎提供的並發(線程安全、無鎖)、事務處理(MVCC、沖突識別、死鎖識別、操作原子性)、快照(標記數據集狀態、降低延遲、減少容量膨脹)等能力。這裡把並發放到後面論述,先看單次請求的延時,主要涉及到存儲介質和數據索引。
作為內存數據庫,Tair 在絕大部分場景使用單次訪問延時在 ns 級別的內存 / SCM 作為主要的存儲介質。以 Table 存儲為例,服務端的常駐數據大概可以分為 Tuple(可以認為是表裡面的某一行)、String Pool、Index 三部分,這些數據都是存放在內存 / SCM 中,隻有快照和日志會存放在磁盤上。
除瞭存儲介質的延時,通常我們還需要關心的是介質的成本。成本一方面是從硬件上,Tair 是率先采用 SCM 的雲產品,相對於 DRAM,SCM 的密度更高能支持持久化,且成本更低。上面提到的三部分數據結構中, Tuple 和 String Pool 是主要占用數據的空間,存放在空間更大的 SCM 上,Index 需要頻繁訪問且占用空間更低,存放在空間較小延時更低的 DRAM 上。
另外一方面是從數據結構上去降低成本,這裡的技術手段包括,設計更友好的數據結構和碎片整理的機制、進行透明的數據壓縮。 Tair 中會以 Page 為單位來管理 Tuple,隨著數據的刪除,每個 Page 會有一些空閑的 Tuple,存儲引擎會按照空閑率來對 Page 分組,當整體的空閑率高於一定閾值(默認是 10%)時,就會試圖根據空閑率進行頁的合並。
Tair 目前在使用的索引主要有 HashTable、SkipList、RBTree、RTree、Number Tree、Inverted index 等,分別應用於不同的場景。索引和需要服務的模型是相關聯的,比如如果服務的主要模型是 Key-Value,那麼主索引使用 HashTable 來達到 O(1)的時間復雜度,ZSet 涉及到數據排序和排名的獲取,所以 Zset 使用瞭一個可以在查找時同時獲取 Rank 的 Skiplist 作為索引。排序場景使用 SkipList 作為索引是內存數據庫中比較常見的方案,相較於 BTree 來說,由於沒有 Structure Modification,更易於實現並發和無鎖,當然,也會增加一些 Footprint。在 Table 存儲中,使用 RBTree 作為排序索引,在數據量達到 10k 的場景下,RBTree 能夠提供更穩定的訪問延時和更低的內存占用。
在數據庫系統中,索引能力的增強還可以讓執行器對外暴露更強的算子,比如 Tair 中的 RBTree 提供瞭快速計算兩個值之間 Count 的能力,對外提供瞭 IndexCountOperator,這樣類似於 Select count(*) from person where age >= 8 and age <= 25 的查詢就可以直接使用 IndexCountOperator 來獲取結果,無需樸素地調用 IndexScanOperator -> AggregateOperator對索引進行掃描才得出結果。
合適的存儲介質和索引隻是提供低延時的一個前提,要在真實環境提供低延時的內存數據庫服務,至少需要經歷高吞吐的磨練。剛才我們關註瞭單個請求的延時,介質的延時和索引操作的時間復雜度會影響單個請求的延時。如果一個數據庫節點需要承擔每秒數十萬的請求,這些是不夠的,數據庫節點需要擁有良好的並發能力。如果吞吐近一步增長,帶來的 CPU、網絡消耗已經超過瞭單機的極限的時候,比如大促峰值時 Tair 某集群每秒提供瞭數千萬的讀,這些讀會帶來數萬兆的網絡流量消耗,這時候就需要產品能夠支持水平擴展,“凡治眾如寡,分數是也”,把請求散落到不同的 Sharding,提供穩定的低延遲。
並發是低延時場景一個關鍵挑戰。解法通常分為兩種,一種是在存儲引擎內部支持更細粒度的鎖或者無鎖的並發請求;還有一種是在存儲引擎外部來進行線程模型的優化,保證某一部分數據(一般來說是一個分區)隻被一個線程處理,這樣就能夠在單線程引擎之上構建出高吞吐的能力。
早期的版本中,Tair 的鎖粒度是實例級別的,鎖開銷損耗較大。為瞭提升單機的處理能力,Tair 引入瞭 RCU 無鎖引擎,實現內存 KV 引擎的無鎖化訪問,成倍提升瞭內存引擎的性能,相關工作發表在 FAST20 上:《HotRing: A Hotspot-Aware In-Memory Key-Value Store》。
在提升單機引擎的並發上,SQL 場景使用瞭另外一種解法,讓每一個 Partition 的數據由一個單獨的線程來進行處理,這樣能在引擎內部通過增加分區達到線性的擴展,而且無需使用前面提到的無鎖實現中常會使用的重試步驟。相對於上面的方案,這種方案的工程難度更低,且能夠天然地支持 Serializable 的事務隔離級別,某一特定時刻隻有一個事務能夠運行在特定的分區上,增加 undo buffer 即可以保證事務的原子性。
但是使用這種方式需要滿足一些假設:對每個 Partition 的訪問是均衡的;跨 Partition 的訪問比較少。如果某個 Partition 存在熱點訪問,也就是明顯高出其它的 Partition,由於隻有一個線程能處理這個 Partition 的數據,很容易造成這個 Parition 的請求堆積;如果出現跨 Partition 的訪問,就需要在各個 Partition 之間做同步,這樣也會造成等待並影響並發性能。目前支持的優惠、購物車場景都是用戶維度的,表中的 partition column 都是 buyer_id,所以單個請求基本是針對某一個特定分區的,數據鏈路不存在跨分區請求。數據統計調用的類似於 Select count(*) from table這種請求,由於存儲引擎的支持,單分區內可以 O(1) 的時間返回,所以也不存在問題。當然,對於 delete * from table 這種跨分區的寫操作,目前會對請求造成秒級抖動,未來會加入 Lazy Free 的處理邏輯,降低對正常請求的影響。
水平擴展是應對高吞吐的有效手段之一。水平擴展為分佈式系統帶來瞭應對高吞吐的能力,但同時相對於單節點的系統而言,也會帶來很多挑戰,比如:跨節點的請求如何保證事務性;如何彈性地進行節點增減;如何應對節點的失效等。在 Tair 的大部分場景而言,並不存在需要保證跨分區請求的原子性。Tair 的 SQL 引擎也支持跨節點的分佈式事務,但是這些分佈式事務一般不是常規的業務訪問,而是運維類的操作。
對於很多系統而言,分區和節點是 N 對1 的關系(常見於 Hash 分區),采用固定的分區數,和動態的節點數是一種常見的解決方案,比如說 Redis Cluster,在這類系統中,彈性地進行節點增減的問題就轉換為如何在節點前進行分區遷移的問題。也有一些系統的分區和節點是 1對1 的(常見於 Range 分區),比如 HBase,在這類系統中,彈性伸縮的問題就轉換為分區分裂的問題瞭。
對於節點失效,涉及到判活和後續的數據處理兩類的問題。對於很多系統而言,冗餘和分片是分開的,比如 Redis Cluster、MongoDB、AnalyticDB Worker,即有一個 HA Group 的概念,HA Group 中的每一個節點,數據是完全一致的。不同系統處理的時候依然會有些區別,一些系統 HA Group 中的某一個節點所有分區都是 Leader,我們稱為 Leader 節點,提供讀寫服務,其它節點隻有冗餘數據的同步流量,稱為 Follower 節點,比如 Redis Cluster,這類系統在調度系統不夠成熟的時期,有一個明顯的短板就是 Follower 節點所在的機器資源是有空餘的,通常是通過樸素的混佈來提供資源的利用率,但也帶來瞭部署上的復雜度,所以在系統設計的時候就會有這樣的考量:能不能在 HA Group內分散這些分區的 Leader 呢,於是就有瞭下面這些系統。一些系統 HA Group 的每一個節點都承擔部分分區的 Leader,這就是每個節點都會提供讀寫服務,比如 AnalyticDB Worker。這類有 HA Group 的系統,判活和後續數據處理一般隻在 HA Group 內,即某個節點失效後,會把訪問流量轉移到 HA Group 內的其它節點,然後通過上層的調度在 HA Group 內補充新的節點。還是老問題,在調度系統還不夠成熟的時期,補充新的節點也會帶來運維的復雜度,那就會有新的考量:能不能跨越 HA Group 的限制,把冗餘的個數和系統內節點的個數解耦呢?於是就有瞭 Kudu 這類系統的架構,冗餘和分片是交織起來的,某一個節點失效之後,它上面的分區會由中心節點來調度到其它節點。在調度能力非常成熟的今天,數據庫系統自身的能力怎麼和數據庫相關的調度能力想結合,也會給系統架構帶來新的啟發。
連接數的限制是一個比較容易被忽略的約束。但在一個真實的系統中,連接數過多會給系統帶來巨大的壓力。比如說 Redis,即使在 6.0 支持瞭多 io 之後,能夠支持的連接數也是有限的。而目前直接訪問 Tair 的應用動輒有 100k 規模的容器數目,所以支持超多連接數是一個必選項。其中涉及到的技術主要是幾方面:a. 提高多線程 io 的能力,目前成熟的網絡框架基本都有這個能力;b. 把 io 線程和 worker 線程解耦,這樣可以獨立增強 worker 的處理能力,避免對 io 產生阻塞,當然這個策略取決於 worker 的工作負載,對於單次處理延時穩定較小的場景,支持無鎖並發後,整個鏈路使用 io 線程處理避免線程切換是更優的方案;c. 輕量化連接,把關聯到連接上的業務邏輯和 io 功能剝離開,可以更加靈活地做針對性的優化,一些系統中連接對資源的消耗較大,一個連接需要消耗 ~10M 的內存資源,這樣連接數就比較難以擴展瞭。
現在有瞭高效的存儲引擎和水平擴展,已經具備瞭提供低延時和高吞吐服務的能力,但是成為一個健壯地提供低延時的數據庫系統還需要能夠應對一些異常的場景,比如說某一個分區有熱點訪問,比如說某個租戶的流量異常對其它租戶產生幹擾,比如說某些慢請求消耗瞭大量的服務資源。本章節將介紹 Tair 是如何處理這些“異常”場景來提供穩定的低延時的。
熱點訪問是商品維度、賣傢維度的數據常常會遇到的一個挑戰,熱點方案也是 Tair 能夠服務於低延時場景的關鍵能力。前面講瞭水平擴展之後,用戶的某個請求就會根據一定的規則(Hash、Range、List 等)路由到某一個分區上,如果存在熱點訪問,就會造成這一個分區的訪問擁塞。處理熱點有很多方案,比如二級散列,這種方案對於熱點的讀寫可以做進一步拆分的場景是有用的,比如現在我們有一個賣傢訂單表,然後賣傢 id 是分區列,則我們可以再以訂單 id 做一次二級散列,解決一個大賣傢導致的熱點問題;目前淘寶大規模使用的 Tair 的 KV 引擎不滿足使用二級散列的前提,一般來說商品的信息映射到 Tair 內就是某一個 Value,更新和讀取都是原子的。所以 Tair 目前使用的方案是在一層進行散列,借助於和客戶端的交互,將熱點數據分散到集群當中的其它節點,共同來處理這個熱點請求,當然這種方案需要應用接受熱點在一定時間內的延遲更新。另外這種方案需要客戶端和服務端協同,需要應用升級到對應的客戶端才能使用。所以最新的 Tair 熱點策略在兼容社區 Redis 的服務時使用瞭不同的方案,應用能夠直接使用任一流行的開源客戶端進行訪問,因此需要在服務端提供獨立的熱點處理能力。目前的 Tair 熱點能力是由 Proxy 來提供的,相對於 Tair 之前的方案,這種方案擁有更強大的彈性和更好的通用性。
c22d106363b962a4657e63b3799d3744
服務於多租戶的數據庫系統,解決資源隔離的問題通常需要對進行容量或者訪問量的配額管理來保證 QoS。即使服務於單租戶的系統,也需要在用戶有突發異常流量時,保證系統的穩定性,識別出異常流量進行限制,保證正常流量不受影響,比如 Tair 中對於 慢 SQL 識別和阻斷。再退一步,即使面對無法識別的異常流量,如果判斷請求流量已經超過瞭服務的極限,按照正常的行為進行響應會對服務端造成風險,需要進行 Fast Fail,並保證服務端的可用性,達到可用性防禦的目的,比如 Tair 在判斷有客戶端的 Output Buffer 超過一定內存閾值之後,就會強制 Kill 掉客戶端連接;在判斷目前排隊的請求個數或者回包占用的內存超過一定閾值之後,就會構造一個流控的回包並回復給客戶端。
流控一般包含以下幾部分內容:請求資源消耗的統計,這部分是為流控策略和行為提供數據支撐;流控的觸發,一般是給資源消耗設定一個閾值,如果超過閾值就觸發;流控的行為,這部分各個系統根據服務的場景會有較大的不同;最後的流控的恢復,也是就是資源消耗到達什麼情況下解除流控。
經典的 NoSQL 系統,提供的 API 都是和服務端的處理流程非常耦合的,比如說 Redis 提供瞭很多 API,光是 List 就有 20 多個接口。在服務端其實很多接口的執行過程中的步驟是比較類似的,比如說有一些 GenericXXX 的函數定義。我們再看看一般的 RDBMS 中的處理 SQL 的流程,一般是 解析(從 SQL 文本到 AST),然後是優化器編譯 (把 AST 編譯成算子,TableScan、Filter、Aggregate),然後是執行器來執行。類比到 Redis 中,用戶傳進來的就是 AST,且服務端已經預定瞭執行計劃,直接執行就行瞭。如果我想使用 SQL,不想學習這麼多 API,同時由於我的訪問場景是比較固定的,比如進行模板化之後,隻有十多種 SQL 語句,且訪問的數據比較均衡,某一條特定的語句所有的參數用一條特定的索引就足夠瞭,有沒有辦法在執行過程中省去解析、編譯的開銷來提高運行的效率?有很多同學可能已經想到瞭存儲過程。是的,存儲過程很多場景是在擴充表達能力,比如多條語句組成的存儲過程,需要進行比較復雜的邏輯判斷,單條語句存儲過程本質上是在靈活性和性能上進行折衷。Tair 所有線上運行的 SQL 都是預先創建存儲過程的,這樣進行訪問就類似於調用 Redis 的一個 API 瞭,這是在復雜計算邏輯的場景下保證低延時的一種方案。
很多熟悉數據庫實現的同學對火山執行模型都不陌生,tuple-at-a-time 的執行方式會消耗比較多的 cpu cycle,對 cache locality 也不太友好。在分析場景,通常會引入 code-gen 技術來進行優化,比如 Snowflake、GreenPlum。Tair 中使用 Pipeline 執行模型,使用 Bulk Processing 更適合目前應用的 TP 場景。使用 Pipeline 執行模型對於算子的設計和執行計劃的生成更有挑戰,以 Scan 算子為例,Scan 算子中內聯瞭 Filter、Aggregate 和 Projection,Scan 算子本身邏輯比較多,且在執行計劃編譯過程需要在邏輯優化階段進行算子內聯的轉換。
從最早的 KV 到擴展的 Pkey-Skey-Value,再到 List、Zset,再到支持地理位置的 GIS,再到支持全文索引的 Search 和 Table 結構 的 SQL,Tair 早已不再是一個單純用來存儲熱數據的緩存,而是能夠把更多存儲上構建的計算能力方便地提供給業務使用的內存數據庫。這一章節介紹內存數據庫 Tair 在雙十一場景的應用。
提到 MySQL,開發者很容易想到 Table 模型,想到 SQL 查詢來進行過濾、排序、聚合等操作;想到 Redis,很容易想到高吞吐、低延時。使用 Redis 來進行讀加速的場景,都需要把 MySQL 中數據查詢出來之後,序列化到某一個 Value,加速場景直接獲取 Value 即可,無需再進行過濾、排序等操作。如果一個讀加速的場景不僅需要高吞吐低延時,也需要進行過濾等操作,Redis 還能夠滿足需求麼?更進一步,如果引入讀加速的過程中不希望改變數據模型,依然希望使用表模型,省去模型轉換的心智負擔,同時擁有高吞吐低延時,支撐 10w 級別的連接數,需要使用什麼產品呢?目前優惠查詢和購物車的場景的需求抽象出來就是這樣,這種需要關系型數據庫超級隻讀的場景就需要引入 Tair 的 SQL引擎,兼有 MySQL 和 Redis 優勢的產品。
89e80b21f62fa8410c1ca974d581710e
歷史上雙十一因無法解決銷量的實時計算問題對商傢產生過很多困擾。為應對2022年雙十一,Tair 銷量計數項目應運而生:利用已有的 Tair 非精確“去重計數”算子開發新的“去重求和”算子,解決用戶商品銷量計數慢而無法實時獲得銷量數據的痛點問題。通過對用戶的商品訂單消息進行原子地“去重和銷量的實時求和”能力,雙十一首次做到瞭“買傢訂單數不降級”、“商品月銷量不降級”兩項大促核心體驗。同時,利用 Tair-PMem 底座進一步幫助用戶降低使用成本,提升數據持久化能力。相比於傳統的 AP 類數據庫,通過開發的獨特非精確計算算子,有效降低瞭單 QPS 的計算成本。
淘菜菜是阿裡社區電商對外的統一品牌,賣傢維度的優惠券召回作為一個重要的功能模塊,需要搜索系統滿足低成本、實時索引和低延遲的搜索能力。鑒於之前使用的搜索系統無法滿足需求,淘菜菜今年雙十一首次使用 TairSearch 能力實現賣傢維度優惠券召回功能,Tair 以其高效實時的內存索引技術為商傢提供更加平滑友好的操作體驗。
2fc47ee7ea5a4ff95ad48af35977c005
TairSearch 是Tair自主研發的高性能、低延時、基於內存的實時搜索特性,不但增強瞭 Tair 在實時計算領域的能力,還和現有的其他數據結構一起為用戶提供一站式的數據解決方案。Tair采用瞭和 ElasticSearch(下文稱之為ES)相似的基於 JSON 的查詢語法,滿足瞭靈活性的同時還兼容ES用戶的使用習慣。Tair 除瞭支持 ES 常用的分詞器,還新增JIEBA 和 IK 中文分詞器,對中文分詞更加友好。Tair支持豐富的查詢語義和聚合能力,並且支持索引實時更新和局部更新。Tair 可以通過 msearch 方案實現索引的分片和搜索能力,並通過讀寫分離架構實現搜索性能的水平擴展。
隨著同城購業務的興起,商戶判店場景越來越流行,判店就是商傢給自己的一個門店圈出來一個銷售范圍,可以是行政區域,也可以是不規則形狀,或者按照半徑圈選,如果消費者在這個銷售范圍內就認為門店對該消費者可售,如果不在消費范圍內則不可售,抽象此模型則是:點和多邊形包含關系的判斷。
傳統的判店架構使用 MySQL 或者 PostGis 數據庫,雖然其對 GIS 相關能力有專業的支持,API 也比較完備,但是由於其本身磁盤存儲的特性,查詢速度較慢,特別是數據量較大的場景下,產生多次磁盤讀 IO,導致業務查詢超時。
新一代判店系統,依托 Tair 的 Gis 能力,底層使用 RTree 結構,支持常見的 Contains, Within, Intersects 等關系判斷,可以在 ms 級別返回查詢數據,目前已經在淘菜菜、天貓超市、淘鮮達、盒馬、同城購等多個業務使用。
TairGis 的新一代判店系統
雙十一主互動場景一直是技術挑戰最大的場景之一,一方面參與活動的用戶數量大,在活動時間集中活躍,帶來的大量的訪問請求對數據庫層面的沖擊尤其巨大;另一方面要求活動體驗不降級,對延時的要求更高。今年主互動活動--猜價格,使用瞭 Tair 單一數據庫的模式支撐瞭整個互動活動。在主互動場景中,Tair 作為KV數據庫支撐幾乎所有的數據存儲和讀寫,後端無 DB 兜底,是唯一的數據源,除瞭要求讀寫的低延遲、高並發以外,還要求數據的絕對安全無丟失。
TairHash 提供的高並發寫入能力確保瞭千萬級用戶的答案提交順暢;TairZSet 提供的有序數據結構,幫助應用在10s內計算出千萬體量的用戶分數排行榜,並支撐快速查詢;帶有二級Key 主動過期的 TairHash 為業務設計復活卡、拉新、錦鯉抽獎等多種玩法提供瞭方便而強大的技術支撐。
Tair 已經在這一類需要低延遲、高並發、數據安全、快速開發的業務場景中表現出瞭強大的能力,還將持續追求更高性能、更易用、更安全。
在產品力上,Tair 提供瞭遠遠不止以上圍繞著低延時來打造的產品能力,比如數據多副本管理、全球多活、任意時間點恢復、審計日志等等。同時 Tair 在兼容 Redis 之外,提供瞭豐富的數據處理能力和基於不同存儲介質的混合引擎來提升性價比。
2022 年還有一些其它的事情在發生:Tair 的論文發表在數據庫領域頂會 VLDB ,雲原生內存數據庫 Tair 獨立產品上線阿裡雲官網,Tair 全自研 Redis 兼容內核在公共雲所有 Region 上線等等。有一些成績,也有很多挑戰,還有更多機會。Tair 會將已經具備的能力建設得更通用,並在新的領域尋求新的突破,在更豐富的低延時場景承擔起更重要的責任,為客戶創造更多價值。
2017年7月30日9:00慶祝中國人民解放軍建軍90周年閱兵在內蒙古朱日和訓練基地舉行這次閱兵是繼1981年華北軍事大演習閱兵後解...