在咱們的業務需求中一般有須要一些惟一的ID,來記錄咱們某個數據的標識:html
一般咱們會調研各類各樣的生成策略,根據不一樣的業務,採起最合適的策略,下面我會討論一下各類策略/算法,以及他們的一些優劣點。java
UUID是通用惟一識別碼(Universally Unique Identifier)的縮寫,開放軟件基金會(OSF)規範定義了包括網卡MAC地址、時間戳、名字空間(Namespace)、隨機或僞隨機數、時序等元素。利用這些元素來生成UUID。node
UUID是由128位二進制組成,通常轉換成十六進制,而後用String表示。在java中有個UUID類,在他的註釋中咱們看見這裏有4種不一樣的UUID的生成策略:git
UUID的優勢:github
UUID的缺點:面試
適用場景:UUID的適用場景能夠爲不須要擔憂過多的空間佔用,以及不須要生成有遞增趨勢的數字。在Log4j裏面他在UuidPatternConverter中加入了UUID來標識每一條日誌。redis
你們對於惟一標識最容易想到的就是主鍵自增,這個也是咱們最經常使用的方法。例如咱們有個訂單服務,那麼把訂單id設置爲主鍵自增便可。算法
優勢:數據庫
缺點:緩存
適用場景: 根據上面能夠總結出來,當數據量很少,併發性能不高的時候這個很適合,好比一些to B的業務,商家註冊這些,商家註冊和用戶註冊不是一個數量級的,因此能夠數據庫主鍵遞增。若是對順序遞加強依賴,那麼也可使用數據庫主鍵自增。
熟悉Redis的同窗,應該知道在Redis中有兩個命令Incr,IncrBy,由於Redis是單線程的因此能保證原子性。
優勢:
缺點:
適用:因爲其性能比數據庫好,可是有可能會出現ID重複和不穩定,這一塊若是能夠接受那麼就可使用。也適用於到了某個時間,好比天天都刷新ID,那麼這個ID就須要重置,經過(Incr Today),天天都會從0開始加。
利用ZK的Znode數據版本以下面的代碼,每次都不獲取指望版本號也就是每次都會成功,那麼每次都會返回最新的版本號:
Zookeeper這個方案用得較少,嚴重依賴Zookeeper集羣,而且性能不是很高,因此不予推薦。
這個方法在美團的Leaf中有介紹,詳情能夠參考美團技術團隊的發佈的技術文章:Leaf——美團點評分佈式ID生成系統,這個方案是將數據庫主鍵自增進行優化。
biz_tag表明每一個不一樣的業務,max_id表明每一個業務設置的大小,step表明每一個proxyServer緩存的步長。 以前咱們的每一個服務都訪問的是數據庫,如今不須要,每一個服務直接和咱們的ProxyServer作交互,減小了對數據庫的依賴。咱們的每一個ProxyServer回去數據庫中拿出步長的長度,好比server1拿到了1-1000,server2拿到來 1001-2000。若是用完會再次去數據庫中拿。
優勢:
缺點:
適用場景:須要趨勢遞增,而且ID大小可控制的,可使用這套方案。
固然這個方案也能夠經過一些手段避免被人猜想,把ID變成是無序的,好比把咱們生成的數據是一個遞增的long型,把這個Long分紅幾個部分,好比能夠分紅幾組三位數,幾組四位數,而後在創建一個映射表,將咱們的數據變成無序。
Snowflake是Twitter提出來的一個算法,其目的是生成一個64bit的整數:
上面只是一個將64bit劃分的標準,固然也不必定這麼作,能夠根據不一樣業務的具體場景來劃分,好比下面給出一個業務場景:
這個時候咱們根據上面的場景能夠再次合理的劃分62bit,QPS幾年以內會發展到百萬,那麼每毫秒就是千級的請求,目前10臺機器那麼每臺機器承擔百級的請求,爲了保證擴展,後面的循環位能夠限制到1024,也就是2^10,那麼循環位10位就足夠了。
機器三地部署咱們能夠用3bit總共8來表示機房位置,當前的機器10臺,爲了保證擴展到百臺那麼能夠用7bit 128來表示,時間位依然是41bit,那麼還剩下64-10-3-7-41-1 = 2bit,還剩下2bit能夠用來進行擴展。
適用場景:當咱們須要無序不能被猜想的ID,而且須要必定高性能,且須要long型,那麼就可使用咱們雪花算法。好比常見的訂單ID,用雪花算法別人就沒法猜想你天天的訂單量是多少。
public class IdWorker{ private long workerId; private long datacenterId; private long sequence = 0; /** * 2018/9/29日,今後時開始計算,能夠用到2089年 */ private long twepoch = 1538211907857L; private long workerIdBits = 5L; private long datacenterIdBits = 5L; private long sequenceBits = 12L; private long workerIdShift = sequenceBits; private long datacenterIdShift = sequenceBits + workerIdBits; private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 獲得0000000000000000000000000000000000000000000000000000111111111111 private long sequenceMask = -1L ^ (-1L << sequenceBits); private long lastTimestamp = -1L; public IdWorker(long workerId, long datacenterId){ this.workerId = workerId; this.datacenterId = datacenterId; } public synchronized long nextId() { long timestamp = timeGen(); //時間回撥,拋出異常 if (timestamp < lastTimestamp) { System.err.printf("clock is moving backwards. Rejecting requests until %d.", lastTimestamp); throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; } /** * 當前ms已經滿了 * @param lastTimestamp * @return */ private long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } private long timeGen(){ return System.currentTimeMillis(); } public static void main(String[] args) { IdWorker worker = new IdWorker(1,1); for (int i = 0; i < 30; i++) { System.out.println(worker.nextId()); } } }
上面定義了雪花算法的實現,在nextId中是咱們生成雪花算法的關鍵。
由於機器的緣由會發生時間回撥,咱們的雪花算法是強依賴咱們的時間的,若是時間發生回撥,有可能會生成重複的ID,在咱們上面的nextId中咱們用當前時間和上一次的時間進行判斷,若是當前時間小於上一次的時間那麼確定是發生了回撥,普通的算法會直接拋出異常,這裏咱們能夠對其進行優化,通常分爲兩個狀況:
經過上面的幾種策略能夠比較的防禦咱們的時鐘回撥,防止出現回撥以後大量的異常出現。下面是修改以後的代碼,這裏修改了時鐘回撥的邏輯:
本文分析了各類生產分佈式ID的算法的原理,以及他們的適用場景,相信你已經能爲本身的項目選擇好一個合適的分佈式ID生成策略了。沒有一個策略是完美的,只有適合本身的纔是最好的。
這篇文章被我收錄於JGrowing,一個全面,優秀,由社區一塊兒共建的Java學習路線,若是您想參與開源項目的維護,能夠一塊兒共建,github地址爲:https://github.com/javagrowing/JGrowing 麻煩給個小星星喲。
最後打個廣告,若是你以爲這篇文章對你有文章,能夠關注個人技術公衆號,也能夠加入個人技術交流羣進行更多的技術交流。最近做者收集了不少最新的學習資料視頻以及面試資料,關注以後便可領取,你的關注和轉發是對我最大的支持,O(∩_∩)O。