從NoSQL到NewSQL,談交易型分佈式數據庫建設要點

在上一篇文章《從架構特色到功能缺陷,從新認識分析型分佈式數據庫》中,咱們完成了對不一樣「分佈式數據庫」的橫向分析,本文Ivan將講述拆解的第二部分,會結合NoSQL與NewSQL的差別,從縱向來談談OLTP場景「分佈式數 據庫」實現方案的關鍵技術要點。本文既是前文的延伸,同時也算是分佈式數據庫專題文章的一個總綱,其中的要點Ivan以後也會單獨撰文闡述。數據庫

特別說明:本文是原創文章,首發在DBAplus社羣,轉載須得到做者贊成。apache


1、NewSQL & NoSQL服務器

NewSQL是本專題關注的重點,也是前文中特指的「分佈式數據庫」,其適用於OLTP場景,具備高併發低延遲的特色,特性接近Oracle/DB2等傳統數據庫,依賴通用X86服務器實現性能上的水平拓展,可以扛住海量交易的性能壓力。網絡

目前具備較高知名度的NewSQL有Google的Spanner / F一、阿里的OceanBase、CockroachDB、TiDB。其中後二者是正在成長中的開源項目,2018年相繼發佈了2.0版本。數據結構

NewSQL與NoSQL有很深的淵源,因此下文在對NewSQL的介紹中會穿插一些NoSQL對應的實現技術方式。架構

1.存儲引擎併發

B+ Treeapp

B+樹是關係型數據庫經常使用的索引存儲模型,可以支持高效的範圍掃描,葉節點相關連接而且按主鍵有序,掃描時避免了耗時的遍歷樹操做。B+樹的侷限在於不適合大量隨機寫場景,會出現「寫放大」和「存儲碎片」。框架

如下借用姜承堯老師書中的例子[1]來講明B+樹的操做過程↓異步

存在高度爲2的B+樹,存儲在5個頁表中,每頁可存放4條記錄,扇出爲5。下圖展現了該B+ Tree的構造,其中略去了葉子節點指向數據的指針以及葉子節點之間的順序指針:

B+樹由內節點(InterNode)和葉節點(LeafNode)兩類節點構成,後者攜帶指向數據的指針,而前者僅包含索引信息。

當插入一個索引值爲70的記錄,因爲對應頁表的記錄已滿,須要對B+樹從新排列,變動其父節點所在頁表的記錄,並調整相鄰頁表的記錄。完成從新分佈後的效果以下:

變動過程當中存在兩個問題:

  • 寫放大

    本例中,邏輯上僅須要一條寫入記錄(黃色標註),實際變更了3個頁表中的7條索引記錄,額外的6條記錄(綠色標註)是爲了維護B+樹結構產生的寫放大。

注: 寫放大(Write Amplification):Write amplification is the amount of data written to storage compared to the amount of data that the application wrote,也就是說實際寫入磁盤的數據大小和應用程序要求寫入數據大小之比

  • 存儲不連續

    新 增葉節點會加入到原有葉節點構成的有序鏈表中,總體在邏輯上是連續的;但磁盤存儲上,新增頁表申請的存儲空間與原有頁表極可能是不相鄰的。這樣,在後續包 含新增葉節點的查詢中,將會出現多段連續讀取,磁盤尋址的時間將會增長。進一步來講,在B+Tree上進行大量隨機寫會形成存儲的碎片化。

在 實際應用B+Tree的數據庫產品(如MySQL)中,一般提供了填充因子(Factor Fill)進行鍼對性的優化。填充因子設置太小會形成頁表數量膨脹,增大對磁盤的掃描範圍,下降查詢性能;設置過大則會在數據插入時出現寫擴大,產生大量 的分頁,下降插入性能,同時因爲數據存儲不連續,也下降了查詢性能。

LSM-Tree

LSM-Tree(Log Structured-Merge Tree)由Patrick O'Neil首先提出,其在論文[2]中系統闡述了與B+樹的差別。然後Google在Bigtable中引入了該模型,以下圖所示:

LSM-Tree的主要思想是藉助內存將隨機寫轉換爲順序寫,提高了寫入性能;同時因爲大幅度下降了寫操做對磁盤的佔用,使讀操做得到更多的磁盤控制權,讀操做性能也並未受到過多的影響。

寫操做簡化過程以下:

  • 當寫入請求到達時,首先寫入內存中Memtable,處理增量數據變化,同時記錄WAL預寫日誌;
  • 內 存增量數據達到必定閾值後,當前Memtable會被凍結,並建立一個新的Memtable,已凍結的Memtable中的數據會被順序寫入磁盤,造成有 序文件SSTable(Sorted String Table),這個操做被稱爲Minor Compaction(在HBase中該操做稱爲Flush操做,而Minor Compaction有其餘含義);
  • 這些SSTable知足必定的規則後進行合併,即Major Compaction。每一個Column Family下的全部SSTable被合併爲一個大的SSTable。

該模型規避了隨機寫的IO效率問題,有效緩解了B樹索引的寫放大問題,極大的提高了寫入效率。

NoSQL普遍使用了LSM-Tree模型,包括HBase、Cassandra、LevelDB、RocksDB等K/V存儲。

固然LSM-Tree也存在自身的缺陷:

  • 首先,其Major Compaction的操做很是重影響聯機讀寫,同時也會產生寫放大。由於這個緣由,HBase的使用中一般會禁止系統自動執行Major Compaction。

註釋:

Major Compaction操做的意義是下降讀操做的時間複雜度。設系統包含多個SSTable文件,共有N數據,SSTable平均包含m數據。

執行讀操做時,對單一SSTable文件採用折半查找方法的時間複雜度爲O(log2m),則總體時間複雜度爲O(N/m* log2m);合併爲一個SSTable後,時間複雜度可下降到O(log2N)

  • 其次是對讀效率的影響,由於SSTable文件均處於同一層次,根據批量寫的執行時序造成若干文件,因此不一樣文件中的Key(記錄主鍵)會出現交叉重疊,這樣在執行讀操做時每一個文件都要查找,產生非必要的I/O開銷。

  • 最後是空間上的放大(Space Amplification),最壞狀況下LSM Tree須要與數據大小等同的自由空間以完成Compact動做,即空間放大了100%,而B+樹的空間放大約爲1/3。

Leveled LSM Tree

Leveled LSM Tree 的變化在於將SSTable作了進一步的分層,下降寫放大的狀況,縮小了讀取的文件範圍,在LevelDB 中率先使用,隨後Cassandra 1.0也引入了該策略[3]。

SSTable的層次化設計策略是:

  • 單個SSTable文件大小是固定的,在Cassandra中默認設置爲5M;
  • 層 級從Level 0開始遞增,存儲數據量隨着層級的提高而增加,層級之間有一致的增加係數(Growth Factor)。Cassandra中Growth Factor設置爲10,Level 1文件爲1-10M則Level 2 文件爲10-100M,這樣10TB數據量會達到Level 7;
  • Level 0的SSTable比較特殊,固定爲4個文件,且文件之間存在Key交叉重疊的狀況,從Level 1開始,SSTable再也不出現Key交叉狀況;
  • Level 0層的SSTable超過容量大小時,向Level 1 Compaction,由於存在Key交叉,因此要讀取Level 0的全部SSTable;當Level 1 的文件大小超過閾值時,將建立Level 2的SSTable並刪除掉Level 1原有的SSTable;當Level 1的Key範圍對應Level 2的多個SSTable時,要重寫多個SSTable,但因爲SSTable的大小固定,因此一般只會涉及少數的SSTable。
Level間Compact操做

多個有序的SSTable,避免了Major Compaction這樣的重量級文件重寫,每次僅更新部份內容,下降了寫放大率。

對於讀取元數據來鎖定相關的SSTable,效率顯然超過了對全部SSTable的折半查找和Bloom Filter。所以,讀取效率獲得了顯著提高,按照某種評估方式[3],在每行數據大小基本相同的狀況下,90%的讀操做僅會訪問一個SSTable。

該策略下,Compaction的操做更加頻繁,帶來了更多I/O開銷,對寫密集型操做而言,最終結果是否可以獲得足夠的效率提高存在不肯定性,須要在應用中權衡。

NewSQL的策略

NewSQL 數據庫的存儲層廣泛都採用K/V存儲,因此基本沿用了LSM Tree模型。其中CockroachDB和TiDB都在KV層使用RocksDB。OceanBase採用了不一樣的方法規避Major Compaction的影響,大致是利用閒置的副本(Follower)進行Compaction操做,避免了對讀操做的阻塞,在Compaction完 成後,進行副本的角色的替換。

同時,K/V存儲引擎仍然在繼續發展中,一些其餘的改進如分形樹(Fractal Tree)等,限於篇幅咱們就不在此展開了。

2.分片

分片(Sharding)概念與RDBMS的分區相近,是分佈式數據庫或分佈式存儲系統的最關鍵特性,是實現水平擴展的基礎,也在NoSQL類系統中獲得了大量運用。

分片的目標是將數據儘可能均勻地分佈在多個節點上,利用多節點的數據存儲及處理能力提高數據庫總體性能。

Range&Hash

雖然不一樣的系統中對分片策略有不少細分,但大體能夠概括爲兩種方式,Range和Hash。

Range分片有利於範圍查詢,而Hash分片更容易作到數據均衡分佈。在實際應用中,Range分片彷佛使用得更多,但也有不少應用會混合了兩種分片方式。

HBase採用了Range方式,根據Rowkey的字典序排列,當超過單個Region的上限後分裂爲兩個新的Region。Range的優勢是數據位置接近,在訪問數據時,範圍查找的成本低;缺點也比較明顯,在容易出現熱點集中的問題。

例 如,在HBase一般不建議使用業務流水號做爲RowKey,由於連續遞增的順序號在多數時間內都會被分配到同一個RegionServer,形成併發訪 問同時競爭這個RegionServer資源的狀況。爲了不該問題,會建議將RowKey進行編碼,序號反轉或加鹽等方式。這種方式實質上是使用應用層 的設計策略,將Range分片轉換成相似Hash分片的方式。

Spanner的底層存儲沿用了BigTable的不少設計思路,但在分片上有所調整,在Tablet內增長了Directory的動態調配來規避Range分片與操做熱點不匹配的問題,後續在事務管理部分再詳細描述。

靜態分片&動態分片

按照分片的產生策略能夠分爲靜態分片和動態分片兩類。

靜態分片在系統建設之初已經決定分片的數量,後期再改動代價很大;動態分片是指根據數據的狀況指定的分片策略,其變動成本較低,能夠按需調整。

傳 統的DB + Proxy方案,進行水平分庫分表就是一種常見的靜態分片。咱們熟知的幾個互聯網大廠在大規模交易系統中都進行過相似的設計,默認將數據作成某個固定數量 的分片,好比100、25五、1024,或者其它你喜歡的數字。分片的數量能夠根據系統預期目標的總體服務能力、數據量和單節點容量進行評估,固然具體到 100片合適仍是1024片合適,多少仍是有拍腦殼的成分。

在NoSQL中,Redis集羣也採用一樣的靜態分片方式,默認爲16384個哈希槽位(等同於分片)。

靜 態分片的缺點是分片數量已經被肯定,基於單點處理能力造成一個容量的上限;靈活性較差,後續再作分片數量調整時,數據遷移困難,實現複雜。優勢也很明顯, 靜態分片的策略幾乎是固化的,所以對分區鍵、分區策略等元數據管理的依賴度很低,而這些元數據每每會造成分佈式數據庫中的單點,成爲提高可靠性、可用性的 障礙。

相比之下,動態分片的靈活性更好,適用於更豐富的應用場景,因此NewSQL也主要採用動態分片方式,代價則是對元數據管理的複雜度增長。

在分片處理上,NoSQL與NewSQL面臨的問題很是接近。

3.副本

首先是因爲通用設備單機可靠性低,必需要經過多機副本的方式。本文中關注兩個問題:一是副本一致性;二是副本可靠性與副本可用性的差別。

數據副本一致性

多 副本必然引入了副本的數據一致性問題。以前已經有大名鼎鼎的CAP理論,相信你們都是耳熟能詳了,但這裏要再囉嗦一句,CAP裏的一致性和事務管理中的一 致性不是一回事。Ivan遇到過不少同窗有誤解,用CAP爲依據來證實分佈式架構不可能作到事務的強一致性,而只能是最終一致性。

事務的一致性是指不一樣數據實體在同一事務中一塊兒變動,要麼所有成功,要麼所有失敗;而CAP中的一致性是指原子粒度的數據副本如何保證一致性,多副本在邏輯上是同一數據實體。

副本同步大體概括爲如下三種模式:

  • 強同步:即 在多個副本都必須完成更新,數據更新才能成功。這種模式的問題是高延時、低可用性,一次操做要等待全部副本的更新,加入了不少網絡通信開銷,增長了延時。 多個副本節點必須都正常運行的狀況下,整個系統纔是可用的,任何單點的不可用都會形成整個系統的不可用。假設單點的可用性是95%,則三個節點的構成的多 副本,其可靠性爲95% * 95% * 95% = 85.7%。所以雖然Oracle/MySQL等主流數據庫都提供了強同步方式,但在企業實際生產環境中不多有應用。

  • 半同步:MySQL提供了半同步方式,多個從節點從主節點同步數據,當任意從節點同步成功,則主節點視爲成功。這個邏輯模型有效規避了強同步的問題,多節點可用性的影響從「與」變爲「或」,保障了總體的可用性。但遺憾的是在技術實現上存在瑕疵,會有退化爲異步的問題。

  • Paxos/Raft:該 方式將參與節點劃分爲Leader/Follower等角色,主節點向多個備節點寫入數據,當存在一半以上節點寫入成功,即返回客戶端寫入成功。該方式可 以規避網絡抖動和備節點服務異常對總體集羣形成的影響。其餘像Zookeeper的ZAB協議,Kafka的ISR機制,雖然與Paxos/Raft有所 區別,但大體是一個方向。

副本可靠性與副本可用性

數據副本僅保證了數據的持久性,即數據不丟失。咱們還面臨着副本的可用性問題,即數據是否持續提供服務。以HBASE-10070爲例來講明這個問題:

HBase經過分佈式文件系統HDFS實現了數據多副本的存儲,可是在提供服務時,客戶端是鏈接到RegionServer進而訪問HDFS上的數據。由於一個Region會被惟一的RegionServer管理,因此RegionServer仍然是個單點。

在 RegionServer宕機時,須要在必定的間隔後才被HMaster感知,後者再調度起一個新的RegionServer並加載相應的Region, 整個過程可能達到幾十秒。在大規模集羣中,單點故障是頻繁出現的,每一個單點帶來幾十秒的局部服務中斷,大大下降了HBase的可用性。

爲了解決這問題,HBase引入從RegionServer節點的概念,在主節點宕機時,從節點持續提供服務。而RegionServer並非無狀態服務,在內存中存儲數據,又出現了主從RegionServer間的數據同步問題。

HBase實現了數據的可靠性,但仍不能充分實現數據的可用性。CockroachDB和TiDB的思路是實現一個支持Raft的分佈式KV存儲,這樣徹底忽略單節點上內存數據和磁盤數據的差別,確保數據的可用性。

4.事務管理

分佈式事務處理因爲其複雜性,是NoSQL發展中最早被捨棄的特性。但因爲大規模互聯網應用普遍出現,其現實意義逐漸突出,又從新成爲NewSQL沒法規避的問題。隨着NewSQL對事務處理的完善,也讓過去十餘年數據庫技術的演進終於實現了一個接近完整的上升螺旋。

鑑於分佈式事務管理的複雜性,Ivan在本文中僅做簡要說明,後續文章中會進一步展開。

NewSQL 事務管理從控制手段上分爲鎖(Lock-Base)和無鎖(Lock-Free)兩種,其中,無鎖模式一般是基於時間戳協調事務的衝突。從資源佔用方式 上,分爲樂觀協議和悲觀協議,二者區別在於對資源衝突的預期不一樣:悲觀協議認爲衝突是頻繁的,因此會盡早搶佔資源,保證事務的順利完成;樂觀協議認爲衝突 是偶發的,只在能夠容忍的最晚時間纔會搶佔資源。

如下經過最經典的「兩階段提交協議」和具體的兩種應用實踐,來具體闡述實現方式:

兩階段提交協議(2PC)

兩階段提交協議(Tow phase commit protocol,2PC)是經典的分佈式事務處理模型,處理過程分爲兩個階段:

請求階段:

  • 事務詢問。協調者向全部參與者發送事務內容,詢問是否能夠執行事務提交操做,並開始等待各參與者的相應;
  • 執行事務。各參與者節點執行事務操做,並將Undo和Redo信息記入事務日誌中;
  • 各參與者向協調者反饋事務詢問的響應。若是參與者成功執行了事務操做,那麼就反饋給協調者Yes,表示事務能夠執行;若是參與者沒有成功執行事務,那麼就反饋給No,表示事務不能夠執行。

提交階段:

  • 提交事務。發送提交請求。協調者向全部參與者節點發出Commit請求;
  • 事務提交。參與者接到Commit後,會正式執行事務提交操做,並在完成提交以後釋放在整個事務執行期間佔有的事務資源;
  • 反饋事務提交結果。參與者在完成事務提交後,向協調者發送Ack消息;
  • 完成事務。協調者收到全部參與者反饋的Ack消息後,完成事務;
  • 中斷事務。發送回滾請求。協調者向全部參與者發出Rollback請求;
  • 事務回滾。參與者接收到Rollback請求後,會利用其階段一記錄的Undo信息來執行事務回滾操做,並在完成回滾以後釋放在整個事務執行期間佔有的事務資源;
  • 反饋事務回滾結果。參與者在完成事務回滾後,向協調者發送Ack消息;
  • 中斷事務。協調者接收到全部參與者反饋的Ack消息後,完成事務中斷。

該模型的主要優勢是原理簡單,實現方便。

缺點也很明顯,首先是同步阻塞,整個事務過程當中全部參與者都被鎖定,必然大幅度影響併發性能;其次是單點問題,協調者是一個單點,若是在第二階段宕機,參與者將一直鎖定。

Spanner

根據Spanner論文[4]的介紹,其分佈式事務管理仍採用了2PC的方式,但創新性的設計是改變了Tablet的數據分佈策略,Tablet再也不是單一的連續Key數據結構,新增了Directory做爲最小可調度的數據組織單元。

經過動態的調配,下降事務內數據跨節點分佈的機率。

Ivan將這種事務處理的設計思想理解爲:「最好的分佈式事務處理方式,就是不作分佈式事務處理,將其變爲本地事務」。在OceanBase的早期版本中也採用了一個獨立的服務器UpdateServer來集中處理事務操做,理念有相近之處。

Percolator

Percolator[5] 是Google開發的增量處理網頁索引系統,在其誕生前,Google採用MapReduce進行全量的網頁索引處理。這樣一次處理的時間取決於存量網頁 的數量,耗時很長;並且即便某天只有少許的網頁變動,一樣要執行全量的索引處理,浪費了大量的資源與時間。採用Percolator的增量處理方式後,大 幅度減小了處理時間。

在這篇論文中給出了一個分佈式事務模型,是「兩階段提交協議」的變形,其將第二階段的工做簡化到極致,大幅提高了處理效率。

具體實現上,Percolator是基於BigTable實現分佈式事務管理,經過MVCC和鎖的兩種機制結合,事務內全部要操做的記錄均爲新增版本而不更新現有版本。這樣作的好處是在整個事務中不會阻塞讀操做。

  • 事務中的鎖分爲主(Primary)和從鎖(Secondary),對事務內首先操做的記錄對加主鎖,然後對事務內的其餘記錄隨着操做過程逐步加從鎖並指向主鎖記錄,一旦遇到鎖衝突,優先級低的事務釋放鎖,事務回滾;
  • 事務內的記錄所有更新完畢後,事務進入第二階段,此時只須要更新主鎖的狀態,事務便可結束;
  • 從鎖的狀態則依賴異步進程和相關的讀操做來協助完成,因爲從鎖記錄上保留了指向主鎖記錄的指針,異步進程和讀操做都很容易判斷從鎖的正確狀態,並進行更新。

分佈式事務管理的其餘內容,包括無鎖事務控制、全局時鐘的必要性等等,待後續文章中再討論。

2、結語

本文最初的立意是面向幾類典型技術背景的同窗,對「分佈式數據庫」展開不一樣方向的解讀,並就其中部分技術要點進行闡述,使不一樣技術領域的同窗可以對相關技術有些許瞭解,爲有興趣深刻研究的同窗作一個鋪墊。

隨着分析的深刻愈發以爲文章框架過於龐大難於駕馭,於是對關鍵技術的解讀也存在深淺不一的狀況。對於本文中未及展開的部分,Ivan會盡力在後續系列文章中予以補充,水平所限文中必有錯漏之處,歡迎你們討論和指正。

文獻參考:

[1] 姜承堯, MySQL技術內幕:InnoDB存儲引擎機, 械工業出版社, 2011

[2] Patrick O'Neil The Log-Structured Merge-Tree

[3] Leveled Compaction in Apache Cassandra

https://www.datastax.com/dev/blog/leveled-compaction-in-apache-cassandra

[4] James C. Corbett, Jeffrey Dean, Michael Epstein, et al. Spanner: Google's Globally-Distributed Database

[5] Daniel Peng and Frank Dabek, Large-scale Incremental Processing Using Distributed Transactions and Notifications

相關文章
相關標籤/搜索