Twitter分布式自增ID代码解析(Snowflake算法)

分布式环境下自增ID的解决方案中,Snowflake是无疑是非常优秀的选择了。代码同样值得我们学习,把位运算运用得淋漓尽致。
更详细的内容大家看代码吧。

package cn.tx.idwoker;
/*
    ID的构成,位长64,首位是符号为,不能使用,剩下63位长。41位的时间戳 + 5位机房编号 + 5位机器编号 + 12位的顺序数字。
    对于某台机器来说,一个号码内产生4096个序列号(0~4095),就是2的12次方个,应该是够用了
* */
public class IdWorker {
    private long workerId;
    private long datacenterId;
    private long sequence;

    /*twepoch : (twitter epoch 推特纪元)时间戳计数基准点,我们用41位字节存放的是实时获取的时间戳减去这个基准点的差。
        通过用new Date(twepock)转化为日期,发现结果是2010-11-04 09:42:54,应该是Twitter公司写函数前的一个日期
        我们做项目时可以修改成当前System.currentTimeMillis()对应的long值。
        如果不设置这个基准时间,那生产的ID最多能用到1970 + 69 = 2039,没几年了。(1970年的某个时间的时间戳是0,是计数起点)
        如果使用Twitter这个时间,那么可以用到2010 + 63 = 2079年。
        所以改成项目启动时间之前的某个时间戳,就可以用到从目前开始计算的第69年。这辈子够用了,至于百年老店,到时候早就是128位操作系统了。

        解释:
            容量为啥是69年,这里用41位长记录时间戳,41位最大数字是2199023255551L,大约是69年的时间。

        拓展:
            程序语言受电脑系统影响,而现代电脑系统都受到Unix系统的广泛影响,而1970年1月1日这个时间正是Unix系统的起始时间(epoch·time)。
            所以每个时间戳都以自从1970年1月1日午夜(历元)经过了多长时间来表示
     */
    private long twepoch = 1288834974657L;
    private long workerIdBits = 5L;
    private long datacenterIdBits = 5L;

    /*机器编号能使用的最大数字。
        二进制运算,-1的二进制是1111...1111,即64位都是1。把它左移5位就是59个1和5个0
        然后再和另一个-1做异或预算,就是使其翻转,变成59个0和5个1,也就十进制的31了。
        其实就是获得5个长度的二进制能表达的最大数字
     */
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);   //31

    //机房编号能使用的最大数字,原理同前
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);  //31

    //顺序数字位长。
    private long sequenceBits = 12L;
    //顺序数字的最大值,原理同前
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long lastTimestamp = -1L;

    //位移量定义,生产四种编码数字后,要位移到指定位置,然后进行与操作,得到最后的ID
    private long workerIdShift = sequenceBits;
    private long datacenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;


    public synchronized long nextId() {
        // 这儿就是获取当前时间戳
        long timestamp = timeGen();
        /*
            机器时间不能回调,回调就生成重复的ID了,这是这个算法的掣肘所在。
        * */
        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        //时间戳发生变化是,顺序号清零。时间戳不发生变化时
        if (lastTimestamp == timestamp) {
            // 取下个顺序号,并判读顺序号是否超过4095,超过则等到下一毫秒的到来。不超过则使用该顺序号。
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        // 这儿记录一下最近一次生成id的时间戳,单位是毫秒
        lastTimestamp = timestamp;

        // 这儿就是将时间戳左移,放到 41 bit那儿;
        // 将机房 id左移放到 5 bit那儿;
        // 将机器id左移放到5 bit那儿;将序号放最后12 bit;
        // 最后拼接起来成一个 64 bit的二进制数字,转换成 10 进制就是个 long 型
        return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift) | sequence;
    }

    public IdWorker(long workerId, long datacenterId, long sequence) {
        // sanity check for workerId
        // 检查了传递进来的机房编码和机器编码不能超过31,不小于0
        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));
        }

        System.out.printf(
                "worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d\n",
                timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);

        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }

    public long getWorkerId() {
        return workerId;
    }

    public long getDatacenterId() {
        return datacenterId;
    }

    public long getTimestamp() {
        return System.currentTimeMillis();
    }

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

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

    // ---------------测试---------------
    public static void main(String[] args) {

        IdWorker worker = new IdWorker(1, 1, 1);
        for (int i = 0; i < 30; i++) {
            System.out.println(worker.nextId());
        }
    }
}


//.... by T.G.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值