MySQL 億級數據量水平分庫實踐

水平拆分和垂直拆分一直是最常見的數據庫優化方式,筆者所在的部門所使用的數據庫一直是主從熱備的架構,但數據量在一年前就已經破億,並以飛快的增長速度不斷增加。爲了減小數據庫的負擔,提高數據庫的效率,縮短查詢時間,水平拆分的工作已經必不可免。 而水平拆分必然會帶來一些問題,例如原本依賴於數據庫自增 id 的主鍵在分庫的場景下,多個分庫下 id 做不到全局唯一;引入了分佈式事務的問題,如果同一個邏輯事務裏,涉及的數據跨多個數據庫實例,本地事務將不生效;需要將原本的源庫做拆分遷移,如果數據量很大的情況下,不停機的數據遷移也將成爲一個難點;引入了跨庫聚合的問題,分庫分表後,表之間的關聯操作將受到限制,就無法 join 位於不同數據庫實例的表,結果原本一次查詢能夠完成的業務,可能需要多次查詢才能完成。同時當拆分後的數據庫再次達到瓶頸,如何擴容也成爲一個問題。 當前主流的分庫方案既有基於客戶端中間件的,也有引入 MySQL 代理中間層。大部分公司的分庫方案都是使用中間件的形式,基於中間件的方式是分庫方案中最快的,沒有代理中間層,僅需要客戶端進行一次哈希計算,不需要經過代理便可直接操作分庫節點,不需要多餘的 Proxy 機器,不用考慮 Proxy 部署與維護。但是這樣需要每種語言都實現一遍客戶端中間件的邏輯,維護和開發的成本較高;耦合度相較於中間層的形式來說更高,靈活度不夠高。 分庫前最重要的工作便是先對數據庫進行遷移拆分,將原來的源庫按照自身的業務需求和邏輯拆分成多個分庫。筆者所在的部門採用的方案爲基於自研客戶端中間件的分片+不停機數據遷移的方式。由於我們所有的業務代碼統一使用了 Golang 編寫,因此並無需要重複開發客戶端中間件的問題。採用自研的方式是由於我們的需求並不複雜,並不需要引入一些重量級的分庫中間件。全局唯一 id 的問題也有自研開發的分佈式 id 生成器提供全局唯一的有序id以及其他雲文檔存儲部門生成提供的唯一 file_id 保證。由於我們的業務中,現有的所有事務都是針對同一個 file_id 的,因此基於 file_id 分庫後的數據都落在同一數據庫實例上,不存在跨庫乃至分佈式事務的問題。當然,如果有跨庫事務與分庫這種需求同時存在時,分佈式事務將不可避免,有關於分佈式事務的方案,可見我寫的這篇文章:[對於 MySQL 分佈式事務的幾個看法][1]。跨庫 join 的問題,首先我們應當儘量避免跨庫 join 操作,這種操作因爲需要中間件支持跨庫查詢並且跨庫聚合的操作,不僅增加了中間件開發的複雜度和耦合度,而且十分沒有必要。可以從以下幾個方面進行優化:1.字段的冗餘,將本來需要跨庫查詢的字段冗餘在一張表裏;2.設計表結構和分庫時,需要 join 的表儘量採用同一個唯一 id 使得需要聯表的數據落在同一庫裏;3.在業務層進行數據的聚合,分別查詢後自行聚合篩選;實在沒有其他辦法時再考慮是否讓中間件支持跨庫操作。 具體的 Golang 中間件開發思路如下,由於標準庫 sql 包中已經維護了一套非常完善的連接池機制以及數據操作流程,並且有非常優秀的 MySQL 驅動包 go-mysql-driver,我們決定對 go-mysql-driver 包進行封裝,我們的中間件是基於 go-mysql-driver 實現 sql 包的一套驅動,對 sql 進行解析,根據開始時的配置,拿取 sql 中的分區鍵的值( sql 中已經包含分區鍵的值,即如 select * from user where id = '1')或者是分區鍵所處的位置( sql 中分區鍵的值爲?,即如 select * from user where id = ?),並對最終獲取到的分區鍵的值做hash計算,計算出映射的槽,並操作槽對應的 db 實例執行這行 sql,db 實例是使用 go-mysql-driver 驅動打開的,無需重複從頭開始造輪子。通過這種實現標準庫 sql 包的驅動,從舊驅動改成新驅動,只需要改動一行代碼便可輕鬆切換。 介紹完中間件,下面詳細得闡述下,我們的遷移工具的開發以及開發過程中遇到的一些問題: 當前互聯網最常用 MySQL 遷移方式有兩種,第一種爲對業務無侵入的方案,在 MySQL 資源層操作,主要方式爲全量遷移後的基於 Binlog 增量遷移後,在 Binlog 追平後切換。第二種爲在業務層面上操作的數據庫雙寫方案,大致經歷以下過程:開啓先寫A庫再寫B庫(A爲舊庫) -> 校驗,並等 A、B庫 Binlog 差距爲0時 -> 開啓讀B庫開關 -> 校驗 -> 開啓寫B庫再寫A庫開關 -> 校驗 -> 開啓只寫B庫開關。 由於我們決定整體方案對業務是無感知的,因此我們採用了第一種無侵入的方式,遷移過程中的數據拆分的方式我們借鑑了 Redis 擴容的方式,採用分槽的概念對所有數據根據配置進行分槽映射,並根據每個表設立的分區鍵與總槽數的 hash 值決定每條數據的 slot 節點。此處採用分槽的設計目的是爲了當數據容量過大時可以進行靈活伸縮,而不管是在初次遷移過程中,還是爲了以後的擴容遷移,開發一套在線遷移工具勢在必行。 由於在遷移的過程中,必須加入自身的業務邏輯,如分庫分表等,因此類似於 MySQLDumper 的這種全量遷移工具我們無法使用,我們採用的是類 MySQLDumper 不鎖表的全量分庫遷移+建立主從複製的增量複製遷移/基於 Binlog 的增量複製遷移。由於我們使用的雲數據庫的特殊性,無法直接與源庫建立主從複製關係,我們採用了基於 Binlog 增量複製的方案。 如何實現全量遷移呢?將 MySQLDumper 過程中執行的命令拿出去分析即可得知,首先是對數據庫進行 flush table 的操作
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章