今天微信公衆號推送了一片文章,是關於如何在分佈式集羣中生成全局唯一ID。文章主要講了如何使用SnowFlake算法來生成的。
1、第一位,佔用1bit,其值始終爲0,沒有實際意義。
2、時間戳,佔用41bit,精確到毫秒,總共可容納140年時間。
3、工作機器,其高5位爲數據中心ID,低5位爲工作節點ID,最多可容納1024個節點。
4、序列號,佔用12bit,這個值在同一毫秒內同一節點上從0開始不斷累加,最多可以累加到4095(共4096個數)。
由上可知,SnowFlake算法在1毫秒之內最多能夠生成的全局唯一ID個數爲:1024*4096=4194304。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SnowFlake {
// 初始時間截 (2017-01-01)
private static final long INITIAL_TIME_STAMP = 1483200000000L;
// 機器id所佔的位數
private static final long WORKER_ID_BITS = 5L;
// 數據標識id所佔的位數
private static final long DATACENTER_ID_BITS = 5L;
// 支持的最大機器id,結果是31 (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數)
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
// 支持的最大數據標識id,結果是31
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
// 序列在id中佔的位數
private final long SEQUENCE_BITS = 12L;
// 機器ID的偏移量(12)
private final long WORKERID_OFFSET = SEQUENCE_BITS;
// 數據中心ID的偏移量(12+5)
private final long DATACENTERID_OFFSET = SEQUENCE_BITS + WORKER_ID_BITS;
// 時間截的偏移量(5+5+12)
private final long TIMESTAMP_OFFSET = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
// 生成序列的掩碼,這裏爲4095 (0b111111111111=0xfff=4095)
private final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
// 工作節點ID(0~31)
private long workerId;
// 數據中心ID(0~31)
private long datacenterId;
// 毫秒內序列(0~4095)
private long sequence = 0L;
// 上次生成ID的時間截
private long lastTimestamp = -1L;
public SnowFlake(long workerId, long datacenterId) {
if (workerId > MAX_WORKER_ID || workerId < 0) {
throw new IllegalArgumentException(
String.format("WorkerId can not be greater than %d or smaller than 0", workerId));
}
if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
throw new IllegalArgumentException(
String.format("DataCenterId can not be greater than %d or smaller than 0", datacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Current time is smaller than last timestamp!");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = tilNextMills(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - INITIAL_TIME_STAMP) << TIMESTAMP_OFFSET) | (datacenterId << DATACENTERID_OFFSET)
| (workerId << WORKERID_OFFSET) | sequence;
}
protected long tilNextMills(long lastTimeStamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimeStamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
public static void main(String[] args) {
final SnowFlake snowFlake = new SnowFlake(1, 1);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10000; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("Current Id: " + snowFlake.nextId());
}
});
}
}
}
其中
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
這段代碼能夠很快的計算出幾位二進制數能夠表示的最大十進制數。我們能夠通過幾段簡單的代碼來驗證。
private static final long WORKER_ID_BITS = 5L;
public static void main(String[] args) {
System.out.println(Long.toBinaryString(-1L) + ".");
System.out.println(Long.toBinaryString(-1L << WORKER_ID_BITS) + ".");
System.out.println(Long.toBinaryString(~(-1L << WORKER_ID_BITS)) + ".");
}
其輸出爲
1111111111111111111111111111111111111111111111111111111111111111.
1111111111111111111111111111111111111111111111111111111111100000.
11111.
部分輸出如下:
Current Id: 176443278823133185
Current Id: 176443278823133184
Current Id: 176443278827327488
Current Id: 176443278831521792
Current Id: 176443278831521794
Current Id: 176443278831521793
Current Id: 176443278831521795
Current Id: 176443278835716096
Current Id: 176443278835716097
Current Id: 176443278839910400
我們可以發現,其生成ID具有很大的關聯性,或者說是有序性,可用於儲存於關係型數據庫中。
另,還有種生成全局唯一ID的方法,即使用UUID(Universally Unique Identifier)
System.out.println(UUID.randomUUID().toString());
其輸出通常是無序的,用於儲存在關係型數據庫時或造成數據庫資源的浪費。