導讀:隨着大數據的進一步發展,NoSQL 數據庫系統迅速發展並獲得了普遍的應用。其中,Apache Cassandra 是最普遍使用的數據庫之一。對於 Cassandra 的優化是你們研究的熱點,而 ScyllaDB 則爲其提供了一個新的思路。ScyllaDB 是一個基於 C++ 的開源的高性能的 Cassandra 的實現,較之 Cassandra 在性能上有了很大的提高。Nodetool repair 是 Cassandra 平常維護的重要一環,今天主要和你們分享一下 ScyllaDB 在這方面的優化。node
今天的介紹會圍繞下面五點展開:算法
ScyllaDB 介紹數據庫
Row level repair 介紹緩存
Row level repair 實現網絡
實驗結果數據結構
總結框架
▌ScyllaDB 介紹運維
首先給你們簡單介紹一下 ScyllaDB:性能
咱們公司是一傢俱備較多的底層軟件開發經驗的公司,團隊創始人是 KVM 和 OSv 的做者。對於 Cassandra 數據庫的優化,咱們進行了一系列嘗試。最開始是從操做系統的角度,經過提升操做系統的性能來提升 Cassandra 應用的性能,其效果是提升了 Cassandra 約20%的性能而沒法再得到更高的性能提高。爲了更好地優化 Cassandra,團隊開始思考是否能夠從新實現 Cassandra。咱們首先開發了一個很是高性能的 C++ 的開源框架 Seastar,而後基於 Seastar 框架改寫的 Cassandra 數據庫,即 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 ( 行級修復 )
首先介紹一下什麼是 repair:
修復是 Cassandra 中一個重要的維護操做。
Cassandra 中的數據具備多個副本,有可能在寫操做發生時某些節點不在線,從而拿不到數據的副本,致使了數據的不一致。這種狀況發生時,須要讓各個節點的數據一致,即 repair。
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。
其問題在於,須要兩次讀取數據,讀磁盤開銷較高。
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 是一個通用的高效的數據修復方法,但願能對你們有所幫助。