Snowflake ID組成
Snowflake ID有64bits長,由以下三部分組成:
- time—42bits,精确到ms,那就意味着其可以表示長達(2^42-1)/(1000360024*365)=139.5年,另外使用者可以自己定義一個開始紀元(epoch),然後用(目前時間-開始紀元)算出time,這表示在time這個部分在140年的時間裡是不會重複的,官方文檔在這裡寫成了41bits,應該是寫錯了。另外,這裡用time還有一個很重要的原因,就是可以直接更具time進行排序,對于twitter這種更新頻繁的應用,時間排序就顯得尤為重要了。
- machine id—10bits,該部分其實由datacenterId和workerId兩部分組成,這兩部分是在配置檔案中指明的。
-
datacenterId的作用(個人看法)
1.友善搭建多個生成uid的service,并保證uid不重複,比如在datacenter0将機器0,1,2組成了一個生成uid的service,而datacenter1此時也需要一個生成uid的service,從本中心擷取uid顯然是最快最友善的,那麼它可以在自己中心搭建,隻要保證datacenterId唯一。如果沒有datacenterId,即用10bits,那麼在搭建一個新的service前必須知道目前已經在用的id,否則不能保證生成的id唯一,比如搭建的兩個uid service中都有machine id為100的機器,如果其server時間相同,那麼産生相同id的情況不可避免。
2.加快server啟動速度。啟動一台uid server時,會去檢查zk同workerId目錄中其他機器的情況,如其在zk上注冊的id和向它請求傳回的work_id是否相同,是否處同一個datacenter下,另外還會檢查該server的時間與目前已有機器的平均時間誤差是否在10s範圍内等,這些檢查是會耗費一定時間的。将一個datacenter下的機器數限制在32台(5bits)以内,在一定程度上也保證了server的啟動速度。
- workerId是實際server機器的代号,最大到32,同一個datacenter下的workerId是不能重複的。它會被注冊到zookeeper上,確定workerId未被其他機器占用,并将host:port值存入,注冊成功後就可以對外提供服務了。
- sequence id —12bits,該id可以表示4096個數字,它是在time相同的情況下,遞增該值直到為0,即一個循環結束,此時便隻能等到下一個ms到來,一般情況下4096/ms的請求是不太可能出現的,是以足夠使用了。
Snowflake ID便是通過這三部分實作了UID的産生,政策也并不複雜。下面我們來看看它的一些關鍵源碼
/**
* 核心代碼就是毫秒級時間41位+機器ID 10位+毫秒内序列12位
* */
public class IdWorker {
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long twepoch = 1288834974657L;
private long workerIdBits = 5L;
private long datacenterIdBits = 5L;
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private long sequenceBits = 12L;
private long workerIdShift = sequenceBits;
private long datacenterIdShift = sequenceBits + workerIdBits;
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private long sequenceMask = -1L ^ (-1L << sequenceBits);
private long lastTimestamp = -1L;
public IdWorker(long workerId, long datacenterId) {
// sanity check for workerId
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
this.workerId = workerId;
this.datacenterId = datacenterId;
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
System.out.println("clock is moving backwards. Rejecting requests until "+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 = 0L;
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
protected long tilNextMillis(long lastTimestamp) {
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
return timestamp;
protected long timeGen() {
return System.currentTimeMillis();
}