MySQL如何髮型不亂的應對半年數十TB資料增量

聽雲APM發表於2016-04-07

文章出自:聽雲部落格

前段時間,Oracle官方釋出了MySQL 5.7的GA版本。新版本中實現了真正意義的並行複製(基於Group Commit的Group Replication),而不再是基於schema的並行複製。這一特性極大的改善了特定場景下的主從複製延遲過高的狀況。隨著MySQL成熟度的提升,越來越多的使用者選擇使用MySQL存放自家的資料,其中不乏使用MySQL來存放大量資料的。

在過去的半年多時間裡,聽雲業務量呈爆發式增長,後端的資料量由去年第一季度的幾TB增長到幾十TB,業務量翻了十幾倍。後端應用及資料庫面臨的一個突出的問題就是頻繁的進行擴容來應對前端流量的增長。資料庫層面我們使用MySQL來分散式儲存業務資料,資料庫叢集的架構也比較簡單,我們使用開源中介軟體Amoeba來實現資料的拆分和讀寫分離。Amoeba後端有幾百個資料庫的節點組,每個節點組中都包含一對主從例項。master例項負責接受write請求,slave負責接受query請求。如下圖: enter image description here

正確的拆分姿勢

隨著可選擇的開源中介軟體越來越多,好多資料量並不是很大的使用者都會過早的考慮水平拆分資料庫。但其實過早的水平拆分未見得是一件有意義的事情。主要原因有兩個:一個方面是水平拆分會對現網的業務造成衝擊,如果系統在設計之初就沒有考慮過後續要進行拆分的話,這個衝擊就會被放大。比如業務中有大量的多表join的查詢,或者是對事務有強一致性的要求時,水平拆分就捉襟見肘了。另一方面,如果過早的進行了水平拆分,那麼到達一定程度後再想要垂直進行拆分時,代價是很大的。以聽雲app為例,當我們業務庫拆成8個分片後,有一天發現資料增長的很快,於是決定對其進行垂直拆分,將小時緯度和天緯度的資料拆分到一個新的例項上去,這時我們不得不同時部署8個節點組來將現有的8個分片上的小時緯度和天緯度的資料遷移出來。工作量相當大。如果水平拆分到了64個片,那麼這時要想再做垂直拆分,保證累的你不要不要的。

所以更合理的路線是這樣的,首先對業務資料進行垂直拆分,原本一個庫按業務單元垂直拆分成多個庫,同時應用中配置多個資料來源或者使用中介軟體來訪問拆分後的多個庫,對應用本身來說,基本沒做什麼改動,但是後端儲存的容量和效能卻翻了好幾倍。如果某天出現瓶頸之後,再來考慮水平拆分的事情。

優雅的從n到2n

enter image description here

水平擴充套件過程中最讓人頭疼的是資料的遷移,以上圖中遷移mod(mobile_app_id,4)=2的資料為例,最開始的做法是先建立兩個新的節點組shared0_new和shared2,拿shared0的全備恢復到shared0_new和shared2,然後在shared0_new上刪除mod(mobile_app_id,4)=2的資料,在shared2上刪除mod(mobile_app_id,4)=0的資料,刪除操作完成後shared0_new、shared2與shared0做同步,同步刪除操作執行過程中的資料增量。同步追上之後,切換amoeba的路由規則,然後下線shared0。這種方式問題很多,首先時耗很高很高,delete完了之後並不能釋放儲存空間,還要optimize table,同樣也是一個漫長的過程。針對大表的delete會產生一個很大的transaction,會在系統表空間中申請很大一塊undo,delete完成後事務提交。這個undo空間並不會釋放,而是直接給其他事務複用,這無疑會浪費很多儲存空間。

後來我們想到一個便捷的辦法,就是利用mysqldump的—where引數,在備份資料的時候加一個mod(mobile_app_id,4)=2的引數,就可以單獨備份出餘數為2的資料,然後拿這個邏輯備份恢復到shared2上去,高效且優雅。

資料傾斜

MySQL分散式儲存不可避免的一個問題就是資料傾斜。業務在執行一段時間之後,會發現少部分shared資料增量特別快,原因是該shared上面部分使用者的資料量較大。對於資料傾斜問題我們目前的措施是將這些shared遷移到1TB儲存上來,但這並非長久之計。因此我們目前正在做一些新的嘗試,比如對Amoeba做了一下擴充套件,擴充套件後的Amoeba支援將某一個mobile_app_id的資料單獨指向後端一個shared節點組,即一個shared只存放一個使用者的資料,同時採用ToKuDB儲存引擎來儲存這部分資料,ToKuDB能夠對資料進行有效的壓縮,除了查詢效能稍有損耗之外,基本具備InnoDB引擎所擁有的特點,而且線上表結構變更比InnoDB快好幾倍不止。這些測試基本已經進入尾聲,很快將會應用到生產環境。

分散式join

分散式join在業界仍沒有完美的解決方案,好在聽雲業務在設計之初就從業務上避免了多表的join,在業務庫中,報表中的每個緯度都會有一張表與之對應,因此查詢某個緯度直接就會查詢後端的某張表,都是在每張表上做一些操作。目前比較流行的分散式join的解決方案主要有兩種:

1、全域性表的形式。舉個例子,A表 join B表,B表分散式儲存在多個shared上,如果A表比較小,可以在所有的shared上都存一份A表的全量資料。那麼就可以很高效的做join。看起來很美好,但是限制很多,應用的場景也很有限。

2、E-R形式。舉個例子,使用者表user(id,name)和訂單表order(id,uid,detail),按使用者id分片,order表的uid引用自user表的id。存放訂單時,首先確定該訂單對應使用者所在的shared,然後將訂單記錄插入到使用者所在的shared上去,這樣檢索某個使用者所有的訂單時,就可以避免跨庫join低效的操作。

目前的開源中介軟體中,MyCat對分散式join處理的是比較細膩的。阿里的DRDS對於分散式join的處理也是這樣的思路。

MySQL擅長什麼

任何一種工具可能都只是解決某一個領域的問題,肯定不是放之四海而皆準的。正確的使用方式是讓工具做自己擅長的事情。關係型資料庫擅長的是結構化的查詢,本身並不擅長巨量資料的清洗。我們在出2015年度APP行業均值資料包表時,需要將後端所有shared上的相關資料彙總起來然後做進一步的分析,這些資料最終彙總在5張表中,每張表都有幾億條的記錄。然後對5、6個欄位group by之後取某些指標的 sum值,最初嘗試在MySQL中處理這些資料,MySQL例項給出24GB的記憶體,結果OOM了好幾次也沒有出結果。最後把資料拉到了hadoop叢集上,使用impala引擎來彙總資料,處理最大的表近7億條記錄,9min左右出結果。所以,不要有all in one的想法,要讓系統中的每個元件做自己擅長的事情。

分散式MySQL架構下的運維

MySQL分散式雖然解決了儲存和效能問題,但是在運維支援過程中卻帶來了一些痛點。

1、跨分片統計資料。中介軟體是無法對後端的全量資料做查詢的,類似年度APP行業均值報表這樣的跨分片的全量資料的查詢,只能使用自動化指令碼從後端逐個shared上提取資料,最終再彙總。

2、DML。經常會有變更表結構的需求,這樣的操作大部分中介軟體是支援不了的,如果只有一個庫好說,當後端幾十個shared時,就比較頭疼了,目前我們並沒有很好的處理辦法,只能使用自動化指令碼批量到後端shared上執行命令,執行完成後,執行一個校驗的指令碼,人工核對校驗指令碼的輸出內容。

應對這樣的情景,髮型必然會稍顯凌亂,但是目前仍舊很無奈,有必要重新設計一下我們的指令碼,寫一個輸出更加友好,完全自動化的工具出來。

原文連結: http://blog.tingyun.com/web/article/detail/386

相關文章