sahrding-jdbc的雪花算法取模为0或1的问题
创始人
2024-05-09 03:41:31
0

工作时无意间发现sahrding-jdbc使用雪花算法生成的id 在某一业务分库分表 永远在那两个库表里面,排查后这里做下分享

环境、配置、问题介绍

  1. 16库16表
  2. 使用的是org.apache.shardingsphere.core.strategy.keygen下面generateKey生成id
  3. 分库表算法是对16取模
  4. 生成数据永远在0库0表 0库1表 1库0表 1库1表

雪花算法构成部分

雪花算法一共由64个bit组成 也就是我们常说的64位,换算下来就是8个字节

  1. 第一位是付号位 也就是代表正负 0正 1负数
  2. 后面的41位是时间戳
  3. 在后面的10位又可以细分成5+5,代表机房id和机器id也可以直接使用机器id表示
  4. 最后12位就是最小颗粒度的序号,也就是同一毫秒值内同一机房同一机器可以生成多少个不同的序号,12位最大也就是4096(包含0就是4095)

在这里插入图片描述

源码分析

下面是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;}

详解
先了解一下二进制部分运算付号 和源码 反码 补码

  1. ‘&’ 记住一点就是遇到0就是0即可 如:0000 0001 & 1111 1110 = 0000 0000
  2. ‘|’ 和’&'类似遇到1就是1即可 如: 0000 0001 | 1111 1110 = 1111 1111
  3. ‘~’ 所有二进制全部取反 如: ~ 0011 0011 = 1100 1100
  4. ‘<<’ 左移符号就是二进制向左边移动多少位,之后高位补0 如: 1100 << 2 = 11 0000 高位补0 -> 0011 0000
  5. ‘>>’ 同理左移 这个是右移符不同的是低位会直接舍弃如: 0010 1111 >> 4 = 0010 高位补0 -> 0000 0010
  6. ‘反码’ 正数来说反码就是原码 负数就是二进制进行取反也就是上面说的’~’ 唯一不同的是负数反码不针对符号位
  7. ‘补码’ 正数补码就是原码 负数就是反码最低位+1 如: 0001 0000 的补码就是 0001 0001

通过上面的了解再来看下面这段代码
这里的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 的整数倍

相关内容

热门资讯

赛事火热、“南客北上”、长期参...   央视网消息:近日,随着第十二届全国大众冰雪季的全面启动以及寒假的陆续到来,各主要冰雪运动目的地客...
遥远星系里“小红点”神秘天体是...   中新网北京1月17日电 (记者 孙自法)国际知名学术期刊《自然》最新发表一篇天文学论文称,研究人...
美舰船过航台湾海峡 东部战区全...   东部战区新闻发言人就美“菲恩”号导弹驱逐舰、“西尔斯”号海洋测量船过航台湾海峡发表谈话  东部战...
全球首次!中国10万亿用电量背...   超过10万亿千瓦时的用电量这一数字在全球单一国家中尚属首次。  全社会用电量年度成绩单出炉。  ...
优良天数率超90% 绘就长江黄...   编者按:2026年是“十五五”开局之年。内需市场潜力持续释放,消费新场景不断涌现,文旅融合、冰雪...
视频丨“一半冰湖、一半沙海” ...   入冬以来,新疆冰雪旅游热潮持续升温,各大雪场迎来客流高峰。新疆巴州博湖县依托得天独厚的沙漠与湖泊...
马杜罗之子建议向国际组织寻求支...   新华社加拉加斯1月16日电 在16日举行的委内瑞拉法学家会议上,委内瑞拉总统马杜罗之子尼古拉斯·...
(经济观察)破万亿美元后再蓄新...   中新社南宁1月17日电 (记者 蒋雪林)中国海关总署近日公布的数据显示,2025年中国对东盟进出...
人民论坛网评 | “老字号”焕...   元旦假期,哈尔滨中华巴洛克历史文化保护街区累计接待游客19.8万人。百年老字号“张包铺”门前排起...
国宝画重点|不断刷新认知的“海...   汉代文明  上接先秦文脉之深厚  下启后世华章之滥觞  在历史坐标系中  占据着独一无二的地位 ...