Twitter的分佈式雪花算法 SnowFlake

原理

Twitter的雪花算法SnowFlake,使用Java語言實現。java

SnowFlake算法產生的ID是一個64位的整型,結構以下(每一部分用「-」符號分隔):算法

1位標識部分,在java中因爲long的最高位是符號位,正數是0,負數是1,通常生成的ID爲正數,因此爲0;app

41位時間戳部分,這個是毫秒級的時間,通常實現上不會存儲當前的時間戳,而是時間戳的差值(當前時間-固定的開始時間),這樣可使產生的ID從更小值開始;41位的時間戳可使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年;less

10位節點部分,Twitter實現中使用前5位做爲數據中心標識,後5位做爲機器標識,能夠部署1024個節點;分佈式

12位序列號部分,支持同一毫秒內同一個節點能夠生成4096個ID;ide

SnowFlake算法生成的ID大體上是按照時間遞增的,用在分佈式系統中時,須要注意數據中心標識和機器標識必須惟一,這樣就能保證每一個節點生成的ID都是惟一的。或許咱們不必定都須要像上面那樣使用5位做爲數據中心標識,5位做爲機器標識,能夠根據咱們業務的須要,靈活分配節點部分,如:若不須要數據中心,徹底可使用所有10位做爲機器標識;若數據中心很少,也能夠只使用3位做爲數據中心,7位做爲機器標識。this

snowflake生成的ID總體上按照時間自增排序,而且整個分佈式系統內不會產生ID碰撞(由datacenter和workerId做區分),而且效率較高。這個算法單機每秒內理論上最多能夠生成1000*(2^12),也就是409.6萬個ID。spa

源碼

/**
 * 描述: Twitter的分佈式自增ID雪花算法snowflake (Java版)
 *
 * @author jinzg
 * @create 2018-03-14 12:37
 **/
public class IdWorker {

    /**
     * 起始的時間戳
     */
    private final long twepoch = Date
      .from(LocalDate.of(2018, 1, 1).atStartOfDay()
          .atZone(ZoneId.systemDefault()).toInstant()).getTime();

    /**
     * 每一部分佔用的位數
     */
    private final long sequenceBits = 12L;
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;

    /**
     * 每一部分的最大值
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /**
     * 每一部分向左的位移
     */
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    private long workerId;// 數據中心
    private long datacenterId;// 機器標識
    private long sequence = 0L;// 序列號
    private long lastTimestamp = -1L;// 上一次時間戳

    /**
     * Init.
     */
    @PostConstruct
    public void init() {
        try {
            String ip = IpUtils.getRealIp();
            if (StringUtils.isEmpty(ip)) {
                throw new RuntimeException("IdWorker get ip is empty");
            }
            this.workerId = this.datacenterId = Math.abs(ip.hashCode() % 31);
            log.info("ip:{},workerId:{},datacenterId;{}", ip, workerId, datacenterId);
        } catch (SocketException e) {
            log.error("init error,error:{}", e);
            throw new RuntimeException("IdWorker init error");
        }
    }

    /**
     * Instantiates a new Id worker.
     */
    public IdWorker() {
        super();
    }

    /**
     * Instantiates a new Id worker.
     *
     * @param workerId     the worker id
     * @param datacenterId the datacenter id
     */
    public IdWorker(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;
    }

    /**
     * Next id long.
     *
     * @return the long
     */
    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 + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }

    /**
     * Til next millis long.
     *
     * @param lastTimestamp the last timestamp
     * @return the long
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * Time gen long.
     *
     * @return the long
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * test
     */
    static class IdWorkThread implements Runnable {
        private Set<Long> set;
        private IdWorker idWorker;

        /**
         * Instantiates a new Id work thread.
         *
         * @param set      the set
         * @param idWorker the id worker
         */
        public IdWorkThread(Set<Long> set, IdWorker idWorker) {
            this.set = set;
            this.idWorker = idWorker;
        }

        @Override
        public void run() {
            while (true) {
                long id = idWorker.nextId();
                System.out.println(Thread.currentThread().getName() + ":" + id);
                if (!set.add(id)) {
                    System.out.println("duplicate:" + id);
                }
            }
        }
    }

    /**
     * The entry point of application.
     *
     * @param args the input arguments
     */
    public static void main(String[] args) {
        Set<Long> set = new HashSet<Long>();
        final IdWorker idWorker1 = new IdWorker(0, 0);
        final IdWorker idWorker2 = new IdWorker(1, 0);
        Thread t1 = new Thread(new IdWorkThread(set, idWorker1));
        Thread t2 = new Thread(new IdWorkThread(set, idWorker2));
        t1.setDaemon(true);
        t2.setDaemon(true);
        t1.start();
        t2.start();
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

特色

優勢:code

  • 快。
  • 沒有啥依賴,實現也特別簡單。
  • 知道原理以後能夠根據實際狀況調整各各位段,方便靈活。

缺點:orm

  • 只能趨勢遞增。(有些也不叫缺點,網上有些若是絕對遞增,競爭對手中午下單,次日在下單便可大概判斷該公司的訂單量,危險!!!)
  • 依賴機器時間,若是發生回撥會致使可能生成id重複。
相關文章
相關標籤/搜索