在高並發或者分表分庫情況下怎么保證數據id的冪等性呢?
經常用到的解決方案有以下幾種:
1. 微軟公司通用唯一識別碼(UUID)
2. Twitter公司雪花算法(SnowFlake)
3. 基於數據庫的id自增
4. 對id進行緩
本文將對snowflake算法進行講解:
1. snowflake是Twitter開源的分布式ID生成算法,結果是一個long型的ID。
2. 其核心思想是:使用41bit作為毫秒數,10bit作為機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作為毫秒內的流水號,最后還有一個符號位,永遠是0。
snowflake算法所生成的ID結構:
1. 整個結構是64位,所以我們在Java中可以使用long來進行存儲。
2. 該算法實現基本就是二進制操作,單機每秒內理論上最多可以生成1024*(2^12),也就是409.6萬個ID(1024 X 4096 = 4194304)
64位說明:
1. 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
2. 1位標識,由於long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0
3. 41位時間截(毫秒級),注意,41位時間截不是存儲當前時間的時間截,而是存儲時間截的差值(當前時間截 - 開始時間截) 得到的值)。
這里的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程序來指定的(如下下面程序IdWorker類的startTime屬性)。
41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
4. 10位的數據機器位,可以部署在1024個節點,包括5位datacenterId和5位workerId
5. 12位序列,毫秒內的計數,12位的計數順序號支持每個節點每毫秒(同一機器,同一時間截)產生4096個ID序號加起來剛好64位,為一個Long型。
SnowFlake的優點:
1. 整體上按照時間自增排序,並且整個分布式系統內不會產生ID碰撞(由數據中心ID和機器ID作區分),並且效率較高,經測試,SnowFlake每秒能夠產生26萬ID左右。
2. 生成ID時不依賴於DB,完全在內存生成,高性能高可用。
3. ID呈趨勢遞增,后續插入索引樹的時候性能較好。
SnowFlake算法的缺點:
依賴於系統時鍾的一致性。如果某台機器的系統時鍾回撥,有可能造成ID沖突,或者ID亂序
算法代碼如下:

1 /** 2 * 功能描述:SnowFlake算法 3 * @author PanHu Sun 4 * @Date 2019/12/1 18:47 5 */ 6 public class SnowflakeIdWorker { 7 // ==============================Fields================== 8 /** 開始時間截 (2019-08-06) */ 9 private final long twepoch = 1565020800000L; 10 11 /** 機器id所占的位數 */ 12 private final long workerIdBits = 5L; 13 14 /** 數據標識id所占的位數 */ 15 private final long datacenterIdBits = 5L; 16 17 /** 支持的最大機器id,結果是31 (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數) */ 18 private final long maxWorkerId = -1L ^ (-1L << workerIdBits); 19 20 /** 支持的最大數據標識id,結果是31 */ 21 private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); 22 23 /** 序列在id中占的位數 */ 24 private final long sequenceBits = 12L; 25 26 /** 機器ID向左移12位 */ 27 private final long workerIdShift = sequenceBits; 28 29 /** 數據標識id向左移17位(12+5) */ 30 private final long datacenterIdShift = sequenceBits + workerIdBits; 31 32 /** 時間截向左移22位(5+5+12) */ 33 private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; 34 35 /** 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) */ 36 private final long sequenceMask = -1L ^ (-1L << sequenceBits); 37 38 /** 工作機器ID(0~31) */ 39 private long workerId; 40 41 /** 數據中心ID(0~31) */ 42 private long datacenterId; 43 44 /** 毫秒內序列(0~4095) */ 45 private long sequence = 0L; 46 47 /** 上次生成ID的時間截 */ 48 private long lastTimestamp = -1L; 49 50 //==============================Constructors==================== 51 /** 52 * 構造函數 53 * @param workerId 工作ID (0~31) 54 * @param datacenterId 數據中心ID (0~31) 55 */ 56 public SnowflakeIdWorker(long workerId, long datacenterId) { 57 if (workerId > maxWorkerId || workerId < 0) { 58 throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); 59 } 60 if (datacenterId > maxDatacenterId || datacenterId < 0) { 61 throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); 62 } 63 this.workerId = workerId; 64 this.datacenterId = datacenterId; 65 } 66 67 // ==============================Methods================================= 68 /** 69 * 獲得下一個ID (該方法是線程安全的) 70 * @return SnowflakeId 71 */ 72 public synchronized long nextId() { 73 long timestamp = timeGen(); 74 75 //如果當前時間小於上一次ID生成的時間戳,說明系統時鍾回退過這個時候應當拋出異常 76 if (timestamp < lastTimestamp) { 77 throw new RuntimeException( 78 String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); 79 } 80 81 //如果是同一時間生成的,則進行毫秒內序列 82 if (lastTimestamp == timestamp) { 83 sequence = (sequence + 1) & sequenceMask; 84 //毫秒內序列溢出 85 if (sequence == 0) { 86 //阻塞到下一個毫秒,獲得新的時間戳 87 timestamp = tilNextMillis(lastTimestamp); 88 } 89 } 90 //時間戳改變,毫秒內序列重置 91 else { 92 sequence = 0L; 93 } 94 95 //上次生成ID的時間截 96 lastTimestamp = timestamp; 97 98 //移位並通過或運算拼到一起組成64位的ID 99 return ((timestamp - twepoch) << timestampLeftShift) // 100 | (datacenterId << datacenterIdShift) // 101 | (workerId << workerIdShift) // 102 | sequence; 103 } 104 105 /** 106 * 阻塞到下一個毫秒,直到獲得新的時間戳 107 * @param lastTimestamp 上次生成ID的時間截 108 * @return 當前時間戳 109 */ 110 protected long tilNextMillis(long lastTimestamp) { 111 long timestamp = timeGen(); 112 while (timestamp <= lastTimestamp) { 113 timestamp = timeGen(); 114 } 115 return timestamp; 116 } 117 118 /** 119 * 返回以毫秒為單位的當前時間 120 * @return 當前時間(毫秒) 121 */ 122 protected long timeGen() { 123 return System.currentTimeMillis(); 124 } 125 126 //==============================Test============================================= 127 /** 測試 */ 128 public static void main(String[] args) { 129 SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); 130 for (int i = 0; i < 1000; i++) { 131 long id = idWorker.nextId(); 132 System.out.println(Long.toBinaryString(id)); 133 System.out.println(id); 134 } 135 } 136 }
快速使用snowflake算法只需以下幾步:
1. 引入hutool依賴

1 <dependency> 2 <groupId>cn.hutool</groupId> 3 <artifactId>hutool-captcha</artifactId> 4 <version>5.0.6</version> 5 </dependency>
2. ID 生成器

1 import cn.hutool.core.date.DatePattern; 2 import cn.hutool.core.lang.ObjectId; 3 import cn.hutool.core.lang.Snowflake; 4 import cn.hutool.core.net.NetUtil; 5 import cn.hutool.core.util.IdUtil; 6 import cn.hutool.core.util.RandomUtil; 7 import lombok.extern.slf4j.Slf4j; 8 import org.joda.time.DateTime; 9 10 import javax.annotation.PostConstruct; 11 import java.util.concurrent.ExecutorService; 12 import java.util.concurrent.Executors; 13 14 /** 15 * 功能描述: 16 * @author PanHu Sun 17 * @Date 2019/12/1 18:50 18 */ 19 @Slf4j 20 public class IdGenerator { 21 22 private long workerId = 0; 23 24 @PostConstruct 25 void init() { 26 try { 27 workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr()); 28 log.info("當前機器 workerId: {}", workerId); 29 } catch (Exception e) { 30 log.warn("獲取機器 ID 失敗", e); 31 workerId = NetUtil.getLocalhost().hashCode(); 32 log.info("當前機器 workerId: {}", workerId); 33 } 34 } 35 36 /** 37 * 獲取一個批次號,形如 2019071015301361000101237 38 * 數據庫使用 char(25) 存儲 39 * @param tenantId 租戶ID,5 位 40 * @param module 業務模塊ID,2 位 41 * @return 返回批次號 42 */ 43 public static synchronized String batchId(int tenantId, int module) { 44 String prefix = DateTime.now().toString(DatePattern.PURE_DATETIME_MS_PATTERN); 45 return prefix + tenantId + module + RandomUtil.randomNumbers(3); 46 } 47 48 @Deprecated 49 public synchronized String getBatchId(int tenantId, int module) { 50 return batchId(tenantId, module); 51 } 52 53 /** 54 * 生成的是不帶-的字符串,類似於:b17f24ff026d40949c85a24f4f375d42 55 * @return 56 */ 57 public static String simpleUUID() { 58 return IdUtil.simpleUUID(); 59 } 60 61 /** 62 * 生成的UUID是帶-的字符串,類似於:a5c8a5e8-df2b-4706-bea4-08d0939410e3 63 * @return 64 */ 65 public static String randomUUID() { 66 return IdUtil.randomUUID(); 67 } 68 69 private Snowflake snowflake = IdUtil.createSnowflake(workerId, 1); 70 71 public synchronized long snowflakeId() { 72 return snowflake.nextId(); 73 } 74 75 public synchronized long snowflakeId(long workerId, long dataCenterId) { 76 Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId); 77 return snowflake.nextId(); 78 } 79 80 /** 81 * 生成類似:5b9e306a4df4f8c54a39fb0c 82 * ObjectId 是 MongoDB 數據庫的一種唯一 ID 生成策略, 83 * 是 UUID version1 的變種,詳細介紹可見:服務化框架-分布式 Unique ID 的生成方法一覽。 84 * @return 85 */ 86 public static String objectId() { 87 return ObjectId.next(); 88 } 89 90 91 92 93 // 測試 94 public static void main(String[] args) { 95 // 還會有重復的 96 // for (int i = 0; i < 100; i++) { 97 // String batchId = batchId(1001, 100); 98 // log.info("批次號: {}", batchId); 99 // } 100 101 // UUID 不帶 - 102 // for (int i = 0; i < 100; i++) { 103 // String simpleUUID = simpleUUID(); 104 // log.info("simpleUUID: {}", simpleUUID); 105 // } 106 107 // UUID 帶 - 108 // for (int i = 0; i < 100; i++) { 109 // String randomUUID = randomUUID(); 110 // log.info("randomUUID: {}", randomUUID); 111 // } 112 113 // 沒有重復 114 // for (int i = 0; i < 100; i++) { 115 // String objectId = objectId(); 116 // log.info("objectId: {}", objectId); 117 // } 118 119 ExecutorService executorService = Executors.newFixedThreadPool(20); 120 IdGenerator idGenerator = new IdGenerator(); 121 for (int i = 0; i < 100; i++) { 122 executorService.execute(() -> { 123 log.info("分布式 ID: {}", idGenerator.snowflakeId()); 124 }); 125 } 126 executorService.shutdown(); 127 } 128 }
3. 測試類

1 public class IdGeneratorTest { 2 @Autowired 3 private IdGenerator idGenerator; 4 5 @Test 6 public void testBatchId() { 7 for (int i = 0; i < 100; i++) { 8 String batchId = idGenerator.batchId(1001, 100); 9 log.info("批次號: {}", batchId); 10 } 11 } 12 13 @Test 14 public void testSimpleUUID() { 15 for (int i = 0; i < 100; i++) { 16 String simpleUUID = idGenerator.simpleUUID(); 17 log.info("simpleUUID: {}", simpleUUID); 18 } 19 } 20 21 @Test 22 public void testRandomUUID() { 23 for (int i = 0; i < 100; i++) { 24 String randomUUID = idGenerator.randomUUID(); 25 log.info("randomUUID: {}", randomUUID); 26 } 27 } 28 29 @Test 30 public void testObjectID() { 31 for (int i = 0; i < 100; i++) { 32 String objectId = idGenerator.objectId(); 33 log.info("objectId: {}", objectId); 34 } 35 } 36 37 @Test 38 public void testSnowflakeId() { 39 ExecutorService executorService = Executors.newFixedThreadPool(20); 40 for (int i = 0; i < 20; i++) { 41 executorService.execute(() -> { 42 log.info("分布式 ID: {}", idGenerator.snowflakeId()); 43 }); 44 } 45 executorService.shutdown(); 46 } 47 }
運行結果:
注:在項目中我們只需要注入 @Autowired private IdGenerator idGenerator;
即可,然后設置id order.setId(idGenerator.snowflakeId() + "");
轉載鏈接:https://juejin.im/post/5d8882d8f265da03e369c063