Mysql分庫分表的主鍵生成演算法

weixin_33860722發表於2016-11-06

mysql單表在資料量超過千萬的時候,效能就會受到極大的影響。尤其是對於不命中索引的請求,破壞性是難以想象的。當單表的資料量達到一定程度的時候,我們就需要進行分表或者表分割槽了。分表面臨的第一個問題就是主鍵ID生成的問題,因為涉及到多表,所以原本單表的自增ID生成已經不可用了。那麼我們就需要生成全域性的ID,有兩種方法供我們選擇,兩者也各有優缺點。

1.使用外部依賴生成全域性ID#####

最常見的演算法就是利用外部的儲存,例如Redis、Mysql或者Zookeeper來實現。上述三者比較推崇的是用Redis來實現,因為Redis是單執行緒架構,同時天生是為高併發而生,而且實現起來是比較簡單的。對於Mysql的話有點重,大家都懂,效能實在不如Redis。對於Zookeeper的話,場景不是很適用,你可以建立順序的臨時節點來生成ID,但是這確實不是Zookeeper擅長的,就像拿著鐵鍬切白菜。同時Zookeeper對於高併發場景實在是不行。例如,某東雙十一的服務爆炸事件....。但是上面這些實現多少都有些臃腫。因為你需要去依賴一個第三方的東西,而僅僅是為了生成一個ID。第三方系統的可用性,也直接決定了你係統的可用性。這種依賴確實是有些重。所以我們需要更輕量級的ID生成方案。

2.利用演算法生成ID#####

相比上面依賴第三方生成ID,那麼利用演算法生成簡直是輕量,效能也是遠遠高於上面的方法。但是有幾個關鍵的點:
1.對於資料庫來說,對於隨機ID的插入會導致索引頁頻繁分裂,這樣會使插入操作變慢,索引頁碎片越來越嚴重。所以成演算法需要能保證生產ID有序。
2.現在的後臺都是服務化的,那麼這樣生成演算法要保證,無論在哪個節點都要保證生成的ID都是全域性唯一的。
3.生成的ID需要有比較強的隨機性,這樣在分表的時候可以儘可能的均勻分佈。

這樣看來,這樣的生成演算法確實是比較困難的,下面我們就實現一個這樣的ID生成演算法。
首先,我們需要確定一個隨機因素,這應該是一個隨機遞增的因子,那麼時間戳無疑十分合適,線上伺服器往往都有全域性統一的時間。我們可以用時間戳遞增的特點,來保證ID遞增。
同時我們需要一個標識來區分不同的機器,這樣能在同一毫秒衝突下,解決衝突問題。
但是光有機器的衝突解決還是不夠的,在高併發場景下,同一毫秒會有很多的請求,我們需要解決一臺機器的高併發問題,我們可以使用一個遞增的序列號,來保證一臺機器上的ID是有序的。
那麼ID就變成了下面的格式:

時間戳|機器ID|衝突遞增序列號####

這樣就解決了ID生成的問題,但是好像還有一些問題沒解決,ID的長度怎麼控制,分表的路由規則怎麼確定?

首先,對於ID長度的確定,上面的ID規則裡,機器ID和衝突遞增號基本是不會更改的,我們可以決定一個Seed,來生成字首時間戳,可以用當前時間戳減去Seed,這樣可以通過控制Seed的長度來控制字首時間戳的長度,進而控制ID的長度。
對於分表的路由規則,如果我們利用ID取模來實現路由,其實是不能保證均勻的,因為後面機器ID和衝突的序列號對路由取模是有很大影響的,所以我們可以利用位移運算來取字首的時間戳,因為字首時間戳是全域性順序的,那麼做分割槽路由的時候也會是儘可能均勻的。

其實ID的生成演算法是比較簡單的,但是使用過程中還是有很多問題的,比如ID長度,看上去沒什麼大礙。但是對於一些對接其他系統的場景,ID可能會讓你痛不欲生,而洗資料也是體力活兒。如果ID過長極容易產生前端顯示問題,畢竟js的long是15位的。所以實際運用中ID長度也要嚴格把控。

相關文章