实现全局唯一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<Long> nextId(long machineId, int batchSize) {
        List<Long> 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,如在支付系统中就会有支付ID、退款ID等。那一般生成ID都有哪些...
    Java大生阅读 3,304评论 0 4
  • 本文主要介绍在一个分布式系统中, 怎么样生成全局唯一的 ID 一, 问题描述 在分布式系统存在多个 Shard 的...
    hanayona阅读 2,043评论 0 5
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,780评论 18 399
  • 一、业务背景: 将长链接转化成“指定的短域名+字母数字组合”不重复的URL短地址,用于业务线的常规业务及日常推广使...
    meng_philip123阅读 1,359评论 0 5
  • 出来工作已经5.6年了,月收入还是3千,太失败了,父母年纪越来越大,身体毛病也逐渐越来越多,今早,姐姐又打电话跟我...
    糖火阅读 1,433评论 0 0