1. 程式人生 > >分散式UUID的生成

分散式UUID的生成

1.JDK標準API

要生成UUID,大多會直接使用下面這句:

UUID.randomUUID().toString().replace("-", "");

在多數情況,這樣的處理是沒問題的,畢竟是JDK標準介面。但是在某些情況下,會出現重複。搜素 uuid 重複,就會發現有人踩到了雷

先看UUID各版本的實現原理:Universally unique identifier

再看JDK的實現(只實現了UUID的1,3,4版本)java.util.UUID

會發現在分散式場景下JDK自帶的這個工具類並不好用。原因:

  • 會存在多臺Web容器在同1個物理/雲主機上,mac地址相同。因此,版本1的UUID,不合適
  • randomUUID實現的是UUID的版本4,產生重複的概率是可以計算出來的,海量儲存時,重複不可避免。這也是有人踩雷的原因
  • nameUUIDFromBytes實現的是UUID的版本3,保證種子的唯一性才能確保生成的UUID唯一。在分散式的場景下,如果我們每次都能獲取到唯一的種子,那也就不必用這個方法生成UUID了

2.資料庫獲取UUID

通過這種消耗大量效能來獲取UUID,當然可行,但在高併發的場景下你真的會去考慮嗎?

3.分散式UUID的生成

分散式?多臺Web容器(我們可以稱之為例項)在同1個機器(mac地址相同)下?不依賴第3方工具?最好在JVM解決?

思路

  • 確保每臺例項

    具有唯一的名字(我們可以稱之為例項名)

  • 確保某臺例項生成的UUID不會重複: 當前系統時間 + 遞增的數值(避免高併發的影響)

因此,演算法如下:

UUID = 例項名 + 當前系統時間毫秒數 + 遞增的int數

方法

  1. 對每臺Web容器的JAVA_OPTIONS配置不一樣的例項名

    以Tomcat(8.0.53)為例,在startup.bat裡配置:

    rem to set JAVA_OPTS
    set "JAVA_OPTS=%JAVA_OPTS% -Dinstance.name=cico.mba"

    這樣,上文的instance.name,就變成了JVM裡的1個引數了

  2. 程式碼實現

    package java.main;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class UUIDUtil {
    
        /* 從當前Web容器的JAVA_OPTIONS中,獲取JVM的配置:當前例項名 */
        private static final String INSTANCE_NAME = System.getProperty("instance.name");
        /* 例項名脫敏後的值 */
        private static String INSTANCE_NAME_BY_NUM = null;
        /* 計數器。AtomicInteger是java.util.concurrent下的類,JDK的演算法工程師會避免併發問題 */
        private static AtomicInteger CNT = new AtomicInteger(0);
    
        /**
         * 初始化INSTANCE_NAME_BY_NUM。需考慮併發
         */
        private synchronized static void initInstanceNameByNum() {
            if (null != INSTANCE_NAME_BY_NUM) {
                return;
            }
            char[] chars = INSTANCE_NAME.toCharArray();
            StringBuilder sb = new StringBuilder();
            for (char c : chars) {
                sb.append((int) c);
            }
            INSTANCE_NAME_BY_NUM = sb.toString();
        }
    
        /**
         * 生成分散式的UUID
         * 
         * @return
         */
        public static String getConcurrentUUID() {
            if (null == INSTANCE_NAME) {
                return null;
            }
            if (null == INSTANCE_NAME_BY_NUM) {
                initInstanceNameByNum();
            }
            StringBuilder uuid = new StringBuilder();
            uuid.append(INSTANCE_NAME_BY_NUM);
            uuid.append(System.currentTimeMillis());
            uuid.append(CNT.incrementAndGet());
            return uuid.toString();
        }
    }   

說明

通過上文的方法可在JVM內快速生成支援分散式的UUID。這個UUID的長度:

  • 13: System.currentTimeMillis()的長度是13位
  • 11: Integer.MIN_VALUE的長度。Int值遞增,達到Int的上限後,會從負數重新計數,因此長度是11位
  • 2 * 例項名的字元數。例項名一般由字母、數字、小數點、減號、下劃線組成,這些字元的ASCII碼值是2位

如果這個UUID需要持久化,持久化的欄位可定義成VARCHAR2(255),其中例項名的字元數最大可以是115 = ( 255 - 13 - 11 ) / 2