根據阿裡交易型業務的特點,以及在雙十一這樣業内罕有的需求推動下,我們在官方的MySQL基礎上增加了非常多實用的功能、性能更新檔。而在使用MySQL的過程中,資料一緻性是繞不開的話題之一。本文主要從阿裡巴巴“去IOE”的後時代講起,向大家簡單介紹下我們過去幾年在MySQL資料一緻性上的努力和實踐,以及目前的解決方案。
一.MySQL單機的資料一緻性
MySQL作為一個可插拔的資料庫系統,支援插件式的存儲引擎,在設計上分為Server層和Storage Engine層。
在Server層,MySQL以events的形式記錄資料庫各種操作的Binlog二進制日志,其基本核心作用有:複制和備份。除此之外,我們結合多樣化的業務場景需求,基于Binlog的特性建構了強大的MySQL生态,如:DTS、單元化、異構系統之間實時同步等等,Binlog早已成為MySQL生态中不可缺少的子產品。而在Storage Engine層,InnoDB作為比較通用的存儲引擎,其在高可用和高性能兩方面作了較好的平衡,早已經成為使用MySQL的首選(PS:官方從MySQL 5.5.5開始,将InnoDB作為了MySQL的預設存儲引擎 )。和大多數關系型資料庫一樣,InnoDB采用WAL技術,即InnoDB Redo Log記錄了對資料檔案的實體更改,并保證總是日志先行,在持久化資料檔案前,保證之前的redo日志已經寫到磁盤。Binlog和InnoDB Redo Log是否落盤将直接影響執行個體在異常當機後資料能恢複到什麼程度。InnoDB提供了相應的參數來控制事務送出時,寫日志的方式和政策,例如:
innodb_flush_method:控制innodb資料檔案、日志檔案的打開和刷寫的方式,建議取值:fsync、O_DIRECT。
innodb_flush_log_at_trx_commit:控制每次事務送出時,重做日志的寫盤和落盤政策,可取值:0,1,2。
當innodb_flush_log_at_trx_commit=1時,每次事務送出,日志寫到InnoDB Log Buffer後,會等待Log Buffer中的日志寫到Innodb日志檔案并重新整理到磁盤上才傳回成功。
sync_binlog:控制每次事務送出時,Binlog日志多久重新整理到磁盤上,可取值:0或者n(N為正整數)。
不同取值會影響MySQL的性能和異常crash後資料能恢複的程度。當sync_binlog=1時,MySQL每次事務送出都會将binlog_cache中的資料強制寫入磁盤。
innodb_doublewrite:控制是否打開double writer功能,取值ON或者OFF。
當Innodb的page size預設16K,磁盤單次寫的page大小通常為4K或者遠小于Innodb的page大小時,發生了系統斷電/os crash ,剛好隻有一部分寫是成功的,則會遇到partial page write問題,進而可能導緻crash後由于部分寫失敗的page影響資料的恢複。InnoDB為此提供了Double Writer技術來避免partial page write的發生。
innodb_support_xa:控制是否開啟InnoDB的兩階段事務送出.預設情況下,innodb_support_xa=true,支援xa兩段式事務送出。
以上參數不同的取值分别影響着MySQL異常crash後資料能恢複的程度和寫入性能,實際使用過程中,需要結合業務的特性和實際需求,來設定合理的配置。比如:
MySQL單執行個體,Binlog關閉場景:
innodb_flush_log_at_trx_commit=1,innodb_doublewrite=ON時,能夠保證不論是MySQL Crash 還是OS Crash 或者是主機斷電重新開機都不會丢失資料。
MySQL單執行個體,Binlog開啟場景:
預設innodb_support_xa=ON,開啟binlog後事務送出流程會變成兩階段送出,這裡的兩階段送出并不涉及分布式事務,mysql把它稱之為内部xa事務。
當innodb_flush_log_at_trx_commit=1,sync_binlog=1,innodb_doublewrite=ON,innodb_support_xa=ON時,同樣能夠保證不論是MySQL Crash 還是OS Crash 或者是主機斷電重新開機都不會丢失資料。
但是,當由于主機硬體故障等原因導緻主機完全無法啟動時,則MySQL單執行個體面臨着單點故障導緻資料丢失的風險,故MySQL單執行個體通常不适用于生産環境。
二.MySQL叢集的資料一緻性
MySQL叢集通常指MySQL的主從複制架構。通常使用MySQL主從複制來解決MySQL的單點故障問題,其通過邏輯複制的方式把主庫的變更同步到從庫,主備之間無法保證嚴格一緻的模式,于是,MySQL的主從複制帶來了主從“資料一緻性”的問題。
MySQL的複制分為:異步複制、半同步複制、全同步複制。
異步複制
主庫在執行完用戶端送出的事務後會立即将結果返給給用戶端,并不關心從庫是否已經接收并處理,這樣就會有一個問題,主如果crash掉了,此時主上已經送出的事務可能并沒有傳到從庫上,如果此時,強行将從提升為主,可能導緻“資料不一緻”。早期MySQL僅僅支援異步複制。
半同步複制
MySQL在5.5中引入了半同步複制,主庫在應答用戶端送出的事務前需要保證至少一個從庫接收并寫到relay log中,半同步複制通過rpl_semi_sync_master_wait_point參數來控制master在哪個環節接收 slave ack,master 接收到 ack 後傳回狀态給用戶端,此參數一共有兩個選項 AFTER_SYNC & AFTER_COMMIT。
配置為WAIT_AFTER_COMMIT

rpl_semi_sync_master_wait_point為WAIT_AFTER_COMMIT時,commitTrx的調用在engine層commit之後,如上圖所示。即在等待Slave ACK時候,雖然沒有傳回目前用戶端,但事務已經送出,其他用戶端會讀取到已送出事務。如果Slave端還沒有讀到該事務的events,同時主庫發生了crash,然後切換到備庫。那麼之前讀到的事務就不見了,出現了資料不一緻的問題,如下圖所示。圖檔引自
Loss-less Semi-Synchronous Replication on MySQL 5.7.2。
如果主庫永遠啟動不了,那麼實際上在主庫已經成功送出的事務,在從庫上是找不到的,也就是資料丢失了。
PS:早在11年前後,阿裡巴巴資料庫就創新實作了在engine層commit之前等待Slave ACK的方式來解決此問題。
配置為WAIT_AFTER_SYNC
MySQL官方針對上述問題,在5.7.2引入了Loss-less Semi-Synchronous,在調用binlog sync之後,engine層commit之前等待Slave ACK。這樣隻有在确認Slave收到事務events後,事務才會送出。如下圖所示,圖檔引自
:
在after_sync模式下解決了after_commit模式帶來的資料不一緻的問題,因為主庫沒有送出事務。但也會有個問題,當主庫在binlog flush并且binlog同步到了備庫之後,binlog sync之前發生了abort,那麼很明顯這個事務在主庫上是未送出成功的(由于abort之前binlog未sync完成,主庫恢複後事務會被復原掉),但由于從庫已經收到了這些Binlog,并且執行成功,相當于在從庫上多出了資料,進而可能造成“資料不一緻”。
此外,MySQL半同步複制架構中,主庫在等待備庫ack時候,如果逾時會退化為異步後,也可能導緻“資料不一緻”。
三.MySQL主備的“資料一緻性”方案
下面簡單介紹下阿裡巴巴早期在MySQL資料一緻性問題的一些思考和實踐。
1.單元化架構下的“資料一緻性”
背景:受機架位限制,單機房或地域總會出現容量瓶頸,業務發展受限;以及跨地域容災的需求,阿裡巴巴在早期通過單元化的方案來解決。
由上圖看到中心和各單元之間通過DTS進行實時資料同步,為了保證中心和單元的資料一緻性,我們早期搭建了資料校驗和訂正平台。主要包括:TCP(terminal compare platform)全量資料校驗訂正平台(支援表級,庫級,執行個體級,叢集級别的資料校驗)和AMG(Alibaba Magic Glass)實時的增量資料校驗訂正平台。
TCP和AMG早已成為阿裡巴巴資料庫生态中的核心元件,被廣泛用于衆多場景中保障資料一緻性,如:主從複制、單元化同步、邏輯遷移、資料庫拆分、字元集更新等。
2.ADHA的復原和回補
ADHA(Alibaba Database High Availability)是阿裡巴巴集團資料庫高可用體系。ADHA的復原回補功能幫助我們在發生切換過程中盡量保證資料品質,将老主庫還沒傳到老備庫的資料復原掉rollback,将復原掉的資料回補到新主庫中replay。
ADHA的復原和回補的目的是盡量保證HA切換過程中的資料一緻性。
3.主從一緻性保障措施
複制沖突自動處理:MySQL 5.5/5.6/5.7的參數slave_exec_mode用于解決主從複制沖突和錯誤。預設值是STRICT适合于所有模式(不解決沖突),值IDEMPOTENT 會忽略duplicate-key和no-key-found錯誤,也不适合解決上面主從不一緻問題。我們從5.6開始給slave_exec_mode增加了一個值smart,用于自動修複一些場景(包括PK沖突及UK沖突引起的HA_ERR_KEY_NOT_FOUND/HA_ERR_FOUND_DUPP_KEY/HA_ERR_END_OF_FILE),具體處理政策如下圖
從庫複制開啟SMART模式,可以修複主從複制中斷錯誤,但不能嚴格保證主備一緻,是以當使用smart模式修複複制問題後,需要盡快對主從庫做一個全量資料校驗(這裡包括TCP全量校驗+AMG增量校驗),以識别有差異的資料。
4.最大保護邏輯 Max Protection
為了保證主從強一緻,我們增加了MySQL最大保護(maximum protection)模式功能,簡稱MP模式(這個是參照ORACLE資料庫的最大保護模式(maximum protection))設計做的,具體由參數 maximum_protection 控制,取值為 ON和OFF )。當配置半同步時,一旦判斷主從連接配接斷開了,會讓主庫停止對外服務,主庫所有目前連接配接會被KILL,并拒絕接受普通帳号新的連接配接請求。此刻如果有事務在等待從庫回應binlog的同步資訊這一步,連接配接是無法被kill,該事務在等待逾時後會繼續走完(Engine Commit),然後傳回網絡錯誤給用戶端。即該筆事務被送出了,需要ADHA介入復原掉。MySQL的MP機制是需要ADHA一起實作的。
引入最大保護邏輯,滿足了對資料一緻性要求非常高的業務場景,如金融業務。也給MySQL的高可用解決方案提出更大挑戰。
以上都是我們早期在MySQL主備時代關于“資料一緻性”問題的部分對策,其目的都是為了盡可能的保證“資料一緻性”,并沒有徹底解決“資料一緻性”問題。然而我們相信技術的發展能帶來更大的運維便利性以及更好的使用者體驗,以Google Spanner以及Amazon Aruora 為代表的NewSQL系統為資料庫的“資料一緻性”給出了與以往不同的思路: 基于一緻性協定!基于一緻性協定我們建構了高性能強一緻MySQL資料庫,RDS三節點企業版。關于一緻性協定和RDS三節點企業版相關知識下面的章節會給大家詳細介紹,敬請關注!