TDSQL 是騰訊云旗下金融級分布式數據庫產品,具備強一致高可用、全球部署架構、分布式水平擴展、高性能、 HTAP 雙引擎、Oracle 兼容、企業級安全、便捷易運維等特性,目前金融核心系統客戶已超過 20 家,尤其 在銀行傳統核心系統領域,位居國內第一陣營。客戶涵蓋多家國有大行,及平安銀行、張家港銀行、昆山農 商行等頭部銀行及廣泛金融行業機構。
TDSQL 架構介紹
TDSQL 的全稱為 Tencent Database SQL。下圖是 TDSQL 的最新技術架構,它描繪了一個計算/存儲分離、 數據面 / 管控面分離的高可用的原生分布式架構的關系型數據庫。 TDSQL 分為三層:綠色部分是計算層,稱之為 SQLEngine ;紫色部分是管控層,簡稱為 MC ,負責整個 集群的管控調度工作;藍色部分是存儲層,稱為 TDStore 。 TDSQL 架構三個重要的特性:全分布式,無論在計算層還是管控層,每一層都是分布式的架構;計算與存 儲分離;可實現動態可擴展功能特性。
TDSQL 的四個主要功能特點分別是:
- 高度兼容 MySQL 。 TDSQL 對單機遷移過來的業務,兼容度高達 100%,能夠實現無感知遷移。
- 高可擴展性。在存儲層和計算層,用戶只需手動在管理界面上添加一個存儲節點或計算節點,后續內部的 管控機制會自洽地完成整個流程。
- 支持原生 Online DDL,可多寫架構下以原生方式實現 Online DDL。
- 全局讀一致性。TDMetaCluster 統一分配全局唯一遞增事務時間戳,實現金融級場景下的數據強一致。
分布式架構主要分為計算層、存儲層和管控層。
首先是計算層。TDSQL 計算層最大的特點是多主架構,每個節點都支持讀寫,完全相互獨立;每個計算節 點為無狀態,具備擴容優勢,可實現 MySQL 的高度兼容,具備無狀態化的彈性擴容的架構特性。
其次是存儲層。它是我們自研的 KV 存儲引擎。在存儲層和計算層的交互中,存儲層承擔事務協調者的角色。 我們以一定方式將數據打散到每個存儲節點上,每個數據分片稱為 Region 。存儲層對所有數據的狀態自身 無感知,只負責數據的讀寫,所有數據的調度都由它與 MC 的交互來進行。
最后是管控層。它是一個分布式的集群,以 N 倍的方式去部署。在整個集群中,它要同時承擔管控層面和數 據層面的工作。比如在數據層面,MC 負責一個全局、一個中心的嚴格遞增,是唯一的分配者角色,且同時 負責管理所有的計算節點、存儲節點的元數據、MDL 鎖。
以下是數據庫中常見的主功能流程:
分布式事務。數據庫的訪問是天然的分布式事務。整體流程為:從 MySQL 客戶端發送請求,通過計算層對 存儲層進行讀寫,讀寫過程中,存儲層和計算層都會去和 MC 交互,獲取時間戳,最終確定每個事務之間的 偏序關系,完成整個流程。
無感知擴縮容。當存儲空間不夠時就需要擴容。在擴容過程中,必然會涉及到數據的搬遷。如下圖例子所示, 整個集群中只有一個存儲節點,當需要擴容時,可以在界面上點擊多購買一個存儲節點。此時存儲節點上數 據的分裂搬遷、計算層對最終數據路由的感知、計算層感知路由的變化后完成的重試等過程可以完全自洽地 包含在整個數據庫體系中,實現業務層無感知。
Online DDL。TDSQL 的分布式體系架構采用多寫架構。為達到更好的并發性能,需要在多寫的架構下,實 現 Online DDL。相較同類產品,當業務嘗試在運行過程中加一列、加一個索引時,需要借助外部的工具, 及堵塞業務來完成 DDL 的操作。為了更好的用戶體驗,降低對業務的影響,通過 TDSQL 可以把多寫架構 下的 DDL 以原生方式去完成。
TDSQL 在分布式場景下的挑戰
TDSQL 架構的三大功能特性:
- 原生分布式,全部都是分布式,沒有中心節點管控。
- 存算分離,計算跟存儲完全分離,不在一個服務器上。
- 數據跟管控完全分離,數據層面完全不參與數據管理。
這些特性直接、簡單, 卻在工程落地時遇到 諸多挑戰,主要是高 性能、高可用、復雜 調度三個方面。下面 將著重分享我們在高 性能、復雜調度方面 遇到的挑戰。
首先是高性能方面 的挑戰。在架構上, 要做到分布式的完 全存算分離的架構, MC 作 為 集 群 內 唯 一一個中心的管控 模塊,必須承擔全 局授時源角色。
在分布式事務的整體架構圖中,可以了解到 MC 在事務過程中需要和存儲層做網絡交互,提供時間戳,這是 關鍵路徑。同時 TDSQL 的計算層和存儲層都可以靈活擴縮容。存算分離、高擴展的兩個特性也意味著 MC 必須要具有非常高的性能。
在復雜調度層面。我們設計成數據和管控完全分離的架構,數據完全存儲在TDStore上,只負責數據流的讀寫。 其他工作完全交由管控層去進行,MC 只需要監控整個集群的狀態做出關于存儲資源的決策。
TDSQL 在集群管控的探索與實踐
高性能方面的探索與實踐
在高性能方面,我們采用 非常經典的三段式協程架 構,即一個協程收、處理、 最后再發。這種架構在我們突破 60 萬時就達到性能瓶頸。
通過性能分析,我們意識 到性能瓶頸集中在第二個 協程里。于是我們將出現 瓶頸的地方并行化。但第 二個協程增加到一定時, 下個瓶頸又出現,因為協 程 1 是單管道模式,新的 瓶頸點集中在協程 1。
我們在下個版本里做了一個略微復雜的 N 對 N 架構,也是多協程架構。基于此我們發現性能可以提升但 CPU 的消耗非常大。我們的設計目標是 MC 在性能方面有較強的表現,其性能數據能到達 500 萬。但當時 盡管達到 75 核,數據還是停留在 320 萬。我們對此進行 perf 分析,發現問題主要來自 RPC 解析,因為 MC 主要網絡框架的實現是基于 GRPC 的網絡通訊,會有比較大的頭部序列化和反序列化的性能開銷。
已知性能阻礙存在于網絡框架,我們優化的目標就成為擺脫網絡框架帶來的性能限制。對此我們給時間戳的 獲取開發了 TCP ROW Socket 通道。因為時間戳數據結構有一個天然優勢,即請求無狀態、無依賴,只包 含兩三個整型字段。這時在網絡上發來的任何請求,MC 只需要收到一個,回答一個,可以去定制化完成請求。
在棄用該框架后,性能提升飛快,在比較低的 CPU 開端的情況下,可以將性能提升到 450 萬。但在后續過 程中,我們發現瓶頸出現在請求進隊列還有請求出隊列的過程中。我們使用 Go Channel 作為消息的進出隊 列載體,Channel 雖然好用且輕量,但底層依舊帶鎖實現,push/pull 操作存在著百納秒級別的開銷。
對此我們計劃實現無鎖隊列,需要實現單生產者、單消費者模式的場景?;谶@種場景,我們實現一個簡單 的信號量,作為兩者之間的喚醒機制。使用該優化方案后,資源的消耗明顯降低且達到更高的性能,峰值吞 吐突破 750 萬。
最初 500 萬的目標雖已完成,但我們團隊仍認為性能數據還可以更好。以下為經典的 CPU 緩存的 MESI 狀 態轉換圖。一行 CPU 的 Cache Line 可以容納 64 個字節,在我們的數據結構中,將其中多個 8 字節的變 量放在同一個緩存,如果一個更新非常多,另一個更新的少,就會影響另一個原子變量的讀寫。從圖片右邊 可知,在這里把變量的 8 字節對齊后,就解決 CPU 緩存行的問題,性能數據也從 750 萬上升至 920 萬。 此時我們的目標是實現單中心的千萬級別的性能數據。
為了進一步實現單中心的千萬級別的性能數據,我們從業務場景進一步深挖。在整個集群中,MC 在時間戳 方面是單一的提供者,而集群中眾多的計算節點和存儲節點會產生源源不斷的請求,因此在分析生產者和消 費者速度時,我們發現生產者速度遠遠跟不上消費者速度。為此我們進行了簡單的改造,即來一批請求再消 費一批時間戳,以批量請求方式去喚醒消費者。為了適應業務場景,我們還對該優化做了開關,運維人員可 以根據業務場景的需求進行調節。執行批量化操作后,整體峰值已經提升至兩千萬,許多數據庫實例的業務 場景都無法到達這種高壓力。
下圖為 TDSQL 原生數據庫下,我們通過一系列優化手段達到 2000 萬性能數據的過程。
復雜調度方面的探索與實踐
由于實現數據面與管控面的分離,MC 要負責整個集群所有跟資源相關的管控。如圖所示,圖中畫有 MC 的 主要功能。
MC 需要負責時間戳的提供,管理全局的唯一 ID 的分配、DDL 的協調、計算層管控層資源的元數據以及數 據分片的管理。在管控層的不同層面,所有跟管理調度相關的工作都集中在 MC 。
為了實現復雜調度,我們首先劃分資源層級,制定可用的具有可擴展性的基礎框架,將 MC 模塊從管理任務 中釋放。將每個資源層級必須劃分清楚,使得數據路徑上的所有模塊只需要被動執行,不需要更新狀態。我 們從集群層面、復制組層面和副本層面進行劃分,劃分出許多子狀態及子步驟。
比如在擴容過程中,有一個數據分片,副本分布在 123 三個存儲節點中,如果要進行數據遷移使得一主兩備 變為 1234 分布,任意時刻這四個節點都知道自己要做的原子子步驟,整個遷移過程實現零感知。
遷移過程包含五個原子子步驟:先在節點 4 上創建新部分,再將新部分加入到原本的數據復制同步組中,去 掉的副本的狀態設置為 off line,最后再把該副本刪除。在分布式數據庫中隨時可能有節點掛掉,但通過步 驟劃分,無論是 MC 掛掉還是 TDStore 掛掉,節點拉起來后都知道要如何操作。通過這樣的機制,存儲層 對每個任務的推進或回滾完全無感知,全部由 MC 來做協調。
該機制主要有以下三方面的效果:
首先是性能。該機制對性能提升的促進非常顯著,在集群比較大時可以輕松支持 EB 級存儲。比如在 500 萬 數據分片的量級下,MC 用 20 個核就能完全支持。通過數據狀態與調度狀態的分離,大大降低了 MC 負載。 性能上的收益還體現在存儲層上。在任意時刻它只需要接收到一個原子步驟即可。因此在代碼層面,存儲層 不需要任何關注數據資源狀態的代碼,更有利于進行性能優化。
其次是健壯性。每個任務都是有限的狀態機,任意一個參與者,如管控或存儲,出現交互中斷,都能夠以確 定方式進行任務的回滾或恢復。
最后是可擴展性。每個管控任務分為多個原子步驟進行,有利于以插件式方式去定義其他更多更復雜的任務。 整個過程中只需要將原子步驟拼裝組合,MC 就可以實現復雜調度。
數據分布方面的探索與實踐
原始版本中,MC 對數據分布管理只有物理位置概念,基于擴展引擎和分布式協議打造的 KV 存儲引擎,數 據分片在整個分布式存儲集群中按照主鍵從空到正無窮的字符序來進行分布。比如創建表或二級索引時,如 果要表達成 KV 形式,主鍵和二級索引都有對應的 ID 。存儲層中以 Key 區間代表一個數據分片,如 01-02 數據分片,落在存儲節點 1 上,02-03 數據分片,落到存儲節點 2 上。這種情況下,同一張表的數據的主 鍵和二級索引會落在不同的 TDStore 上,這就會造成很大的負面影響。
舉個例子,有一張表,每天有大量不同的流水寫入,有三億行數據,業務為方便查詢,做了 20 個索引。在 最壞的情況下,20 個索引分別落在不同的 Region ,又落在了不同的 TDStore 。數據庫使用者從操作上更 新了一行,但可能會發展成 20 個涉及到 60 個參與者的分布式事務,帶來 60 次同步的性能損耗。在這種情 況下,我們針對經常出現的業務場景對兩階段提交進行優化,讓更多的提交變成一階段提交。
我們設立表內數據的概念,每個數據在物理層面都可以知道每個 Key 落在哪個 TDStore,但無法感知到它 們屬于哪個二級索引。對此我們需要建立關系去創建表,使得在創建表和索引時,MC 可以感知到每個 Key 在物理意義上屬于哪個 TDStore ,邏輯意義上屬于哪些表的分區、屬于哪些表的二級索引。
我們還引入了復制組的概念。在原始版本中,每個數據分片是一個復制組,現在則是將多個 Region 歸屬于 一個復制組,通過管控體系架構的改變,將表數據和二級索引放在同一復制組里。這種做法的好處有兩方面: 一方面,業務中常常按照分區鍵來劃分事務,一次性修改的數據非常大,可能只落在同一復制組里,這時需 要進行多次網絡交互才能完成一個分布式事務,優化后只需要一次落盤即可完成;另一方面的好處是計算下推,由于計算層可以感知到要寫的主鍵、二級索引都在同一 TDStore 的同一復制組內,就可以提前將邏輯 下推到存儲層中完成。
接下來解決的問題是表與表之間的親和性。在部分系統中,以一定規則如哈希去分區的表結構中,在更新表 1 分區時,也會去訪問表 2 的 1 分區。這就要求管控層必須理解表與表之間的概念。如果能感知到它們是在 同一組事務里被操作的單位,就可以更好地改善事務的兩階段提交。
對此我們提供了一個擴展語法。假如用戶有需求,可以去指定他所傾向的數據分布策略,為該策略命名,允 許在該分布策略里再指定分區策略。如下圖所示,當下面第三行創建表時,如果有兩個表在業務場景中經常 被訪問,就可以將它們關聯到同一 DP 組里,MC 會在后臺創建表。所有的分布策略都會通過 MC 進行, MC 可以在后臺將這些關聯的表做背后的調度優化。這就為更多跨表之間的操作提供較多的可能性。
Risk & Opportunity
未來我們仍有許多挑戰需要克服。首先是全局事務時間戳,目前 MC 承擔許多的管控操作,后續我們計劃 將其設計成多進程模式,全局事務時間戳獨占一個進程。其次是 Lieutenant 分流,我們計劃增加副隊長角色, 分流部分網絡。最后是數據親和調度的利用也是我們未來會去重點攻堅的領域。
出處:InfoQ 騰訊云技術實踐精選集2021