实现全局唯一id碰到的一个坑

仿照twitter的snowflake写了全局唯一id的实现。

twitter的是基于毫秒的,每一个ms最大能产生4096个id,因为我们是批量取的,4096有点太小了。我们就改成秒了,一秒就是4096*1000=409w,这个就足够大了。

但是修改后测试却发现出现了负数,平时自己是没发现问题。

最后发现问题出现在int的左移上,4096*1000,意味着需要移动12+10=22位。

int一共就32位,大于等于512的时候,左移22位,第一位为1就是负数了。

一个整数或操作一个负数,还是负数。这就是问题的原因了。

可能有人还会问,我一个很大的long,或一个很小的负数int,为什么就是负数呢
我们来看看二进制的表示:
-1: 11111111111111111111111111111111
-2: 11111111111111111111111111111110
-1L: 1111111111111111111111111111111111111111111111111111111111111111
-2L: 1111111111111111111111111111111111111111111111111111111111111110
int和long或操作的时候,int要转为long。负数转为long就是前面的32位都为1了。所以第一位必为1,就是肯定是负数。

上面说的有点抽象,直接看代码吧,代码就60多行而已。

/**
 * snowflake算法实现
 */
public class SnowFlake {

    /**
     * 起始的时间戳(单位: SECONDS),2018年9月20日
     */
    private final static long START = 1537372800L;

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 22; // 序列号占用的位数
    private final static long MACHINE_BIT = 10; // 机器标识占用的位数

    /**
     * 每一部分的最大值
     */
    private final static long MAX_SEQUENCE = (1 << SEQUENCE_BIT) - 1;

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long TIME_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    private long sequence = 0L; // 序列号
    private long lastTime = -1L;// 上一次时间戳

    /**
     * 产生下一个ID
     * machineId必须为long。因为int在大于等于512左移22位,就会变成负数。
     */
    public synchronized List nextId(long machineId, int batchSize) {
        List result = new ArrayList<>(batchSize);
        long currentTime = System.currentTimeMillis() / 1000;
        if (currentTime < lastTime) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currentTime == lastTime && (sequence + batchSize) > MAX_SEQUENCE) {
            // 1秒钟400多万应该是足够了的,还是超过就直接抛异常
            throw new RuntimeException("SnowFlake over " + MAX_SEQUENCE + ",at time " + currentTime);
        }

        if (currentTime > lastTime) {
            sequence = 0L;
        }

        long first = (currentTime - START) << TIME_LEFT // 时间戳部分
                | machineId << MACHINE_LEFT // 机器标识部分
                | (sequence + 1); // 序列号部分
        result.add(first);

        for (int i = 1; i < batchSize; i++) {
            result.add(first + i);
        }
        sequence = sequence + batchSize;
        lastTime = currentTime;
        return result;
    }

你可能感兴趣的:(实现全局唯一id碰到的一个坑)