在分布式系统中,生成全剧唯一的ID,是一个特别刚需的事情。而鼎鼎有名的算法之一——来自Twitter公司的雪花算法。

利用雪花算法创建分布式ID,可以很有效的帮助我们获取到一个全局唯一、总体按时间递增的ID,合理的配置几乎不会获取到重复的ID,所以该ID是可以全局使用。

下面案例是使用41位时间+10位机器码+12位序列来展示。

基于Java版本的源码:

package com.java;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 基于雪花算法生成ID
 *
 * @author yuanjun
 *
 * Twitter_Snowflake<br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
 * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
 * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。
 * 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
 * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 * 以上加起来刚好64位,为一个Long型。(1 + 41 + 10 + 12)<br>
 * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
 */
public class IDCreator {

    private final static int min_time_bits = 41;
    private final static int default_worker_bits = 10;
    private final static int default_sequence_bits = 12;
    /**开始日期*/
    private final static String default_epoch_date = "2022-01-01";

    // 24
    private final static int max_worker_sequence_bits = 64 - 1 - min_time_bits;
    private final static long ts_year = 1000L * 60 * 60 * 24 * 365;

    public final static int default_max_worker_id = getMaxWorkerId(default_worker_bits);

    public static int getMaxWorkerId(int workerBits) {
        return (1 << workerBits) - 1;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////
    private final SimpleDateFormat sdf_date = new SimpleDateFormat("yyyy-MM-dd");
    private final SimpleDateFormat sdf_ts = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    private final int workerBits;
    private final int sequenceBits;
    private final int timeBits;

    private final long epoch;
    private final int workerId;
    private int sequence = 0;

    private final int shiftTime;
    private final int shiftWorker;
    private final int sequenceMask;

    private final long tolerantClockBackTimestamp;
    private long lastTimestamp = -1L;
    private final String desc;

    /**
     * 默认1024个worker,每个worker产生400w个id/秒。
     *
     * @param workerId
     *            唯一值(0-1023)
     * @param tolerantClockBackTimestamp
     *            容忍时间回拨毫秒数(发生回拨,则等待2倍的容忍时间)
     */
    public IDCreator(int workerId, long tolerantClockBackTimestamp) {
        this(workerId, tolerantClockBackTimestamp, null);
    }

    public IDCreator(int workerId, long tolerantClockBackTimestamp, String desc) {
        this(workerId, tolerantClockBackTimestamp, default_worker_bits, default_sequence_bits, default_epoch_date,
                desc);
    }

    /**
     * workerBits+sequenceBits<=24 (24=64-1-min_time_bits)
     */
    protected IDCreator(int workerId, long tolerantClockBackTimestamp, int workerBits, int sequenceBits,
                        String epochDate, String startupDesc) {
        this.tolerantClockBackTimestamp = tolerantClockBackTimestamp;
        this.workerId = workerId;
        int maxWorkerId = getMaxWorkerId(workerBits);
        if (this.workerId < 0 || this.workerId > maxWorkerId) {
            throw new IllegalArgumentException(logPrefix() + "WorkerId应为:0-" + maxWorkerId);
        }
        this.workerBits = workerBits;
        this.sequenceBits = sequenceBits;
        this.shiftTime = workerBits + sequenceBits;
        this.shiftWorker = sequenceBits;
        this.sequenceMask = -1 ^ (-1 << sequenceBits);
        this.timeBits = 64 - 1 - workerBits - sequenceBits;
        if (timeBits < min_time_bits) {
            throw new IllegalArgumentException(logPrefix() + "时间戳位数不应小于" + min_time_bits + "(实际" + timeBits
                    + "), workerBits与sequenceBits之和应<=" + max_worker_sequence_bits + ".");
        }
        try {
            epoch = sdf_date.parse(epochDate).getTime();
        } catch (ParseException e) {
            throw new IllegalArgumentException("计时起点日期不正确。");
        }
        // printInfo
        {
             long epochNow = System.currentTimeMillis() - epoch;
             int year = (int) ((1L << timeBits) / ts_year);
             int restOfYear = (int) (((1L << timeBits) - epochNow) / ts_year);
             desc = "[" + this + "] 计时起点日期: " + sdf_date.format(epoch) + ";最多服务节点数: " + (1L << workerBits) + "("
             + workerBits + "位)" + "; 每节点产生ID数: " + ((1L << sequenceBits) *
             1)    + "/秒(" + sequenceBits + "位)"
             + "; 可用" + year + "年,剩余" + restOfYear + "年(" + timeBits + "位)。"
             + (startupDesc == null ? "" : startupDesc);
//            desc = "[" + this + "]epoch:" + sdf_date.format(epoch);
        }
    }

    private String logPrefix() {
        return "[workerId:" + workerId + "] ";
    }

    /**
     * 获得下一个ID (该方法是线程安全的)
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            // 等待
            boolean fixedByWait = true;
            long offset = lastTimestamp - timestamp;
            if (offset <= tolerantClockBackTimestamp && tolerantClockBackTimestamp > 0) {
                try {
                    // wait * 2
                    wait(tolerantClockBackTimestamp << 1);
                } catch (InterruptedException ignored) {
                }
                timestamp = timeGen();
                if (timestamp < lastTimestamp) {
                    fixedByWait = false;
                }
            }
            if (!fixedByWait) {
                throw new IllegalStateException(
                        logPrefix() + "节点时钟被回拨,不能生成唯一ID,回拨值=" + offset + "ms," +
                                "当前时间=" + sdf_ts.format(timestamp) + "。");
            }
        }

        // 如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else if (sequence != 0) {
            // 时间戳改变,毫秒内序列重置
            sequence = 0;
        }

        // 上次生成ID的时间截
        lastTimestamp = timestamp;

        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - epoch) << shiftTime) | (workerId << shiftWorker) | sequence;
    }

    /**
     * 阻塞到下一毫秒
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

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

    public int getWorkerId() {
        return workerId;
    }

    public String getDescription() {
        return desc;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "-" + workerId;
    }

    /**
     * @return id创建时间
     */
    public Date getCreateTime(long id) {
        return new Date((id >> (workerBits + sequenceBits)) + epoch);
    }

    public static void main(String[] args) throws Exception {
        long id = 1324684778368139264L;
        IDCreator g = new IDCreator(0, 0);
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(g.getCreateTime(id)));

        System.out.println(g.nextId());
        System.out.println(g.getDescription());

    }
}

测试输出:

2022-01-02 18:10:58.174
639642999193600
[IDCreator-0] 计时起点日期: 2022-01-01;最多服务节点数: 1024(10位); 每节点产生ID数: 4096000/秒(12位); 可用69年,剩余69年(41位)。

标签:

分类:

更新时间: