作為雲原生技術先驅,騰訊雲資料庫內核團隊致力於不斷提升產品的可用性、可靠性、性能和可擴展性,為用戶提供更加極致的體驗。為幫助用戶瞭解極致體驗背後的關鍵技術點,本期帶來騰訊雲資料庫專家工程師王魯俊給大家分享的騰訊雲原生資料庫TDSQL-C的架構探索和實踐,內容主要分為四個部分: 本次分享主要分為四個部 ...
作為雲原生技術先驅,騰訊雲資料庫內核團隊致力於不斷提升產品的可用性、可靠性、性能和可擴展性,為用戶提供更加極致的體驗。為幫助用戶瞭解極致體驗背後的關鍵技術點,本期帶來騰訊雲資料庫專家工程師王魯俊給大家分享的騰訊雲原生資料庫TDSQL-C的架構探索和實踐,內容主要分為四個部分:
本次分享主要分為四個部分:
第一部分,介紹騰訊雲原生資料庫 TDSQL-C 產品架構,包括產品的研發背景和架構主要特性;
第二部分,分享用戶場景實踐,針對線上真實的用戶場景做一些分析和針對性實踐;
第三部分,分享系統關鍵優化;
第四部分,分享產品未來演進。
TDSQL-C 產品架構
背景
騰訊雲原生資料庫最初採用的是傳統架構,也就是 MySQL 實例,或者說是採用 Binlog 複製的主備方式的架構。但這種架構在現有的一些用戶需求來看是有很多問題的。
比如存儲容量。傳統架構實例的存儲上限受限於本地磁碟上限,一般是幾百 G,或者幾個 T 的量級,做擴展的成本非常高而且麻煩。當用戶數據非常多時,會做分庫分表,使用現有的分庫分表中間件或解決方案會帶來一些分散式事務的問題。
做業務的同學知道,分散式事務處理起來會比較麻煩,涉及如何應對故障,如何應對分散式事務產生的數據不一致等問題。用戶在存儲容量方面的需求是實例容量大於 100T,並且存儲容量能夠快速透明的擴展。
其次是可靠性。傳統架構基於 BinLog 複製,普通的非同步或者半同步的複製方式可能會丟數據,同步的複製方式性能損失會比較大。用戶在可靠性方面的需求是第一不能丟數據,即 RPO 等於 0;第二數據是有多副本容災的,也就是要達到一定程度的數據可靠性。
此外還有可用性,可用性對於用戶來講就是服務有多長時間不可用,比如在傳統架構發生一次 HA,或者宕機重啟,這段時間服務都是不可用的。HA、恢復時間慢對用戶來講很難接受,傳統架構 HA 或者副本的恢復速度可能達到了分鐘級。第二個問題是基於 BinLog 複製的時候,主備副本的延遲比較高,有些可能達到分鐘級,甚至達到小時級。用戶希望能夠快速切換 HA,實現秒級恢復,還包括回檔功能,如果有副本,希望副本的延遲能夠比較小,最好是秒級以下,甚至是毫秒級。
最後是可擴展性。傳統架構的擴展性是非常複雜的,基於 Binlog 創建只讀副本也會非常複雜,要先把原始的數據給複製過來,然後搭建主備同步,只讀副本才能開始工作,這個過程至少是分鐘級甚至是小時級別的。對用戶來講,他們希望當讀需求有擴展性需求的時候,可以實現秒級的讀副本擴展。
我們針對這四個方面的用戶需求,採用了存儲計算分離架構,這也是 TDSQL-C 所採納的核心的架構想法。
簡單來說,要解決存儲容量和可靠性方面的問題,第一我們會用雲存儲,雲存儲之間是可以水平擴展的,理論上它的容量是無限的,而且對於每一份數據都有多副本來保證可靠性。數據分散在雲存儲的各個節點上,在這個基礎上可以做持續備份,並行回檔等功能。
在可用性方面,數據放在雲存儲上之後,數據的分片是可以做並行恢復的,回檔也可以做並行回檔。物理複製的時延一般會比基於 Binlog 的邏輯複製更低一點。
最後在可擴展性方面,共用存儲的優勢更加明顯,當新建一個只讀副本的時候,數據不需要複製一份出來,因為數據是在雲存儲上作為共用數據存在的,只需要把數據共用,另外再構建增量的數據複製就可以了。
架構特性
上面這張圖是 TDSQL-C 的整體架構,從這個架構中我們可以看到,它整體上分為上一層的計算層和下一層的存儲層。
計算層有一個讀寫節點,可以提供讀寫請求,還有多個只讀節點,可以提供讀請求,也就是圖裡邊的 Master 節點和 Slave 節點。當讀寫請求,尤其是寫請求進來以後,Master 節點也就是讀寫節點產生數據的修改,然後它會把修改產生的 InnoDB 的 Redo Log 下傳到整個存儲層,同時把 Redo Log 分發到自己的 RO 節點。
存儲層負責管理數據,當產生的 Redo 日誌發送到存儲層之後,它可以負責 Redo 日誌的回放,Segment 把它存儲的頁面對應的 redo 日誌 apply 到自己的頁面上來。整個存儲層是架設在 COS 存儲服務上。
TDSQL-C 的存儲可以自動擴容,最大支持超過 1PB 的容量,目前我們的產品最大支持到 96CPU 和 768GiB 的規格。性能方面,只讀大概能跑到一百萬以上 QPS,寫性能也能超過 40 萬 QPS。
基於這種共用存儲的架構,我們可以做到秒級故障切換,包括秒級的快照備份和回檔,並且因為存儲層本身可以做彈性,計算層也可以做彈性,所以可以實現一定程度的 Serverless。
此外,當需要擴展只讀的時候,可以很容易的增加只讀節點,TDSQL-C 現在最多可以掛 15 個只讀節點,並且在讀寫節點和只讀節點之間只有毫秒級的延遲。因為整個工程是基於 MySQL 代碼庫演化過來的,所以是百分之百相容 MySQL 的。
場景實踐
接下來,我會介紹並分析幾個比較典型的場景實踐。
Serverless
上圖描述的是一些業務預測未來一段時間的數據存儲或者數據計算的需求是持續上漲的,但實際上可能真實的用戶需求是圖中灰色的曲線。為了做好服務,用戶要提前買好服務庫實例,比如圖中紅色的折線,一開始就要準備好這種規格的資料庫實例。
這種情況有一個壞處,實際買的規格都是比真實需求偏大的,這就會造成存儲資源或計算資源的浪費。如果某些時刻有突發的流量進來,突然對存儲或者計算的資源要求非常高,就會出現機器實例資源跟不上、規格太小等情況,這會對業務造成很大的影響。
我們認為理想的情況應該是圖中藍色的曲線,這個曲線的整個資源容量跟真實業務需求的變化規律是一樣的,並且總是比真實的需求稍微多一點。這樣就能真正把資源充分利用起來,而且最大程度上降低成本開銷。
在一些真實的例子里,我們發現有些業務是開發測試場景,業務真正上線之前,會做一些系統的測試開發,這個過程對資料庫的需求頻率是非常低的。還有一些像 IoT、邊緣計算、SaaS 平臺,他們的負載變化有非常大的規律性,白天壓力比較大,但是晚上壓力比較小。
TDSQL-C 做到了智能極致的彈性,能夠根據負載來快速啟停實例。第二是按需計費,用了多少花多少,不用不付費,可以做到按秒的計量,按小時的結算。
彈性擴容
另一個我們線上上業務中發現的實踐需求是彈性擴容。有些業務每天都能產生大量的數據,比如有的業務一天產生幾百 G 的數據,並且這種業務對單庫的容量要求很高,通常都是幾十 T,甚至上百 T 的數據。
有些場景,像開發測試場景,開發完或者某一次測試完,資料庫直接就刪掉了,生命周期非常短。另外有些歷史庫場景,歷史數據存儲只是存儲最近一段時間的,特別老的數據直接就刪除了,刪除了這些數據希望空間立刻回收,不要再產生存儲成本了。
TDSQL-C 可以做到按需擴容,存儲根據操作頁面按需擴容,如果產生的數據比較多就擴展出來,不需要預先規劃好要做多少存儲。第二點是自動回收,有一些空閑空間,比如數據已經刪除了,這些數據實際不需要了,但是傳統的 RDS 是邏輯刪除的,這塊可能還會繼續產生費用,TDSQL-C 可以做到按實際的容量來計費。
備份回檔
很多場景對備份回檔要求比較高,比如金融行業,因為金融行業對數據安全關註度非常高,他們對備份的速度和備份的時效性都有很高的要求。還有像游戲業務,可能會涉及到頻繁的回檔,所以對備份回檔的速度要求也比較高。
回檔作為“後悔藥”,對很多業務來講都是很重要的一個功能,用戶可能會產生一些誤操作。
TDSQL-C 可以做到持續備份,存儲分片可以根據備份點進行併發的獨立備份,同時可以做到設定全局的一致性備份點來進行備份。此外,TDSQL-C 也可以做到並行回檔,每一個分片並行回檔各自的數據的全量和增量的備份,並行回放自己的日誌。還有 PITR,也就是可以快速的恢復到資料庫的任意時間點的數據的狀態。
系統關鍵優化
極速啟停
第一個優化就是前面提到的,如何做到極速啟停,也就是支持更好的 Serverless。
這邊有個測試數據,第一個測試數據叫停機時間,指的是計劃內的停機時間,即主動停機。TDSQL-C 跟傳統的 RDS 資料庫對比,傳統 RDS 資料庫停機需要 26 秒,TDSQL-C 可以做到 3 秒內停機。
第二個是啟動時間,就是停機之後重新把資料庫實例拉起來,大概需要多少時間能夠恢復起來,RDS 需要 48 秒,TDSQL-C 可以做到 4 秒就啟動起來。
我們分析了一下,這 48 秒有 21 秒是在做事務恢復,也就是第三個柱狀圖,我們對事務系統的並行初始化、表鎖恢復做了一些優化,可以把恢復時間降到一秒。
第四個指標叫性能恢復時間,這個指的是比如重啟之前資料庫的 QPS 大概跑到了 20 萬 QPS,重啟之後大概需要多長時間才能重新恢復到 20 萬 QPS。這對很多業務來說都是很重要的,是恢復質量的問題。傳統 RDS 在有些場景下需要 260 秒才能恢復,但是用 TDSQL-C 3 秒就能恢復了。
這裡我們做了一些優化,用的**獨立 BP **的優化方式。Buffer Pool 跟資料庫實例進程是解耦的,由這台機器上的另外一個進程來負責管理這塊記憶體,當資料庫實例重啟之後,Buffer Pool 是可以繼續用的,這種方式就避免了重啟之後,整個 Buffer Pool 都是冷的,需要很長時間慢慢預熱,省了這個過程,所以恢復時間會非常快。
二級緩存
另一個系統關鍵優化是二級緩存。二級緩存是 TDSQL-C 在存儲計算分離架構下做的比較創新的優化,也是對架構的一個重要補充。
如此前所說,存儲層是可以水平擴展的,這意味著數據量膨脹了很多倍,可能幾十倍、上百倍。數據量多了,但是計算層計算節點的 Buffer cache,也就是 InnoDB 的 Buffer Pool 的容量並沒有太大的變化,這就意味著需要用以前相同大小的 Buffer Pool 來服務更多的數據。
大家知道 InnoDB 的 Buffer Pool 在一定程度上承擔了讀緩存的作用,服務更多的數據,意味著讀緩存的效率可能會下降。以前一些並不是 IO Bound 的場景,在這種數據量大了的場景下就變成 IO Bound 了,或者以前本來就是 IO Bound 的場景,IO Bound 更嚴重了,這樣對性能影響還是比較大的。
其次,傳統 RDS 的 BufferPool 和本地磁碟空間的存儲中間,是沒有其他硬體存儲設備的。但是在存儲計算分離架構下,Buffer Pool 可能跑得非常快,它的 IO 延遲很低,但數據是存放在遠端的機器上的,我們這邊叫 Remote IO。需要通過網路訪問其他機器的 SSD,在這之間有至少兩個層次的硬體存儲設備,一個是 SSD,就是本機硬碟,另外一塊是 Persistent Memory,就是持久化記憶體,這些是我們可以利用起來的。我們把這一類存儲用作 secondary cache,通過這種方式能夠有效的減緩 IO Bound 場景下 Buffer Pool 命中率低的問題,因為我們可以緩存很多的熱數據,能夠加速數據的訪問。
通過測試可以看到,隨著數據量的增大,整個性能提升還是比較明顯的,在很多場景下性能可以提升到百分之一百以上,達到一倍多。
當然這個問題也有其他的解決方案,有些產品用的是水平擴展 DRAM,類似於我們的 Buffer Pool,就是把 Buffer Pool 放在遠端的機器上,通過更好的 RDMA 來訪問這部分記憶體,而且這個記憶體可能分散在多台機器上,這種方式也能減緩 IO Bound 場景的一些開銷。
但相對來講,我個人認為使用 Secondary cache 這種方式系統的整體應用成本更低一點,因為畢竟記憶體的成本比 SSD 的成本高的多,尤其是非易失性記憶體,像 3D Xpoint,慢慢流行起來之後,價格是慢慢降低的。用二級緩存的方式,整體的實例成本能夠下降非常多。
極致伸縮
還有一個優化是極致伸縮,我們把存儲功能下放到存儲層之後,存儲層會有存儲池這樣一個概念。
有一些邏輯跟傳統 RDS 方式是類似的,比如段管理還是以 1M 的 extent 為粒度來管理。也有些邏輯有很大的差異,比如我們把存儲空間的擴展,整個 offload 到存儲層,整個空間都池化。當我們發現某個 extent 裡面的所有頁面都回收了之後,變成了一個 Free Extent,就可以物理上真正的把它刪除,而不只是標記為刪除。通過這種方式真正刪除之後,客戶的存儲成本就降低下來了,也就是能夠實現按需計費的能力。
極速備份回檔
極速備份回檔的實現包含兩部分:備份、回檔。
此前提到,我們的數據是分散到分散式存儲組件上的,分散式存儲包含很多的存儲節點,而且它本身還有一定的計算能力。當實例需要做備份的時候,每個存儲節點都可以獨自的去做備份,我們叫自治備份,它可以持續的做備份。當我們需要全局一致的備份位點時,可以由計算節點來負責協調,通過一些特殊的命令,或者日誌來通知所有存儲層的節點基於快照做一個統一的備份。
跟備份相反的一個操作是回檔,基於備份再把實例的數據恢復到某個時間點,回檔也是並行回檔的,每個計算節點都可以獨立的做自己的回放。
針對極速備份回檔的測試數據看,1TB 的備份時間,RDS 實例需要 61 分鐘,TDSQL-C 只需要 21 分鐘就夠了。1TB 的回檔恢復時間,RDS 需要 168 分鐘之多,但 TDSQL-C 22 分鐘就可以恢復出來。
Instant DDL
還有一些優化是功能性優化,包含 Instant DDL 和並行構建索引。
Instant DDL 是 MySQL 8.0 新增的一個功能,它指的是在處理新增列,或修改列類型,或刪除列 DDL 的時候,可以僅僅修改原數據就直接返回。
大概的原理是,比如這張表原來有三列,現在需要新增一列,變成四列,只需要在系統表裡面標記一下,這張表就從原來的三列變成了四列。之後再新寫入的數據都是按四列寫入的,原來的數據在磁碟上存的是三列的,新插入的數據會打上新格式數據的標記,原來的數據是沒有標記的,當用戶讀取的時候,返回客戶之前根據標記來決定。如果是舊數據,我們就給它補一個新的列,一般補預設值 default value;如果是新列就直接返回,通過這種方式就做到了 O(1) 的 DDL,時間非常短。
並行構建索引
接下來介紹下並行構建索引,比如 create index,或者 optimize table 的時候,都會涉及到一些表的重建。
RDS 構建索引的時候,尤其是 8.0 的相對早一點的版本,都是單線程構建的。構建過程是先掃描所有的主表數據,掃描之後,根據掃描到的每一行主表數據,再根據索引信息,生成對應的索引行,這些索引行生成後存儲到臨時文件裡面。
第二步是對這個臨時文件按照索引行的索引鍵進行排序,一般是 mergesort,完成之後把它導入到一個空的 Btree 里,這樣就完成了整個索引的構建。我們針對這塊做了並行化優化,掃描主表、外排,還有把數據導入到 Btree,這三個過程都是可以並行化的。
在第一個階段,我們基於 InnoDB 8.0 的 parallel DDL 做了並行掃描。第二步的 mergesort 我們也做了基於採樣的並行化,通過這種方式來提升並行度。第三步構建 Btree 的時候,也是可以並行化的,比如產生了八萬行的索引行,如果八併發,每一個併發線程負責一萬行數據的構建。最後再把形成的八個子 Btree 合併成一個大的 Btree,再去壓縮層高等等。我們測下來很多場景下能提升到兩倍以上,還有很多場景可以提高的更多。
未來演進
第一個我們在探索的演進叫 Global Database。
如上圖所示,左邊有一個 Primary 實例,這個實例讀寫節點產生了 Redo log,Redo log 需要分發到存儲層,我們現在新增了 Log Store 模塊,它負責接收和分發日誌,通過這種方式,Log Store 一定程度上可以提升日誌的響應速度和整體 Redo log 的 IO 吞吐,進一步提升寫性能。
另外一個很重要的點是 Primary 實例跟右側 Standby 實例可以通過各自的 Log Store 來建立數據複製的鏈路。通過這種方式,相當於擴展出了一個只讀節點,實現讀擴展,而且是跨 Region 的讀,因為 Standby 可以部署在另外一個 Region 上。另外我們通過這種方式實現了跨 Region 容災,這對於很多金融業務來講都是剛需。
另一個我們在探索的演進是計算下推。
根據我們的架構,存儲和計算是分開的,計算在上面,存儲在下麵,存儲不只有存儲能力,還擁有一定的計算能力,像剛纔提到的備份恢復,每個節點可以獨立持續的做備份,就是利用存儲層計算能力的一個例子。
除此之外還有很多業務邏輯也可以通過這種方式把存儲層的計算資源利用起來,第一個是頁面內計算下推。比如現在要做一個有條件的掃描,掃描到了某個頁面,這個頁面可能有一百行數據,滿足條件的有五條,可以把條件下推到存儲層,直接放在存儲層做過濾,只把這五條數據返回給計算層就可以了,這就避免了把這一百行全部讀到計算層,在計算層再做計算,減少了中間網路帶寬的消耗。
第二個是 Undo 頁面間的計算下推,我們 InnoDB 是支持 MVCC 即多版本的,舉個例子,我們現在啟動一個只讀,這個讀事務快照相對比較老,比如讀一小時之前的,當我現在去讀的時候,發現某一行數據太新了,不是我想要的那個數據,需要找到以前的版本。
這個過程在 InnoDB 裡面,需要找到這一行對應的 Undo 頁面,把它的前鏡像找出來,要讀的是 Undo 頁面記錄的前鏡像,這個過程如果放在計算節點做,需要把原始的頁面數據載入到計算節點,然後根據讀快照把 Undo 頁面找出來,再把 Undo 頁面應用到數據頁面上,產生一個舊版本的數據。這整個過程都可以在存儲層來做,我們現在也是把這個下沉下來的。
還有一個下推叫寫下推。比如我就改某個頁面 Header 部分的前幾個位元組,或者 Page Header 的某個欄位,這種情況很多是盲寫的,不需要讀出來,直接可以更新,這種情況也是可以下推的。
計算下推在存儲計算分離的架構下是很自然的一個事情,剛纔講到了存儲計算分離,它的存儲層帶有一定的計算能力,大量的計算實際上都是可以下沉到存儲層的,哪些計算可以下推並沒有很明顯的邊界。我個人覺得除了事務之外,大部分計算型的都可以下推到存儲層,甚至可以把存儲層的一些計算資源當成純粹的計算資源,不關心它是不是存數據。