分庫分表問題
資料庫可以通過主從複製將數據複製多份實現讀寫分離,讀走從庫,寫走主庫,應對量並發讀的能力,同時提⾼數據安全性。
但是對於單個表,還存在很多問題,比如:
- 單表記錄過多,欄位加上索引,索引的佔用空間也會越來越大,影響查詢。
- 不同的數據,用戶,商品等都存放在⼀個庫中,甚至一張表中,⼀旦崩潰,全部模組都受到影響。
因此可以做分庫分表,將問題隔離在某一張表或者某一個庫中,降低單個庫的壓力。基本思想是依照某⼀種策略將數據盡量平均地分配到多個資料庫節點或者多個表中。分庫分表後,不同的節點值保存部分數據,所有庫表加到一塊就是所有的數據。拆分分為垂直拆分和水平拆分。
一、垂直拆分
原則⼀般是專庫專用,注重業務相關性,將資料庫的表分離到不同的庫中,比如用戶關係放在用戶庫,內容放在內容庫。
但是水平拆分無法解決單張表記錄量增長的問題,⽐如影片或者直播業務中,用戶會在影片播放過程中發送大量的彈幕,會有數據膨脹的問題。因此需要做單張表拆分。
二、水平拆分
水平拆分更注重數據的特點,將單表內具有一定相關性的記錄拆分到不同的表或者庫中。但是要考慮拆分規則,也就是如何根據單條記錄定位到庫。
可選方案有:
- 根據欄位進行hash值計算,比如根據用戶ID做hash,相當於把ID打散,然後對資料庫的個數取余,得到的值就是記錄對應的庫。
- 用常規欄位做區間劃分,比如根據記錄的創建時間,⼀條記錄是1⽉創建,插⼊和查找的時候⽤創建時間這個欄位先去找到對應的1號庫。但是這種方式有明顯的熱點現象。⽐如6月11月是購物的熱點事件,對應的兩個表或者庫的QPS就會更多。
三、分庫分表引入的問題
主要是水平分庫
1、區分鍵的問題
分區鍵問題:具體的表現是,如果把記錄存放在哪個庫是依據主鍵ID進行hash取余計算得來的,也就是分表的時候是根據主鍵定位到具體的庫。那麼後續如果根據非主鍵,比如根據用戶名去查找記錄,現在只根據用戶名去定位記錄,就要先找到主鍵ID,否則就要把所有庫掃描一遍。
解決辦法之⼀就是:建立⼀個額外的映射表。這個表只有兩個欄位用戶名和主鍵ID,查詢記錄時候先根據用戶名去找ID,然後再根據ID去定位到庫表,雖然這個映射表會有⼀定的存儲消耗,但是畢竟只有兩個欄位,相對來說可以接受。
2、資料庫特性難以實現
聚合操作:在未分庫分表之前查詢記錄總數時只需要在SQL中執⾏count()聚合函數即可,現在數據被分散到多個庫表中,我們可能要考慮其他的⽅案,比方說將計數的數據單獨在⼀張表中或者記錄在Redis⾥⾯單獨記錄。
3、全局ID問題
水平分表之後引入的另外一個比較嚴重的問題就是如何選擇全局唯一ID作為主鍵。
四、如何選擇全局ID
1、業務欄位作為主鍵
選擇業務欄位作為主鍵,⽐如⾝份證,郵箱或者⼿機號等,但是並不通⽤
- 比如⾝份證,對於一些匿名登陸產生的記錄,則⽆法使用身份證證作為主鍵
- 比如郵箱手機號,一般郵箱和手機號還存在變更的可能
2、UUID作為全局
主鍵要保證全局唯⼀性,保證每個機器上生成的記錄都不會衝突,UUID類似的隨機生成可以保證唯一,但是無法保證一定遞增。
為什麼要保證遞增?
- 因為可能會根據ID排序,⽐如彈幕系統,會按照倒序看最新發送的彈幕。如果ID不能保證遞增,只能額外⽤時間欄位記錄時間戳,對所有的記錄再排序。
- 另外B+樹的數據頁是葉內記錄按照ID遞增,如果ID非遞增,則可能導致記錄非尾部插入最終數據頁分裂,降低插⼊的性能。
3、雪花演算法計算全局ID
Snowflake的核心思想是將64bit的⼆進位數字分成若干部分,每⼀部分都存儲有特定含義的數據,比如說時間戳、機器ID、序列號等等,最終⽣成全局唯⼀的有序ID。
比如當前機器上,第2台機器,時間戳第32毫秒,有12個序列號位,當前時間戳下可以生成2^12個序號ID,基本可以滿足要求。
雪花演算法缺點:
- 比較依賴系統時間戳,重啟伺服器就會機器編號,時間不準可能導致生成重複ID,當⽣成重複ID,可以暫停發號,進行時鐘校準。
- ⽐如服務的QPS並不⾼,時間間隔是毫秒為單位,導致當前毫秒下可以生成2^12個序號ID,序列號只了1-10的前幾個ID,可以序列號的起始號隨機或者擴⼤時間間隔為s。