工作时无意间发现sahrding-jdbc使用雪花算法生成的id 在某一业务分库分表 永远在那两个库表里面,排查后这里做下分享
环境、配置、问题介绍
雪花算法构成部分
雪花算法一共由64个bit组成 也就是我们常说的64位,换算下来就是8个字节

源码分析
下面是sahrding-jdbc生成id的源码跟读一下
@Overridepublic synchronized Comparable> generateKey() {// 获取当前时间戳long currentMilliseconds = timeService.getCurrentMillis();// 这里面判断当前时间戳是否在允许的浮动范围内if (waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {// 不允许的范围会重新获取时间戳currentMilliseconds = timeService.getCurrentMillis();}// 如果本次获取id的时间戳和上次相同则对sequence进行+1 &我们后面细说if (lastMilliseconds == currentMilliseconds) {if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {// 自旋至到当前时间大于上次时间currentMilliseconds = waitUntilNextTime(currentMilliseconds);}} else {// 这里是获取当前sequence的值vibrateSequenceOffset();sequence = sequenceOffset;}lastMilliseconds = currentMilliseconds;// 时间减去初始的时间左移22位 或 workId左移12位 或 获取到的sequencereturn ((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (getWorkerId() << WORKER_ID_LEFT_SHIFT_BITS) | sequence;}
详解
先了解一下二进制部分运算付号 和源码 反码 补码
通过上面的了解再来看下面这段代码
这里的SEQUENCE_MASK是1 左移12位减1等于2的22次方减一也就是4095
(sequence + 1) & SEQUENCE_MASK) 通过上面的二进制运算我们知道SEQUENCE_MASK 4095的二进制是从低位到高位一共12个1 再往高位去全是0而&付号的运算是遇到0就是0所以我们可以得知这里最大值一定是4095 如:
4096 -> 0001 0000 0000 0000 & 4095 -> 0000 1111 1111 1111 1111 = 0000 0000 0000 0000 -> 0
4097 -> 0001 0000 0000 0001 & 4095 -> 0000 1111 1111 1111 1111 = 0000 0000 0000 0001 -> 1
private static final long SEQUENCE_BITS = 12L;private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;0L == (sequence = (sequence + 1) & SEQUENCE_MASK)if (lastMilliseconds == currentMilliseconds) {if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {// 自旋至到当前时间大于上次时间currentMilliseconds = waitUntilNextTime(currentMilliseconds);}}
sequenceOffset默认值是0 这就是运算 0取反 0000 0000 & 1 -> 0000 0001 = 0000 0001 = 1
private byte sequenceOffset;else {vibrateSequenceOffset();sequence = sequenceOffset;}private void vibrateSequenceOffset() {sequenceOffset = (byte) (~sequenceOffset & 1);}
一部分是毫秒值左偏移22位
第二部分是workId左偏移12位
第三部分是sequence
private static final long SEQUENCE_BITS = 12L; private static final long WORKER_ID_BITS = 10L;private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (getWorkerId() << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
问题原因
通过上面的解析我们发现最终雪花算法生成的id由三个部分生成,
第一部分的值左偏移了22位也就是二进制22位到低位全是0
第二部分左偏移12位 12位到低位全是0
第三部分的值由时间戳决定同一毫秒值内能出现的值最大有4095 不同毫秒值内能出现的值只有0和1
我们这里分库分表的算法是%16而我们发现第一部分和第二部分进行|运算后12位到最低位的值是0
12位往高位有值这最终运算后得出来的值一定是2的12次方以上数字相加这样的数字由于一定是16的整数倍所以取模一定是0而最终落日库表就取决于sequence我们的并发又没有高到一毫秒出现很多次请求进来导致生成的sequence不是0就是1所以最终取模会在0和1上面
这里解释一下为什么毫秒值左偏移 | workId左偏移一定可以被16整除
如:
0001 >> 22 = 0100 0000 0000 0000 0000 = 02(n) + 12(23) + 02(22) …+ 02(0)
0001 >> 12 = 0000 0001 0000 0000 0000 = 02(n) + 12(13) + 02(12)…+ 02(0)
最终的值一定是2*2(4) = 16 的整数倍
上一篇:Java-集合(3)