分佈式環境生成一個唯一id從來不是一個容易的事,不同的節點都獨立的各自生成id,高併發性場景下容易生成相同的訂單id。
方案1:數據庫自增主鍵
優點:全局唯一、不會重複
缺點:訂單id有序、容易被外界爬蟲知道業務的訂單量數據
方案2:UUID
UUID(Universally Unique Identifier)的標準型式包含32個16進制數字,以連字號分爲五段,形式爲8-4-4-4-12的36個字符,示例:550e8400-e29b-41d4-a716-446655440000
優點:性能非常高,本地生成,沒有網絡消耗
缺點:不易於存儲,UUID太長,16字節128位,通常以36長度的字符串表示
由於是無序,長字符串,存儲數據庫後索引效率比較差
String uuid = UUID.randomUUID().toString()
方案3:時間戳 + ip或者Mac地址
優點:無序、高併發情況下幾乎不可能產生相同id,大部分業務可以滿足使用
缺點:不適用超高併發,例如1ms可以生成多個訂單id的場景,對系統健壯性要求極高的場景
方案4:snowflake(推薦)
Snowflake,Twitter開源的一種分佈式ID生成算法。基於64位數實現,下圖爲Snowflake算法的ID構成圖。
整個64-bit由以下組成部分組成
1.第一位
佔用1bit,其值始終是0,沒有實際作用。
2.時間戳
佔用41bit,精確到毫秒,總共可以容納約69 年的時間。
3.工作機器id
佔用10bit,其中高位5bit是數據中心ID(datacenterId),低位5bit是工作節點ID(workerId),最多可以容納1024個節點。
4.序列號
佔用12bit,這個值在同一毫秒同一節點上從0開始不斷累加,最多可以累加到4095。
SnowFlake算法在同一毫秒的ID數量 = 1024 X 4096 = 4194304 ,一毫秒可以生成419萬的訂單id !!!
用Java實現SnowFlake算法 , 其實Snowflake算法不難,設計的也很巧妙
/** * Created by Zhon.Thao on 2019/10/24. * * @author Zhon.Thao */ public class SnowFlake { /** * 起始的時間戳:這個時間戳自己隨意獲取 */ private final static long START_MILLS = 1543903501000L; /** * 每一部分佔用的位數 */ private final static long SEQUENCE_BIT = 12; //序列號佔用的位數 /** * 機器標識佔用的位數 */ private final static long MACHINE_BIT = 5; /** * 數據中心佔用的位數 */ private final static long DATACENTER_BIT = 5; /** * 用位運算計算出最大支持的數據中心數量:31 */ private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); /** * 用位運算計算出最大支持的機器數量:31 */ private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); /** * 用位運算計算出12位能存儲的最大正整數:4095 */ private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); /** * 機器標誌較序列號的偏移量 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; /** * 數據中心較機器標誌的偏移量 */ private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; /** * 時間戳較數據中心的偏移量 */ private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; /** * 數據中心id */ private long dataCenterId; /** * 機器標識id */ private long machineId; /** * 序列號 1ms內 */ private static long sequence = 0L; /** * 上一次的時間戳 */ private static long lastMills = -1L; /** * 如果業務方有datacenterId、machineId使用以下構造方法 */ public SnowFlake(long dataCenterId, long machineId) { this.dataCenterId = dataCenterId; this.machineId = machineId; } /** * 如果業務方覺得配置datacenterId、machineId比較麻煩,可以各自機器隨機從最大值裏去一個數 * * 在極高併發下可能會生成相同id */ public SnowFlake() { this.dataCenterId = RandomUtils.nextLong(0, MAX_DATACENTER_NUM); this.machineId = RandomUtils.nextLong(0, MAX_MACHINE_NUM); } /** * 生成訂單ID * * @return */ public synchronized long getId() { /** 獲取當前時間戳 */ long currMills = System.currentTimeMillis(); /** 如果當前時間戳小於上次時間戳則拋出異常 */ if (currMills < lastMills) { throw new RuntimeException("getId error,currMills < lastMills"); } /** 相同毫秒內 */ if (currMills == lastMills) { //相同毫秒內,序列號自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列數已經達到最大 if (sequence == 0L) { /** 獲取下一時間的時間戳並賦值給當前時間戳 */ currMills = getNextMill(); } } else { //不同毫秒內,序列號置爲0 sequence = 0L; } /** 當前時間戳存檔記錄,用於下次產生id時對比是否爲相同時間戳 */ lastMills = currMills; return (currMills - START_MILLS) << TIMESTMP_LEFT //時間戳部分 | dataCenterId << DATACENTER_LEFT //數據中心部分 | machineId << MACHINE_LEFT //機器標識部分 | sequence; //序列號部分 } private static long getNextMill() { long mill = System.currentTimeMillis(); while (mill <= lastMills) { mill = System.currentTimeMillis(); } return mill; } }
方案五 : 美團點評分佈式唯一id生成系統,目前已開源
文檔 : https://tech.meituan.com/2017/04/21/mt-leaf.html
Github: https://github.com/Meituan-Dianping/Leaf