AWS 是怎麼改寫 MySQL 的?

五倍吞吐量的提升,跨可用區的六副本,低於一分鐘的宕機恢復,兼容 MySQL 協議,這是 AWS 推出 Aurora 資料庫時給出的數據。這種量級的提升不可能是小修小補,大都是在架構上有了變革性的突破才能達到,坊間流傳了很多 Aurora 性能提升的秘密,在 Sigmod17 上 Amazon 終於自己發了一篇論文介紹了在雲環境下如何重新打造資料庫這種傳統軟體。很多的傳統軟體都可以看一下上雲是不是只是裝一個軟體那麼簡單。

數據持久性

儘管 Aurora 的性能數據很亮眼,但對於資料庫來說最基本的要求還是一旦數據寫入成功就不能丟,數據的持久性才是第一位的。多副本是解決數據持久性的常用辦法,AWS 採用了基於 quorum 投票的協議來管理副本。簡單說就是如果有 N 個副本,則一次寫數據要求至少寫入(N/2)+1 個節點才算寫入成功,剩下的節點通過相互之間的一致性協議可以達到共同的狀態,而讀數據則要求至少從 N/2 個節點中讀出相同的數據才能決定哪個數據是最新的。

對於一個 3 副本的 quorum 系統,讀寫的 quorum 數都是 2 ,這也是一個比較常見的高可用系統選擇的 quorum 數。但是這個副本數放在大規模多組戶的系統里是不能保證數據的持久性的。因為在大規模系統中,節點出問題是每時每刻都會發生的,如果只有三副本的話,基本上每時每刻都會有一個用戶的系統處在 2/3 的健康狀態。AWS 的 3 副本是分布在 3 個不同的可用區的,如果一個可用區出了故障,那麼所有的資料庫副本就會變成 2/3 的健康狀態,這種情況下任意一個機器的故障都會導致一個用戶的 quorum 健康數變為 1/3,這時候就沒有辦法判斷數據是否一致了。

所以 Aurora 採用的是六副本,每個可用區兩副本。這種架構可以保證寫入操作可以容忍一個可用區不可用的情況 4/6,而讀數據可以容忍一個可用區外加一台機器不可用的情況 3/6。一旦出現了一個可用區加一台機器不可用的 3/6 情況,寫數據會被禁止,但是由於讀數據可以進行可以很快的根據讀出的數據再恢復出一個副本達到 4/6 狀態恢複寫入。

數據分片

現在來想一下出現了一個 AZ 加一台機器掛掉的情況,這時我們需要重建一個副本,那麼重建的時間就取決於資料庫副本文件有多大,隨著資料庫增長,恢復時間也會線性增加。這個對一個發展越來越好的業務是不能接受的,所以需要儘可能降低數據恢復時間。恢復時間過長還會帶來另一個隱患就是在恢復的過程中如果又有一個機器故障,那麼數據就沒辦法直接通過其他副本來恢復了,而恢復時間越長這個風險越大。從 CAP 的角度來看 Aurora 作為一個利用分散式存儲的資料庫是選擇了 CP,但是如果可用性出了問題能在極短的時間恢復,那麼從實際使用角度也就和 CAP 系統差不多了。

一個直接的做法就是把副本分片,這樣一個副本就可以散落在多台機器,這樣一台機器掛的情況下我們就不需要恢復完整的副本,只需要恢復機器上的分片就可以。另一方面由於數據進行了分片,讀數據的時候也可以利用多台主機的 IO 帶寬,對性能也有提升。AWS 採用了 10G 的分片大小,這樣在萬兆網路的內網環境下,恢復一個分片可以在 10s 內完成,這樣在比較極端的 1AZ+1 出故障的情況下,可以保證 10s 內恢復資料庫讀寫。

這種數據分片的方式保證了資料庫有很高的可用性,從運維角度來說就可以對這個系統進行 rolling update 了。比如想要升級底層的操作系統和軟體,可以直接把機器下線進行升級,上面的分片都會很快在別的機器上進行重建,然後再把機器加回集群升級下一個。發現某台機器硬體有異常也不需要做手工的數據遷移,直接把機器下線送修。另外當出現數據熱點的時候也可以直接將這個熱點機器的其他分片標記為不可用把分片遷移走,來避免某一個用戶的行為造成其他用戶的性能下降。這些都是分片帶來的好處。

副本的副作用

前面說了多副本帶來的數據持久和高可用,但是多副本並不是沒有代價的。本來磁碟 IO 的速度就慢,多個副本之間即使是並行的寫入操作,latency 也會變成最慢的節點的 latency,隨著副本數的增多,抖動會變得更大。另外在雲環境下的資料庫和傳統環境下資料庫很重要的一點不同就是計算和存儲是分離的,這種情況下讀數據可以從本地緩存中拿,而寫數據就需要網路 IO,由於存儲分散在了多台機器上可以並行利用磁碟帶寬,瓶頸就來到了網路。

而 MySQL 自己的一些 IO 機制主要是針對本地磁碟設計的,很多優化方法並不適用於網路存儲,此外 MySQL 自己的一些事務和故障恢復的機制,會造成寫放大的問題。來看一下 MySQL mirror 機制下的 IO 流程:

可以看到資料庫一次寫入除了要把數據寫入還有 log, binlog,double-write,frm-file 這些東西要寫入,而且需要在 master 上完成後才能再去 mirror 上執行同樣的過程,等到 mirror 完成後一次請求才能結束。如果直接用 MySQL 的 mirror 機制那麼上面提到的 6 副本系統實際上寫入需要滿足 6/6 才能成功,延遲會極大的加大。如果 AWS 採用這種機制,吞吐量不降到六分之一就算萬幸了,更不要提五倍的提升了。這大概也是 AWS 想要自己搞資料庫的原因,那麼該怎麼搞呢?

Log 即數據

要說 AWS 比起 Oracle(MySQL 現在的爹)做 MySQL 有什麼優勢的話那就是雖然資料庫我沒你們熟,但是底層的系統硬體我們都可以自己控制呀。看一下上面那張 IO 流程圖,其實很多 log 都是怕底層存儲出問題,機器突然斷電造成數據不一致和考慮如何進行恢復才生成的。此外這裡的一些數據是有冗餘的,比如資料庫在寫入數據前都要先寫入 WAL 再進行數據頁的寫入,至於為什麼要這麼做可以參考 《WAL 是如何保證資料庫事務一致性的》這裡只要知道通過 WAL 我們是可以重建數據的,只是這樣做讀數據效率會比較低所以資料庫都會再寫一次完整數據到硬碟,保證讀的效率。

另外由於 MySQL 自己本身不能更改存儲系統的功能,所以鏡像功能需要自己控制實現,所有的數據同步都要過 MySQL 這個用戶層程序去控制,而這些同步的功能又會佔用大量的資源開銷影響正常的讀寫操作,相當於每寫一次數據,資源開銷是原來的多倍。此外在故障恢復的時候 MySQL 在啟動時需要花大量的時間對比日誌和實際數據狀態的差異,就行數據修復,這個過程也會導致故障恢復時間的延長。

AWS 的解決方式就是既然 WAL 里已經包含了所有的數據,那麼我就不同步其他的東西了,只同步 log 就可以了。MySQL 去控制同步過程效率低,我底層的 EBS 自己就帶同步功能啊,根本不需要 MySQL 再去控制,MySQL 節點只需要老老實實的做 SQL 相關的工作就可以了。資料庫的故障恢復,存儲系統可以自己進行數據恢復,MySQL 只需要把引擎啟動起來就可以了。總結來說就是 MySQL 之前之所以慢是因為文件系統有很多缺陷需要很多額外的功夫來保證數據寫入能成功,現在 AWS 給了一個及其完善的文件系統,數據寫入了就能成功,斷電了也能自己恢復,還能自己做多副本,那麼 MySQL 自己要做的事情就少了,性能自然也就上去了。現在的流程就簡化成了:

所有的 Replica 節點不需要將數據寫入磁碟了,數據同步由底層的 EBS 系統來做。Replica 節點只需要接收 Primay 的 Log 信息來更新本地的緩存和一些全局的配置。Primary 的寫入操作也不再需要各種各樣的 log 寫入,只需要把 WAL 給底層存儲系統,存儲系統會自動的寫入到一定的 quorum 節點上。而有了這些 log,存儲系統會自動的去做 checkpoint, 備份和故障恢復,而且這些耗時的操作都是在後台默默的非同步執行的,不需要前台的 MySQL 引擎。這樣寫入操作的 IO 量和操作路徑都顯著變少了,AWS 才能把 MySQL 的性能在雲環境里提升那麼多。

再加上分片的存儲將磁碟 IO 的壓力分散,讀寫性能都得到了顯著提升,放兩張圖來感受一下:

MySQL 這樣的應用在雲環境下都有這種玩法,其他的傳統軟體,在雲環境里又該怎麼辦呢?

論文下載鏈接:allthingsdistributed.com


推薦閱讀:

mysql 的事件調度器(Event Scheduler)穩定性如何?主要用在什麼場合?
一條MySQL的select語句,為什麼性能會差別這麼大?
mysql workbench設置中文失敗?
MySQL成勒索新目標 數據服務基線安全問題迫在眉睫
SQL中 LEFT JOIN ON 條件的效率高低比較?

TAG:数据库 | MySQL | AmazonWebServicesAWS |