sahrding-jdbc的雪花算法取模为0或1的问题

news2024/12/26 21:59:16

工作时无意间发现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的源码跟读一下

    @Override
    public 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位 或 获取到的sequence
        return ((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 的整数倍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/150354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何有效的防护暴力破解和撞库攻击

在网络威胁领域&#xff0c;暴力破解攻击仍然是网络犯罪分子非常喜爱且有利可图的攻击方法。&#xff0c;黑客通过收集互联网已泄露的用户和密码信息&#xff0c;生成对应的字典表&#xff0c;由于许多用户重复使用相同的用户名和密码&#xff0c;攻击者可以使用撞库攻击获得对…

前缀和算法

目录1.概述2.代码实现2.1.一维前缀和2.2.二维前缀和3.应用本文参考&#xff1a; LABULADONG 的算法网站 1.概述 前缀和算法分为一维和二维&#xff0c;一维前缀和可以快速求序列中某一段的和&#xff0c;而二维前缀和可以快速求一个矩阵中某个子矩阵的和。 2.代码实现 2.1.一…

约拍小程序开发,优化约拍产业路径

随着网红、直播经济的发展&#xff0c;年轻群体对于拍摄服务的需求正在急速增长&#xff0c;与此同时该群体用户又极具个性&#xff0c;很多传统影楼拍摄价格高、拍摄风格固定化、等待时间久&#xff0c;个人摄影师宣传推广难度又大&#xff0c;导致拍摄需求被抑制&#xff0c;…

_Linux多线程-基础篇

文章目录1. 什么是线程Linux中的线程叫做轻量级进程&#xff08;LWP&#xff09;2. 线程的优点3. 线程的缺点4. 线程异常5. 线程用途6. Linux进程VS线程单进程7. 总结线程在进程内部执行是OS调度的基本单位。不同视角看待进程轻量级进程1. 什么是线程 在一个程序里的一个执行路…

【Cfeng Work】 Open API的intro和 梳理

OpenAPI 开放平台 内容管理Open API intro腾讯云OpenAPIOpenAPI请求API密钥管理云API签名过程拼接规范请求串 CanonicalRequest拼接待签名字符串计算签名拼接到Authorization中云API签名失败规范Token 和 长期密钥公共参数OpenApi设计AppId、AppSecretsign签名timestamp 时间戳…

制造服务行业需要项目管理软件吗?

伴随着时代的快速发展&#xff0c;电子制造服务行业也正在飞速发展&#xff0c;在日新月异得时代步伐下&#xff0c;科学得管理方法和现代化得管理工具相结合&#xff0c;保证企业得进步与发展。项目管理工具通过构造高效统一的项目管理平台&#xff0c;优化企业管理中存在的问…

win10 conda安装labme安装和使用

1、安装conda 在WIN10中配置conda 1.1miniconda下载 https://docs.conda.io/en/latest/miniconda.html 可以任意选择对应的版本&#xff0c;安装时选择配置路径&#xff0c;以免后期重复配置环境。 1.2.2 在环境变量中添加路径 Win R&#xff0c;打开运行&#xff0c;输入…

在ubuntu系统上用pyinstaller加密打包yolov5项目代码的详细步骤

目录0. 背景1. 创建虚拟环境2. pyinstaller打包2.1. 生成并修改spec文件2.2. 重新生成二进制文件3. 测试4. 加密打包4.1. 创建入口函数main.py4.2. 修改detect.py4.3. 加密生成main.spec文件4.4. 修改main.spec文件4.5. 生成二进制文件4.6. 测试0. 背景 最近需要在ubuntu 18.0…

Payso×OceanBase:云上拓新,开启云数据库的智能托管

日前&#xff0c;聚合支付厂商 Payso&#xff08; 独角鲨北京科技有限公司&#xff09;的平台、交易系统引入 OceanBase 原生分布式数据库&#xff0c;实现显著降本增效—— 硬件成本降低 20&#xff5e;30%&#xff0c;查询效率提升 80%&#xff0c;执行效率提升了 30%&#x…

学习IB生物,我们需要知道什么知识点?

学习IB课程的很多同学应该都听说过一个说法&#xff1a;IB生物算是理科中的文科&#xff0c;没有公式推导&#xff0c;只有大量需要记忆的内容&#xff0c;不需要用学习理科的思维去学习&#xff0c;其实这种观点是有误区的。实际上&#xff0c;学习生物这门课将会面对的是一个…

01背包问题详解

目录 1.1二维dp数组 1.2一维dp数组改进 1.3相关例题 1.3.1分割等和子集 1.3.2一和零 1.1二维dp数组 概述&#xff1a;背包的最大重量是固定的&#xff0c;物品的数量&#xff0c;重量也是固定的&#xff0c;并且物品只能放一次&#xff0c;可以选择放或者不放&#xff0c…

Redis 核心原理串讲(中),架构演进之高可用

文章目录Redis 核心原理总览&#xff08;全局篇&#xff09;前言一、持久化1、RDB2、AOF3、AOF 重写4、混合持久化5、对比二、副本1、同步模式2、部分重同步三、哨兵1、核心能力2、节点通信总结Redis 核心原理总览&#xff08;全局篇&#xff09; 正文开始之前&#xff0c;我们…

【FPGA】Verilog:基本实验步骤演示 | 功能电路创建 | 添加仿真激励 | 观察记录仿真波形

前言&#xff1a; 本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载的完整过程、Verilog语言基本运用&#xff0c;电路设计和Test Bench程序的编写、以及实验开发板的使用&#xff0c;通过观察和数据记录理解仿真和FGPA实现的差异。 目录 Ⅰ. 基础知…

考研政治 马原 易混淆知识点

马哲 1. 哲学基本问题 从何者为第一性&#xff0c;分为唯物主义和唯心主义 从是否具有同一性&#xff0c;分为可知论&#xff08;有同一性&#xff09;和不可知论&#xff08;无同一性&#xff09; 辩证法&#xff1a;联系&#xff0c;发展的观点看世界&#xff0c;认为发展的…

python对接API二次开发高级实战案例解析:百度地图Web服务API封装函数(行政区划区域检索、地理编码、国内天气查询、IP定位、坐标转换)

文章目录前言一、IP定位1.请求URL2.获取IP定位封装函数3.输出结果二、国内天气查询1.请求url2.天气查询封装函数3.输出结果三、行政区划区域检索1.请求url2.区域检索封装函数3.输出结果四、地理编码1.请求url2.地理编码封装函数3.输出结果五、坐标转换1.请求url2.坐标转换封装函…

一文细说Linux虚拟文件系统原理

在 Unix 的世界里&#xff0c;有句很经典的话&#xff1a;一切对象皆是文件。这句话的意思是说&#xff0c;可以将 Unix 操作系统中所有的对象都当成文件&#xff0c;然后使用操作文件的接口来操作它们。Linux 作为一个类 Unix 操作系统&#xff0c;也努力实现这个目标。 虚拟…

CSS 这个就叫优雅 | 多行文本溢出省略

CSS 这个就叫优雅 | 多行文本溢出省略 文章目录CSS 这个就叫优雅 | 多行文本溢出省略一、文本溢出省略方式二、WebKit内核浏览器解决方法&#x1f959;三、通用解决方法四、CSS 预处理器封装&#x1f969;五、参考资料&#x1f498;六、推荐博文&#x1f357;一、文本溢出省略方…

小样本学习(Few-Shot Learning)训练参数意义

一、常规参数 1.1 epoch 是指所有的训练数据都要跑一遍。假设有6400个样本&#xff0c;在训练过程中&#xff0c;这6400个样本都跑完了才算一个epoch。一般实验需要训练很多个epoch&#xff0c;直到LOSS稳定后才停止。 1.2 batch_size 中文名称是批大小&#xff0c;之前的640…

【数据结构趣味多】二叉树概念及性质

1.树的定义 定义&#xff1a;树&#xff08;Tree&#xff09;是n&#xff08;n>0&#xff09;个结点的有限集。n0时称为空树。在任意一棵非空树种&#xff1b; 有且仅有一个根结点&#xff08;root&#xff09;。当n>1时&#xff0c;其余结点可分为m&#xff08;m>0&a…

H13-531云计算HCIE V2.0——400~600常错题和知识点总结

400~600 422、在 FusionCloud 6.x 中&#xff0c;以下关于备份的说法哪项是错误的&#xff1f; A&#xff0e;备份协议支持本地&#xff0c;通过 FTP/SFTP 到第三方服务器及 OBS B. 为了保证系统稳定运行&#xff0c;对管理数据进行备份恢复可以确保在异常时对业务的影响降到…