全域性唯一ID生成常見的幾種方式和twitter/snowflake(雪花演算法)解析

hongmingover發表於2018-09-18

全域性唯一ID生成常見的幾種方式和twitter/snowflake(雪花演算法)解析 

全域性唯一ID生成常見的幾種方式:

1,(twitter/snowflake)雪花演算法

2,利用資料庫的auto_increment特性

3,UUID

4,其他(如redis也有incr,redis加lua指令碼實現twitter/snowflake演算法)

 

一、 (twitter/snowflake)

使用了long型別,long型別為8位元組工64位。可表示的最大值位2^64-1(18446744073709551615,裝換成十進位制共20位的長度,這個是無符號的長整型的最大值)。

單常見使用的是long 不是usign long所以最大值為2^63-1(9223372036854775807,裝換成十進位制共19的長度,這個是long的長整型的最大值)

下面程式來自大象部落格:

http://www.blogjava.net/bolo/archive/2015/07/13/426200.html


public class IdGen {
	
	private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long twepoch = 1288834974657L;
     //Thu, 04 Nov 2010 01:42:54 GMT
    private long workerIdBits = 5L;
     //節點ID長度
    private long datacenterIdBits = 5L;
     //資料中心ID長度
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);
     //最大支援機器節點數0~31,一共32個
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
     //最大支援資料中心節點數0~31,一共32個
    private long sequenceBits = 12L;
     //序列號12位
    private long workerIdShift = sequenceBits;
     //機器節點左移12位
    private long datacenterIdShift = sequenceBits + workerIdBits;
     //資料中心節點左移17位
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
     //時間毫秒數左移22位
    private long sequenceMask = -1L ^ (-1L << sequenceBits);
     //最大為4095
    private long lastTimestamp = -1L;
    
    private static class IdGenHolder {
        private static final IdGen instance = new IdGen();
    }
    
    public static IdGen get(){
        return IdGenHolder.instance;
    }

    public IdGen() {
        this(0L, 0L);
    }

    public IdGen(long workerId, long datacenterId) {
        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) {
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        //如果上次生成時間和當前時間相同,在同一毫秒內
        if (lastTimestamp == timestamp) {
            //sequence自增,因為sequence只有12bit,所以和sequenceMask相與一下,去掉高位
            sequence = (sequence + 1) & sequenceMask;
            //判斷是否溢位,也就是每毫秒內超過4095,當為4096時,與sequenceMask相與,sequence就等於0
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
                 //自旋等待到下一毫秒
            }
        } else {
            sequence = 0L;
             //如果和上次生成時間不同,重置sequence,就是下一毫秒開始,sequence計數重新從0開始累加
        }
        lastTimestamp = timestamp;
        // 最後按照規則拼出ID。
        // 000000000000000000000000000000000000000000  00000            00000       000000000000
        // time                                      datacenterId      workerId     sequence
         // return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
         //        | (workerId << workerIdShift) | sequence;
         
         long longStr= ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
         // System.out.println(longStr);
         return longStr;
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    protected long timeGen() {
        return System.currentTimeMillis();
    }

}

測試程式

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

public class GeneratorTest {
	
	@Test
    public void testIdGenerator() {
        long avg = 0;
        for (int k = 0; k < 10; k++) {
            List<Callable<Long>> partitions = new ArrayList<Callable<Long>>();
            final IdGen idGen = IdGen.get();
            for (int i = 0; i < 1400000; i++) {
                partitions.add(new Callable<Long>() {
                    @Override
                    public Long call() throws Exception {
                        return idGen.nextId();
                    }
                });
            }
            ExecutorService executorPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
            try {
                long s = System.currentTimeMillis();
                executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS);
                long s_avg = System.currentTimeMillis() - s;
                avg += s_avg;
                System.out.println("完成時間需要: " + s_avg / 1.0e3 + "秒");
                executorPool.shutdown();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("平均完成時間需要: " + avg / 10 / 1.0e3 + "秒");
    }

}

我們生產也是按照這個twitter/snowflake的演算法來寫的。

 

二、 利用auto_increment特性

insert into

replace into 

 

三、 UUID

常見的方式。可以利用資料庫也可以利用程式生成,一般來說全球唯一。

優點:

1)簡單,程式碼方便。

2)生成ID效能非常好,基本不會有效能問題。

3)全球唯一,在遇見資料遷移,系統資料合併,或者資料庫變更等情況下,可以從容應對。

缺點:

1)沒有排序,無法保證趨勢遞增。

2)UUID往往是使用字串儲存,查詢的效率比較低。

3)儲存空間比較大,如果是海量資料庫,就需要考慮儲存量的問題。

4)傳輸資料量大

5)不可讀。

變種的UUID

1)為了解決UUID不可讀,可以使用UUID to Int64的方法。

2)為了解決UUID無序的問題,NHibernate在其主鍵生成方式中提供了Comb演算法(combined guid/timestamp)。保留GUID的10個位元組,用另6個位元組表示GUID生成的時間(DateTime)。

 

四、 其他

如:1,redis的incr 和INCRBY來實現可以實現自增。 2,redis-lua指令碼實現twitter/snowflake演算法。3,MongoDB的ObjectId。

相關文章