一次分表踩坑實踐的探討

前言

以前很多人問我「可否分享一些分庫分表相關的實踐」,其實不是我不分享,而是真的經驗很少🤣;和大部分人同樣都是停留在理論階段。java

不過此次多少有些能夠說道了。算法

先談談背景,咱們生產數據庫隨着業務發展量也逐漸起來;好幾張單表已經突破億級數據,而且保持天天 200+W 的數據量增長。數據庫

而咱們有些業務須要進行關聯查詢、或者是報表統計;在這樣的背景下大表的問題更加突出(好比一個查詢功能須要跑好幾分鐘)。多線程

可能不少人會說:爲啥單表都過億了纔想方案解決?其實不是不想,而是因爲歷史緣由加上錯誤預估了數據增加才致使這個局面。總之緣由比較複雜,也不是本次討論的重點。

臨時方案

因爲需求緊、人手缺的狀況下,整個處理的過程分爲幾個階段。運維

第一階段應該是去年末,當時運維反應 MySQL 所在的主機內存佔用很高,總體負載也居高不下,致使整個 MySQL 的吞吐量明顯下降(寫入、查詢數據都明顯減慢)。測試

爲此咱們找出了數據量最大的幾張表,發現大部分數據量在7/8000W 左右,少數的已經突破一億。大數據

經過業務層面進行分析發現,這些數據多數都是用戶產生的一些日誌型數據,並且這些數據在業務上並非強相關的,甚至兩三個月前的數據其實已經不須要實時查詢了。優化

由於接近年末,儘量的不想去動應用,考慮是否能夠在運維層面緩解壓力;主要的目的就是把單表的數據量下降。spa

本來是想把兩個月以前的數據直接遷移出來放到備份表中,但在準備實施的過程當中發現一個大坑。線程

表中沒有一個能夠排序的索引,致使咱們沒法快速的篩選出一部分數據!這真是一個深坑,爲後面的一些優化埋了個地雷;即使是加索引也須要花幾個小時(具體多久沒敢在生產測試)。

若是咱們強行按照時間進行篩選,可能查詢出 4000W 的數據就得花上好幾個小時;這顯然是行不通的。

因而咱們便想到了一個大膽的想法:這部分數據是否能夠直接不要了?

這多是最有效及最快的方式了,和產品溝通後得知這部分數據真的只是日誌型的數據,即使是報表出不來從此補上也是能夠的。

因而咱們就簡單粗暴的作了如下事情:

  • 修改原有表的表名,好比加上(_190416bak)。
  • 再新建一張和原有表名稱相同的表。

這樣新的數據就寫到了新表,同時業務上也是使用的這個數據量較小的新表。

雖然說過程不太優雅,但至少是解決了問題同時也給咱們作技術改造預留了時間。

分表方案

以前的方案雖然說能夠緩解壓力,但不能根本解決問題。

有些業務必須得查詢以前的數據,致使以前那招行不通了,因此正好咱們就藉助這個機會把表分了。

我相信大部分人雖然說沒有作過實際作過度表,但也見過豬跑;網上一搜各類方案層出不窮。

我認爲最重要的一點是要結合實際業務找出須要 sharding 的字段,同時還有上線階段的數據遷移也很是重要。

時間

可能你們都會說用 hash 的方式分配得最均勻,但我認爲這仍是須要使用歷史數據的場景才用哈希分表。

而對於不須要歷史數據的場景,好比業務上只查詢近三個月的數據。

這類需求完成能夠採起時間分表,按照月份進行劃分,這樣改動簡單,同時對歷史數據也比較好遷移。

因而咱們首先將這類需求的表篩選出來,按照月份進行拆分,只是在查詢的時候拼接好表名便可;也比較好理解。

哈希

剛纔也提到了:須要根據業務需求進行分表策略。

而一旦全部的數據都有可能查詢時,按照時間分表也就行不通了。(也能作,只是若是不是按照時間進行查詢時須要遍歷全部的表)

所以咱們計劃採用 hash 的方式分表,這算是業界比較主流的方式就再也不贅述。

採用哈希時須要將 sharding 字段選好,因爲咱們的業務比較單純;是一個物聯網應用,全部的數據都包含有物聯網設備的惟一標識(IMEI),而且這個字段自然的就保持了惟一性;大多數的業務也都是根據這個字段來的,因此它很是適合來作這個 sharding 字段。

在作分表以前也調研過 MyCATsharding-jdbc(現已升級爲 shardingsphere),最終考慮到對開發的友好性及不增長運維複雜度仍是決定在 jdbc 層 sharding 的方式。

但因爲歷史緣由咱們並不太好集成 sharding-jdbc,但基於 sharding 的特色本身實現了一個分表策略。

這個簡單也好理解:

int index = hash(sharding字段) % 分表數量 ;

select xx from 'busy_'+index where sharding字段 = xxx;

其實就是算出了表名,而後路由過去查詢便可。

只是咱們實現的很是簡單:修改了全部的底層查詢方法,每一個方法都裏都作了這樣的一個判斷。

並無像 sharding-jdbc 同樣,代理了數據庫的查詢方法;其中還要作 SQL解析-->SQL路由-->執行SQL-->合併結果 這一系列的流程。

若是本身再作一遍無異於從新造了一個輪子,而且並不專業,只是在現有的技術條件下選擇了一個快速實現達成效果的方法。

不過這個過程當中咱們節省了將 sharding 字段哈希的過程,由於每個 IMEI 號其實都是一個惟一的整型,直接用它作 mod 運算便可。

還有一個是須要一個統一的組件生成規則,分表後不能再依賴於單表的字段自增了;方法仍是挺多的:

  • 好比時間戳+隨機數可知足大部分業務。
  • UUID,生成簡單,但無法作排序。
  • 雪花算法統一輩子成主鍵ID。

你們能夠根據本身的實際狀況作選擇。

業務調整

由於咱們並無使用第三方的 sharding-jdbc 組件,全部沒有辦法作到對代碼的低侵入性;每一個涉及到分表的業務代碼都須要作底層方法的改造(也就是路由到正確的表)。

考慮到後續業務的發展,咱們決定將拆分的表分爲 64 張;加上後續引入大數據平臺足以應對幾年的數據增加。

這裏還有個小細節須要注意:分表的數量須要爲 2∧N 次方,由於在取模的這種分表方式下,即使是從此再須要分表影響的數據也會盡可能的小。

再修改時只能將表名稱進行全局搜索,而後加以修改,同時根據修改的方法倒推到表現的業務並記錄下來,方便後續迴歸測試。


固然沒法避免查詢時利用非 sharding 字段致使的全表掃描,這是全部分片後都會遇到的問題。

所以咱們在修改分表方法的底層查詢時同時也會查看是否有走分片字段,若是不是,那是否能夠調整業務。

好比對於一個上億的數據是否還有必要存在按照分頁查詢、日期查詢?這樣的業務是否真的具備意義?

咱們儘量的引導產品按照這樣的方式來設計產品或者作出調整。

但對於報表這類的需求確實也沒辦法,好比統計表中某種類型的數據;這種咱們也能夠利用多線程的方式去並行查詢而後彙總統計來提升查詢效率。

有時也有一些另類場景:

好比一個千萬表中有某一特殊類型的數據只佔了很小一部分,好比說幾千上萬條。

這時頁面上須要對它進行分頁查詢是比較正常的(好比某種投訴消息,客戶須要一條一條的單獨處理),但若是咱們按照 IMEI 號或者是主鍵進行分片後再分頁查詢那就比較蛋疼了。

因此這類型的數據建議單獨新建一張表來維護,不要和其餘數據混合在一塊兒,這樣無論是作分頁仍是 like 都比較簡單和獨立。

驗證

代碼改完,開發也單測完成後怎麼來驗證分表的業務是否正常也比較麻煩。

一個是測試麻煩,再一個是萬一哪裏改漏了仍是查詢的原表,但這樣在測試環境並不會有異常,一旦上線產生了生產數據到新的 64 張表後想要再修復就比較麻煩了。

因此咱們取了個巧,直接將原表的表名修改,好比加一個後綴;這樣在測試過程當中觀察先後臺有無報錯就比較容易提早發現這個問題。

上線流程

測試驗收經過後只是分表這個需求的80%,剩下如何上線也是比較頭疼。

一旦應用上線後全部的查詢、寫入、刪除都會先走路由而後到達新表;而老數據在原表裏是不會發生改變的。

數據遷移

因此咱們上線前的第一步天然是須要將原有的數據進行遷移,遷移的目的是要分片到新的 64 張表中,這樣纔會對原有的業務無影響。

所以咱們須要額外準備一個程序,它須要將老表裏的數據按照分片規則複製到新表中;

在咱們這個場景下,生產數據有些已經上億了,這個遷移過程咱們在測試環境模擬發現耗時是很是久的。並且咱們老表中對於 create_time 這樣用於篩選數據的字段沒有索引(之前的技術債),因此查詢起來就更加慢了。

最後沒辦法,咱們只能和產品協商告知用戶對於以前產生的數據短時間可能會查詢不到,這個時間最壞可能會持續幾天(咱們只能在凌晨遷移,白天會影響到數據庫負載)。

總結

這即是咱們此次的分表實踐,雖然說很多過程都不優雅,但受限於條件也只能折中處理。

但咱們後續的計劃是,修改咱們底層的數據鏈接(目前是本身封裝的一個 jar 包,致使集成 sharding-jdbc 比較麻煩)最終逐漸遷移到 sharding-jdbc .

最後得出了幾個結論:

  • 一個好的產品規劃很是有必要,能夠在合理的時間對數據處理(無論是分表仍是切入歸檔)。
  • 每張表都須要一個能夠用於排序查詢的字段(自增ID、建立時間),整個過程因爲沒有這個字段致使耽擱了很長時間。
  • 分表字段須要謹慎,要全盤的考慮業務狀況,儘可能避免出現查詢掃表的狀況。

最後歡迎留言討論。

你的點贊與分享是對我最大的支持

相關文章
相關標籤/搜索