【已解决】低并发场景下KOCA ID生成器idwork_snowflake生成的ID偶数数量远大于奇数数量

KOCA版本 :4.2.0
KOCA模块 :koca-id-generator
模块版本 :4.2.0
场景 :低并发
问题 :生成的ID几乎都是偶数
报错细节 :
尝试解决方案:

在并发比较低的情况下,如以下测试用例,每次获取ID后,休眠1ms后再获取下一个ID

        int total = 10000;
        // 奇数数量
        int oddNum = 0;
        // 偶数数量
        int evenNum = 0;
        for (int i = 0; i < total; i++) {
            String id = idGenerator.getId("test");
            if (Long.parseLong(id) % 2 == 0) {
                evenNum++;
            } else {
                oddNum++;
            }
            // 休眠1ms
            Thread.sleep(1);
        }

        oddNum = 0;
        evenNum = 0;
        for (int i = 0; i < total; i++) {
            String id = idGenerator.getId("test");
            if (Long.parseLong(id) % 2 == 0) {
                evenNum++;
            } else {
                oddNum++;
            }
        }
        System.out.println("high concurrency, total: " + total + " odd: " + oddNum + " even: " + evenNum);

获取10000个ID,最终在sleep(1)的情况下,获取的全是偶数,不sleep的获取的奇数偶数相差不大

在部分场景下,会产生问题,列如使用ID作为分表键插入数据,会导致数据大量分布在偶数分表键的表里,极端情况下可能所有数据都在偶数分表键的表里

通过源码可发现同一个毫秒时间戳内,最多可以生成4095个ID,序列sequence从0开始,如果获取序列时,已经不是上一个时间戳,sequence会置为0,这样在并发比较低的情况下,sequence大部分都是0,加上最后生成ID时都是左移位运(<<)之后再或运算(|),导致生成的ID二进制下最后一位都是0,也就是十进制都是偶数。

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

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

        if (littleFlag) {
            //移位并通过或运算拼到一起组成63位的ID datacenterId 的最大值小一倍
            return ((timestamp - TWEPOCH) << TIMESTAMPLEFTSHIFT - 1) | (datacenterId << DATACENTERIDSHIFT) |
                (workerId << WORKERIDSHIFT) | sequence;
        } else {
            //移位并通过或运算拼到一起组成64位的ID
            return ((timestamp - TWEPOCH) << TIMESTAMPLEFTSHIFT) | (datacenterId << DATACENTERIDSHIFT) |
                (workerId << WORKERIDSHIFT) | sequence;
        }

解决方法,如果不在同一个毫秒时间戳内,sequence不置为0,而是使用使用100内随机数,但这样会导致ID数量减少,最大减少99个(随机到了99),但对于总数4095来说能够接受。

       sequence = RandomUtils.nextInt(100);

补丁版本4.2.1已使用上述解决方法解决了该问题,后面的主线版本4.7.0会同步修改。