閱讀223 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Amazon Aurora 讀後感

        幾個月前,AWS在SIGMOD 17上發表了《Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases》,詳細解讀了Aurora的背景、設計思想以及效果。即使本人不搞關係數據庫,也沒有讀過MySQL內核源碼,讀完後也覺得可以從中學習到很多東西,記錄於此。一些理解不正確的地方,也歡迎討論。

        傳統關係數據庫三宗罪,擴展性、可用性和schema 變更(前兩條普適各類存儲係統)。經常見到的方案中 ,人們用分庫分表/讀寫分離等解決擴展性,用主備策略(sync/semi-sync/async/backup|restore)解決可用性,用數據重寫/提前定義多餘列/json支持等解決schema變更。這些解決方案一般是業務感知的,有時也需要業務做痛苦的取舍(保數據不丟還是保服務可用?)。一些新係統的出現一般都是解決了其中一些問題,比如著名的BigTable[2](包括開源的HBase[3]、阿裏自研的表格存儲[4])相當程度的解決了擴展性,可用性,直接摒棄了schema,但是不提供關係數據庫事務。Spanner[5]、OceanBase[6]、TiDB[7]、CockroachDB[8]等除了解決上述問題,還提供了事務,但是在MySQL協議兼容方麵很難做到100%。Aurora[1]和雲棲大會上剛剛推出的PolarDB[9]則選擇了另外的路線,借助於已有的MySQL引擎,做到了MySQL功能無損,同時通過計算存儲分離以及對存儲提供擴展性解決了讀擴展以及可用性問題,通過versioned schema解決schema變更問題(Aurora論文描述)。廢話說完,開始學習Aurora。

        文中描述,數據庫中有些操作很難並行,比如disk read/transaction commit,一旦這些操作出現了fail或者毛刺,對係統影響很大;還有多階段同步協議(比如2PC)對failure容忍度不夠好,而雲環境下failure是常態,所以實現這些協議有挑戰。基於這些基本的考慮,Aurora的計算存儲分離變成了,計算包括query processor, transactions, locking, buffer cache, access methods and undo management,而存儲包括redo logging, durable storage, crash recovery, and backup/restore。為什麼undo log不下沉呢,是因為這個問題不重要還是複雜度高?個人覺得undo log的處理跟一致性密切相關,也許不是那麼容易下沉的。圖1描述了這種架構,左上的可以看成計算層,左下是專為Aurora打造的智能分布式存儲層(有數據庫邏輯在裏麵),由多個AZ(可理解為機房,每個AZ具有獨立的物理設施,通過高速網絡互聯),每個AZ內多個存儲節點組成。注意VPC,其實有3個VPC,第三個是control panel跟實例之間的,作為公有雲,安全是任何時候都要重視的問題。

0?wx_fmt=png

        文章正文主要介紹了其三個貢獻,本文也主要學習這三個點。

        第一個是如何設計quorum係統以抵抗關聯失敗(correlated failure)。Aurora一般部署在3AZ,關聯失敗的例子是一個AZ掛了,然後另外一個AZ的某台機器/磁盤也掛了。Quorum協議是[10],是NWR的理論基礎。基本約束是N台機器,寫成功要大於N/2,讀+寫要大於N。Aurora對容錯的目標是:a) 如果一個AZ掛了,不影響寫(除了掛掉的AZ外,另外2AZ的讀當然也不影響);b) 如果一個AZ掛了,同時剩餘2個AZ中又有一個機器/磁盤等掛了,不丟數據。如何保證這個目標呢?其做法是3AZ,每個AZ寫2個replica,任何時候,隻要4個replica寫成功,就可以認為這份寫是安全的。讀的時候則要求讀3,這樣讀跟寫至少有一個交集,保證讀到最新數據。此時很容易推導出來前麵兩個目標是能夠達到的。任何一個AZ掛了,仍然會有4個replica可用,寫不會中斷,但是此時網絡/磁盤抖動的影響可能會很可觀。如果再有一個replica掛了,數據不會丟失,因為還有跨兩個AZ的3份replica存在,滿足讀3個要求。

        這裏討論一個問題,3個AZ,其中2個AZ寫2份,一個AZ寫1份,是不是也能滿足上述目標?我想也是可以的。此時寫要求>=3,讀要求3。舉例,比如AZ-1,AZ-2要求寫2份replica,AZ-3寫一份replica。a) 當AZ-1或者AZ-2掛,不影響寫;AZ-3掛了當然也不影響寫;b) 當AZ-1掛了,此時AZ-2一個node也掛了,則無法提供寫,AZ-2可以從本AZ的存活節點快速恢複;如果此時AZ-3一個node掛了,無法提供寫,但是AZ-3隻有一個節點,需要向AZ-2請求replica。所以該方案的缺點是

  1. 某些情況下,replication流量會跨AZ

而該方案優點是,

  1. 關聯失敗場景下,部分實例能提供寫:比如AZ-3掛了,此時AZ-1或者AZ-2一個node掛了,不影響寫,在這種概率下,比文中方案具備更高的可用性。而如果對不同實例來說,2-2-1結構被打散在各個AZ,那麼是否可以認為任何時候,一個AZ掛了,另外一個AZ有一個node掛了,此時總有1/3的實例是能提供寫的

  2. 5份replica而不是6份節約了存儲空間以及IOPS,利於性能提升

為什麼不采用類似方案呢?我猜有兩個理由,

  1. 文章一開頭就說網絡是新的約束,所以對AZ間網絡流量應該盡可能節省

  2. 作者將重點放在了快速恢複上,盡最大努力降低MTTR(Mean Time to Repair)。實現方式是,將實例存儲分為以10G為單位的segment。那麼對每個segment內的replica而言,如果node掛了要恢複,在萬兆網卡下,隻要10s就可以(我懷疑這個值沒有包括健康檢測時間)。而且,還可以通過將10G變得更小進一步提高MTTR,或者用更快的網絡。既然這麼快,上麵優勢中的第一條就不是很重要了。

所以,考慮到工程複雜度(分布式係統裏麵太容易出錯了,複雜度是一個重要決策依據),選擇每個AZ 2個replica是合適的。不過從源頭再想一想,關聯失敗場景下10s不可寫是否需要進一步提高?如果要求如此苛刻,可能已經算不上Aurora的客戶了。

        接下來討論第二個問題,為什麼以及如何將存儲下沉?圖2展示了經典場景下MySQL主備在AWS上的工作模式。

0?wx_fmt=png

 

        從圖中可以看到,寫需要經過MySQL engine,EBS的primary/secondary(文中描述是chain replication,所以這個延時應該很可觀)。主實例完成後,block 被replicate到隻讀實例,其也要經曆類似步驟,忍受EBS同步的延時。為了數據安全,作者舉了一個延時最差的例子。文章認為這個流程中最大的問題有兩個,

  1. 不少操作是串行的,比如1,3,5,這會增加延時且碰到毛刺的概率也高

  2. 一個事務操作寫了太多份數據出去,比如redo log、double write、replicated block

        解決思路見圖3。對問題1,通過將chain變成star緩解串行操作的延時和毛刺效應;對問題2,通過隻寫redo減少寫出數據量。

0?wx_fmt=png

        既然隻有redo下沉到了存儲節點上,那麼存儲節點就得知道如何將redo變成page data,所以log applicator下沉也是必然的。不過這裏有一個有意思的設計,redo log是可以直接當做page data用的(redo本來就是page change)。如果隻是從正確性而言,每次啟動後將所有的log apply一遍就可以,這顯然太重了。現實中,很容易走另一個極端,就是redo來了,直接變成data,隻要data 持久化完畢,redo就丟掉。對於那些很少修改的page來說,從redo 變成data是一種浪費,隻有那些變動頻繁的page會被變成data(materialization ),其餘依舊保留redo的形態,參與讀流程。這個設計也許可以稱為lazy page materialization,將合並推遲到read參與的時候或者page change 太多的時候,有機會減少隨機IO。lazy是係統優化中的一個重要手段,一般是係統設計之初就體現在頂層設計中。

        試驗證明,redo log下沉的設計比起經典模式,每個事務平均IO次數降低了85%+,而且數據安全性也是類似甚至更高的。

        同時,crash recovery也更快了,因為每個存儲節點需要replay的redo少了,還有就是laze recovery,存儲節點啟動後並不需要recovery做完才提供服務,而是可以立刻提供服務,如果請求的page需要做log apply,那麼順便recovery也做掉了(第三點貢獻詳細描述了這點)。

        這裏有個疑問,之前看到不少三方工具依賴bin log,文中沒有提如何處理bin log。

        文章繼續描述了其在降低延時方麵的努力,那就是主路徑盡可能的減少操作,將更多的操作放到後台執行。圖4顯示,隻要1 2 完成就可以返回,其餘全部是後台執行,這對降低延時意義重大。這個模式在很多KV數據庫裏麵也是大量使用的。同時,注意下左下角的peer 之間的gossip,這仍然是NWR裏麵的思路,不同節點之間會定期通信,從而將replica補足。這個gossip並不是說peer的數據不用client發送了,隻是偶爾某個peer無法應答(client 4/6就算成功了)的時候client會將其忽略,所以他們隻能通過互相詢問補足數據。不過補足數據也是有個邊界的,否則gossip協議流量會太大,這個邊界的計算在文章後麵做了說明,本文也會提到。

0?wx_fmt=png

 

        文章說的第三點貢獻來自crash recovery、checkpoint機製,這些當然是以前麵的設計為基礎的。每個log都有一個log sequence number,LSN。任何一個用戶層麵的事務都可以拆解成多個mini-transaction,MTR,而每個MTR都可以由多個log records組成。我不了解MTR的說法,從文中描述來看,如果一個事務的某個操作修改了多個page,那麼應該是產生多個log records的,因為兩個page 很可能屬於不同的segment,那麼問題就來了,多個segment之間,靠什麼約束一致性呢?因為前麵說了,存儲層隻接受log,而log隻有LSN,那麼LSN應該是充當這個角色的很好的選擇。

        存儲層分為兩層,一層是segment,一層是每個segment內部的N個replica。對每個segment而言,如果按照quorum協議,其完成了某個LSN的持久化,那麼這個最大的LSN稱作Segment Complete LSN,SCL,這個可以用於上麵說的gossip協議計算hot log邊界,補充log gap。對segment整體而言,也就是存儲服務而言,如果某個LSN是被W/N寫成功的,那麼就會產生一個Volumn Complete LSN,VCL,表示這個之前的log都安全了。然後事務(準確說是MTR)commit前的最後一條log對應的LSN會被標記Consistency Point LSNs(有多個), CPL。比VCL小的最大的CPL稱為Volumn Durable LSN,VDL。commit LSN小於等於VDL的事務都被認為是完成的。這個有點繞,其實還是好理解的,比如有兩個獨立事務,T1 T2。他們交叉進行,產生的log如下,

T1 LSN=100, T2 LSN = 101, T1 LSN = 102, T1 LSN = 103(T1 commit),  T2 LSN = 104, T2 LSN = 105(commit)

        此時CPL是103, 105(隻有這兩條commit log完成了持久化對事務來說才有意義)。現在存儲層持久化VDL隻到了104,那麼顯然T1已經完成了,可以響應client成功,而T2還未完成,如果此時發生failover,那麼103之後的都可以直接移除,103前麵的需要更多的數據結構來記錄以便GC。

        上麵說的是寫和commit,讀也是類似。主實例將redo log和一些meta信息異步發給隻讀實例,如果隻讀實例發現了log對應的page 在buffer cache裏麵就apply準備以後讀,否則直接丟棄,如果以後需要讀,可以直接向存儲層發請求。讀的時候並不需要做quorum,因為數據庫知道讀對應的VDL,隻要選擇VCL大於VDL的存儲節點就可以了。當然,這裏主備異步同步log會存在讀過期數據的問題,這跟當前經典模式隻讀實例是類似的,Aurora並沒有解決這個問題。個人認為這種不一致對業務是很難用的,但是,100% MySQL協議兼容和存儲、計算可擴展同時做到顯然不是件容易的事情。

        文章最後的細節闡述了crash recovery,利用的手段前麵已經描述了,主要將傳統的先恢複再提供服務,改為了先提供服務,後台恢複,按需恢複。能這麼做的原因應該歸功於log applicator下沉到了存儲層,可以獨立工作。

 

        整體看來,Aurora並沒有一開始就要做一個完美的係統,而是先將計算存儲分離開來,獨立解決存儲問題,這樣做已經解決了很多用戶的痛點,作為產品和架構權衡的一個例子,值得思考學習。

 

如無特殊說明,截圖均來自論文[1]。

 

[1]. Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases

[2]. BigTable:A Distributed Storage System forStructured Data

[3]. HBase: https://hbase.apache.org/

[4]. 表格存儲:https://www.aliyun.com/product/ots

[5]. Spanner: Google’s Globally-Distributed Database

[6]. OceanBase: https://www.aliyun.com/product/oceanbase

[7]. TiDB: https://github.com/pingcap/tidb

[8]. CockroachDB: https://www.cockroachlabs.com/

[9]. PolarDB: https://www.aliyun.com/product/polardb

[10]. Weighted Voting for Replicated Data

 

最後更新:2017-10-23 15:34:26

  上一篇:go  PostgreSQL 圖像搜索插件使用篇
  下一篇:go  瀏覽器端CORS策略 + 緩存策略 導致的 跨域策略失效 問題