通用高效的數據修復方法:Row level repair

導讀:隨着大數據的進一步發展,NoSQL 數據庫系統迅速發展並得到了廣泛的應用。其中,Apache Cassandra 是最廣泛使用的數據庫之一。對於 Cassandra 的優化是大家研究的熱點,而 ScyllaDB 則爲其提供了一個新的思路。ScyllaDB 是一個基於 C++ 的開源的高性能的 Cassandra 的實現,較之 Cassandra 在性能上有了很大的提升。Nodetool repair 是 Cassandra 日常維護的重要一環,今天主要和大家分享一下 ScyllaDB 在這方面的優化。

今天的介紹會圍繞下面五點展開:

  • ScyllaDB 介紹
  • Row level repair 介紹
  • Row level repair 實現
  • 實驗結果
  • 總結

ScyllaDB 介紹

首先給大家簡單介紹一下 ScyllaDB:

  1. ScyllaDB 的產生背景

我們公司是一傢俱有較多的底層軟件開發經驗的公司,團隊創始人是 KVM 和 OSv 的作者。對於 Cassandra 數據庫的優化,我們進行了一系列嘗試。最開始是從操作系統的角度,通過提高操作系統的性能來提高 Cassandra 應用的性能,其效果是提高了 Cassandra 約20%的性能而無法再獲得更高的性能提升。爲了更好地優化 Cassandra,團隊開始思考是否可以重新實現 Cassandra。我們首先開發了一個非常高性能的 C++ 的開源框架 Seastar,然後基於 Seastar 框架改寫的 Cassandra 數據庫,即 ScyllaDB。

  1. ScyllaDB 的特點

ScyllaDB 是一個開源的高性能的 Cassandra 的實現,具有以下幾個特點:

  • 一個速度極快的 NoSQL 數據庫

    單個節點的 QPS 可以達到1000000;可以擴展多個節點以提高性能,99%情況下延遲低於1毫秒。

  • 使用 C++ 開發,沒有 GC

  • 每個物理 CPU 只部署一個線程,線程間無共享,無鎖

    每個物理 CPU 只部署一個線程,每個線程內部會有自己的 task,scheduler。每個線程跑完全獨立的 Cassandra 的任務,因此不同線程之間沒有共享,從而也沒有鎖。這樣的效果是獲得了較大的性能提升,並且在有較多物理核的時候具有較高的可擴展性。

  • 在用戶態實現 CPU 和 DISK scheduler,以及可選 TCP/IP 協議棧

    在用戶態實現 CPU scheduler,不同的任務設定不同的優先級,分配不同的 CPU 資源;進行了磁盤的優化,不使用操作系統的 page cache,完全 DMA 操作,實現 DISK scheduler,精確控制各個模塊 DISK 的使用情況;實現了可選的用戶態的 TCP/IP 協議棧。

  • 提供 Apache Cassandra 和 Amazon DynamoDB 的 API

Row level repair ( 行級修復 )

  1. 什麼是 repair

首先介紹一下什麼是 repair:

  • 修復是 Cassandra 中一個重要的維護操作。

    Cassandra 中的數據具有多個副本,有可能在寫操作發生時某些節點不在線,從而拿不到數據的副本,導致了數據的不一致。這種情況發生時,需要讓各個節點的數據一致,即 repair。

  • repair 的兩個步驟:

    第一步:發現不同節點數據副本之間的不一致。

    第二步:修復不一致的數據。

  1. Partition level repair ( 分區級修復 ) 的問題

  • Partition level

    目前的 Cassandra 以及 ScyllaDB 3.1 版本之前使用的是 partition level repair。爲發現數據不一致需要對一組 partitions 進行哈希得到一組 checksum。對於 Cassandra 是 Merkle tree ( 默克爾樹 ) 中的葉子節點,對於 ScyllaDB 是將 token range 進行拆分,直到每個 range 裏面有約100個 partition。

    其問題在於,哈希的粒度太大,會導致不必要的數據傳輸:

    ① 單個行的不一致會導致所有一組 partitions 在網絡上傳輸。

    ② 有些節點上的分區會很大 ( 達到 5G )。

  • Two stage repair

    Repair 過程包括兩個階段:

    ① 通過計算比較哈希值尋找不一致的數據,記錄下來不一致的節點和 token range。

    ② Streaming:根據記錄的節點和 token range 傳輸數據進行 repair。

    其問題在於,需要兩次讀取數據,讀磁盤開銷較高。

  1. Row level repair ( 行級修復 ) 介紹

  • Row level

    考慮 partition level 修復粒度太大,存在不必要數據傳輸的問題,減小修復的粒度:

    ① 對每一行進行哈希計算得到一個 checksum。

    ② 利用 set reconciliation 算法,使每個節點得到一個統一的哈希集合,包含所有應有數據的哈希值。

    ③ 只傳輸不一致的數據行。

  • Single stage repair

    Row level repair 只有一個階段,因爲通過縮小粒度,正在工作的數據集規模被大大縮小,可以完全放進內存。數據只需要從磁盤讀取一次,使用 RPC 框架傳輸緩存中的不一致數據而不需要額外的 streaming 操作,大大降低了讀數據的開銷。

下面,重點分享下 row level repair 的實現細節。

Row level repair 的實現

Row level repair 具有兩個重要元素

① Master:運行 nodetool repair 命令的節點。

② Followers:具有數據備份的節點。

Row level repair 有三個主要步驟

① 首先,master 與其 followers 協商 sync boundary ( 同步邊界 ),以便確定 repair 工作開始的 range。Sync boundary 確定了一 個range,使其中的數據行可以全部放進內存。這個 range 可以小於一個 partition,因此允許 repair 工作在一個大的 partition 的一小部分上。

② 然後,master 從 followers 獲取上述範圍內它缺少的數據行,使得 master 最終包含所有數據行。這裏缺少的數據行有兩個含義:其一是確實缺少的數據,其二是有但是內容不同的數據。

③ 最後,master 通過分析得知 followers 各自缺少的數據行,並將缺失的數據行發送給對應的 follower,最終所有 followers 包含所有的數據行。

下面詳細介紹 row level repair 的各個步驟。

步驟一:協商 sync boundary ( 同步邊界 )

首先,Master 和 followers 讀取數據直到設定大小的數據行緩存滿 ( 如 32M ),計算緩存中每行數據的哈希,並對得到的所有哈希計算一個整體哈希值 ( 如異或 )。Followers 將得到的聯合哈希值返回給 master,同時也將 sync boundary 返回給 master。這裏請注意,緩存中的數據行是有序的,緩存中的最後一行將作爲返回的 sync boundary。

然後,此時的 master 節點已經拿到了所有 followers 節點的整體哈希值和 sync boundary,master 比較這些哈希值和 sync boundary。如果所有節點哈希值一致且 sync boundary 一致,則代表此時這個 range 的數據完全一致,因此繼續處理下一個 range 的數據。如果 sync boundary 不一致,master 會選擇最小的一個作爲本輪的 sync boundary。如果選擇的不是最小的,則會導致有多餘的數據在本輪同步,違反了有限數據到內存的原則。

步驟二:移動數據到 working row buffer ( 工作緩存 )

經過第一步的操作,此時所有節點具有一個統一的 sync boundary。

首先,每個節點把 sync boundary 之內的所有數據移動到 working row buffer 中,每個 follower 會返回一個整體哈希值。

然後,master 節點檢查 working row buffer 中各個節點的整體哈希值是否一致,如果一致,則繼續處理下一個 range 的數據。如果不一致,則進行下面第三個步驟。

步驟三:得到所有數據行哈希值

如果存在節點 working row buffer 中數據的整體哈希值不一致,此時則需要對數據進行 repair。Repair 工作首先要進行的,就是得到各個節點 working row buffer 中所有數據的哈希值集合。這個過程有兩個比較常用的方法:

  • 第一種是比較暴力的方法:Master 節點要求所有 follower 節點將其所有數據行的哈希值集合發送給 master,這種方法最容易實現,並且在數據差異特別大的時候是最有效 ( 經測試,如果差異數據佔比15%以上,這種暴力方式最有效 )。
  • 第二種方法 ( 如 IDF ) 巧妙地構建一些很小的數據結構,followers 不需要傳輸所有的哈希值,而是隻發送這個數據結構讓 master 推測出其具有的數據行。這種方法在數據差異較小的時候最有效。

當拿到所有的哈希之後,master 節點就很清晰地知道了每個 follower 節點有哪些數據行,缺少那些數據行。接下來則需要進行第四個步驟,得到所有的數據行。

步驟四:獲取所有缺少的數據行

Master 節點通過第三個步驟獲得了所有節點 working row buffer 中的數據行哈希。Master 比較本節點的哈希值集合和 follower 節點的哈希值集合,得到本節點缺少的數據行,從而確定應該去每個 follower 節點獲取哪些數據,例如:

n1 節點包含數據 {1,2,3},n2 節點包含數據 {1,2,4},n3 節點包含數據 {1,4,5},n1 節點是 master 節點,則 n1 需要從 n2 獲取數據 {4},從 n3 獲取數據 {5},最終 n1 包含數據 {1,2,3,4,5}。

然後 Master 從 follower 節點讀取缺少的數據到 working row buffer,並寫進磁盤 SStables。此時,master 節點已經包含了所有的數據信息。

步驟五:將缺少的數據發送給 followers

通過前四步,master 節點已經具有了 sync boundary 以內的所有數據,接下來則需要對 follower 節點進行修復。

Master 通過比較本節點和 follower 節點 working row buffer 中數據的哈希值,確定需要將哪些數據推送到哪些節點。例如步驟四中的例子:

最開始 n1 節點包含數據 {1,2,3},n2 節點包含數據 {1,2,4},n3 節點包含數據 {1,4,5}。經過第四步, n1 包含數據 {1,2,3,4,5},n2 包含數據 {1,2,4} ,n3 包含數據 {1,2,5}。在本步驟,n1 將 {3,5} 發送給 n2,將 {2,3} 發送給 n3。

然後 follower 節點將從 master 節點接受到的數據寫進磁盤 SStables。此時 master 和 follower 節點中 sync boundary 之內的數據已經達到一致,本輪的 repair 結束。

如果還有更多數據需要 repair,則重複這五個步驟,知道所有數據修復完畢。

實驗結果

實驗測試了一個三節點集羣,每個節點包含十億行數據,大小爲 1TB,每行數據大小爲 1KB,每個分區只有一行數據。測試包含三個場景:

  • 第一個場景是,其中一個節點完全沒有數據,另外兩個節點有相同的數據,模擬節點的重建。
  • 第二個場景是,各個節點數據完全一致,用來測試數據一致的情況下的 repair 操作的速度。
  • 第三個場景是最常見的情況,即各個節點大量數據是一致的,只有千分之一的數據行不一致。因爲在集羣運維過程中,是比較推薦定期 repair 的,如果定期 repair,實際數據差異是比較小的。

第三種場景,row level repair 速度獲得了六倍的提升,原因是爲什麼呢?

第三種場景,row level repair 速度獲得了六倍的提升,原因是爲什麼呢?我們對 master 節點需要真正傳輸和接受的數據量做了一個統計,row level repair 只有 partition level repair 的約4%,數據傳輸量的大幅度減少是速度提升的最主要原因。

綜上,從實驗結果分析得知,row level repair 速度快的原因有三個方面:

  • 沒有多餘的數據傳輸,本次實驗每個 partition 只有一行數據,當每個 partition 中數據更多時,速度提升會更多。
  • 更快的哈希方法,partition level repair 數據量比較多,因此使用效果更強的256位加密 SHA256 哈希方法,而 row level repair 數據量較少,因此使用64位無加密 xxhash,因此速度更快。
  • 提高了 repair 的並行度,partition level repair 只並行修復一個 token range 的數據,但是 row level repair 同時並行修復更多的 token range 的數據。Scylla 3.1 是16個,3.2更加優化,可以根據每個節點的內存情況確定並行數量。在未來的版本中,還會進一步優化,根據節點之間網絡的延遲自動計算一個最優的並行數。這一點對於跨 DC 的集羣非常有用,跨 DC 的集羣節點之間延遲較高。在這種情況下,爲了提高 repair 的吞吐量,需要提高有效緩存的大小。

總結

通過前面的介紹,大家應該對 row level repair 有了大致的瞭解。Row level repair 降低了數據修復的粒度,從分區粒度到單行數據的粒度,減少了數據的傳輸量,減少了數據修復所需的時間,並且減少了 IO 請求,減少了磁盤的讀取。

綜上,Row level repair 是一個通用的高效的數據修復方法,希望能對大家有所幫助。

作者介紹

Asias He

ScyllaDB | Software Developer

本文來自 DataFunTalk

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzU1NTMyOTI4Mw==&mid=2247495812&idx=1&sn=060ecde872f69ecee56209fd3b8cdfbf&chksm=fbd742e8cca0cbfe23011857cade5b33803d9105bf86c9db8601078d5058985bb326ac424e79&scene=27#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章