雪花算法记录

news2024/9/9 0:36:21

引子

伴随着业务的日渐庞大,单库单表的数据库可能无法支持业务的读写,需要对数据库进行分库分表。
原来数据库中,通常使用自增id的方式生成主键。分库分表之后,如果仍然采用原来的方式,在多个表之间主键会发生重复。
分库分表后,如何保证多张表中的 id 是全局唯一性的呢?

方法

针对此问题,通常有三种解法

  1. uuid
  2. 数据库主键自增。eg:两张表,一张奇数、一张偶数
  3. 雪花算法

此外,Redis 的自增原子性来生成唯一id,比较少用。

原理

雪花算法最早由Twitter提出,格式如下
在这里插入图片描述

  • 最高 1 位固定值 0,因为生成的 id 是正整数,如果是 1 就是负数了。
  • 接下来41位存储毫秒级时间戳,2^41/(1000606024365)=69,大概可以使用 69 年。
  • 再接下 10 位存储机器码,包括 5 位 datacenterId 和 5 位 workerId。最多可以部署 2^10=1024 台机器。
  • 最后 12 位存储序列号。同一毫秒时间戳时,通过这个递增的序列号来区分。即对于同一台机器而言,同一毫秒时间戳下,可以生成 2^12=4096 个不重复 id。

思考

  • 雪花算法有以下几个优点:

    • 高并发分布式环境下生成不重复 id,每秒可生成百万个不重复 id。
    • 基于时间戳,以及同一时间戳下序列号自增,基本保证 id 有序递增。
    • 生成单调自增的唯一ID,在innodb的b+数表中顺序插入不会造成页的分裂,性能高。(uuid的话每个id是随机的,大量的随机IO效率不但低,还会使innodb页造成分裂和合并,使得插入效率低)
    • 生成64位id,只占用8个字节节省存储空间。
    • 不依赖第三方库或者中间件。
    • 算法简单,在内存中进行,效率高。
  • 雪花算法有如下缺点:

    • 依赖服务器时间:
      • 服务器时钟回拨时可能会生成重复 id。
      • 每台数据库的本地时间都要设置相同,否则会导致全局不递增

解法:
算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。如果发生回拨

  • 回拨时间较短,进行等待,直至时间达到最后一个生成id的时间+1,再继续产生。
  • 回拨时间长,报错处理
  • 选取两个bit,最初是00,接下来会有01、10、11三种情况,可以接受三次时间回拨。

代码

public class SnowFlakeUtils {
    /*
        起始时间时间戳:这个时间为第一次运行时的时间,这里设置为2021/11/23/19/17
        可以在未来的69年内稳定运行
     */
    private final static long START_STMP=1637666189914L;


    private final static long SEQUENCE_BIT=12;//序列号占用12bit
    private final static long MACHINE_BIT=5;//机器号占用5bit
    private final static long MACHINE_HOUSE_BIT=5;//机房号占用5bit
    /*
        -1的源码   10000001
        -1的反码   11111110
        -1的补码   11111111
        -1左移12位= 1111 1111 0000 0000 0000
        -1       = 1111 1111 1111 1111 1111
        异或运算  = 0000 0000 1111 1111 1111=4095
        因此MAX_SEQUENCE的值为4095
     */
    private final static long MAX_SEQUENCE=-1L^(-1L<<SEQUENCE_BIT);
    //同理 MAX_MACHINE为31
    private final static long MAX_MACHINE=-1L^(-1L<<MACHINE_BIT);
    //同理 MAX_MACHINE_HOUSE值为31
    private final static long MAX_MACHINE_HOUSE=-1L^(-1L<<MACHINE_HOUSE_BIT);
    //机器ID
    private long machineID;
    //机房ID
    private long machineHouseID;
    private long lastTime=0;//上一次生成ID的时间戳
    private long sequence=0;//序列号,默认为0

    public SnowFlakeUtils(long machineID, long machineHouseID) {
        this.machineID = machineID;
        this.machineHouseID = machineHouseID;
    }

    public long getMachineID() {
        return machineID;
    }

    public void setMachineID(long machineID) {
        this.machineID = machineID;
    }

    public long getMachineHouseID() {
        return machineHouseID;
    }

    public void setMachineHouseID(long machineHouseID) {
        this.machineHouseID = machineHouseID;
    }


    /***
     *产生下一个ID
     * 用long型来表示我们生成的64位ID,
     * @return
     */

    public  synchronized long nextId(){
        if(machineHouseID>MAX_MACHINE_HOUSE ||machineID>MAX_MACHINE){
            throw new RuntimeException("机房ID或机器ID超出最大值");
        }
        //获取当前时间戳
        long currentTime=System.currentTimeMillis();
        //如果当前时间小于上一次生成ID的时间,抛出异常
        if(currentTime<lastTime){
            throw new RuntimeException("当前时间为异常值,请勿回拨时间!");
        }
        //如果当前时间等于上一次生成ID时间,说明是在同一毫秒中生成,那么序列号加一
        else if(currentTime==lastTime){
            /*
                MAX_SEQUENCE: 0000 1111 1111 1111
                            &
                        4096: 0001 0000 0000 0000
                           = 0
                 当sequence小于4095时, (sequence+1)&MAX_SEQUENCE=sequence+1
                 当sequence等于4095时,(sequence+1)&MAX_SEQUENCE=0
             */
            sequence= (sequence+1)&MAX_SEQUENCE;
            if(sequence==0L){
                //获取下一个毫秒值
                currentTime=getNextMill();
            }

        }else{
            //毫秒值不同,sequence初始为0
            sequence=0L;
        }
        //更新最近一次生成时间的毫秒值
        lastTime=currentTime;
        return (currentTime-START_STMP)<<22//左移22位 空出机房ID5位+机器ID5位+序列号12位
                |machineID<<12//左移12位 空出序列号12位
                |machineHouseID<<17//左移17位 空出机器ID5位+序列号12位
                |sequence;//序列号部分
    }

    /**
     * 获取下一个毫秒值
     * @return
     */
    private  long getNextMill() {
        long mill=System.currentTimeMillis();
        //如果当前时间等于上一次的时间则一直自旋
        while(mill==lastTime){
            mill=System.currentTimeMillis();
        }
        return mill;

    }

    /**
     * Main方法测试
     * @param args
     */

    public static void main(String[] args) {
        //初始化一个雪花算法工具类,设置机房ID和机器ID都为0
        SnowFlakeUtils snowFlakeUtils=new SnowFlakeUtils(0,0);
        for (int i = 0; i <100; i++) {
            //生成100个ID
            System.out.println(snowFlakeUtils.nextId());
        }

    }
}

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

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

相关文章

斑梨电子树莓派Zero 2W显示屏7寸DIY电容触摸屏RJ45 USB HUB接口 兼容Banana pi Zero

spotpear.cn/index/product/detail/id/1320.html detail.tmall.com/item.htm?id719583990252&spma211lz.success.0.0.63982b90oweBSa 【产品简介】 为了让你的Zero正常工作&#xff0c;你需要很多模块&#xff0c;如一个显示器&#xff0c;一个USB HUB&#xff0c;一个…

Java StringBuilder类

3 StringBuilder可变字符串类 StringBuilder是一个可变的字符串类,内容可以改变3.1 StringBuilder构造方法 范例public class Demo {public static void main(String[] args) {//创建空白可

chatgpt赋能python:Pythonspidev简介

Python spidev简介 Python spidev是一个可以与SPI设备进行通信的Python库。SPI是一种简单的通信协议&#xff0c;通常用于与单片机或其他嵌入式设备进行通信。Python spidev库可以使用SPI协议读写数据&#xff0c;然后与其他设备交换数据。 开发环境和使用方法 开发环境 Py…

如何把视频中的声音提取出来转化成文字?

在观看电影、综艺节目等视频内容时&#xff0c;我们可以使用视频转文字的方法来帮助我们更好地了解对话内容&#xff0c;从而提高观影体验。那么&#xff0c;如何把视频声音转成文字&#xff1f;视频声音转成文字的软件有哪些呢&#xff1f;我给你介绍几个非常好用的视频声音转…

PCL学习之点云重建

1&#xff1a;点云模型重建 离散点云 • 数据量大 • 渲染显示大 • 模型操作计算不方便 网格模型 • 数据量小 • 渲染方便 • 模型操作计算方便 重建步骤 2&#xff1a;凸包算法 凸包 • 平面凸包&#xff1a;平面的一个子集S被称为是“ 凸”的&#xff0c;当且仅当对于任…

【Netty】ChannelPipeline源码分析(五)

文章目录 前言一、ChannelPipeline 接口1.1 创建 ChannelPipeline1.2 ChannelPipeline 事件传输机制1.2.1 处理出站事件1.2.2 处理入站事件 二、ChannelPipeline 中的 ChannelHandler三、ChannelHandlerContext 接口3.1 ChannelHandlerContext 与其他组件的关系3.2 跳过某些 Ch…

tcp拥塞控制

序列号: 在TCP连接中传送的字节流中的每一个字节都按顺序编号。该字段表明发送数据的第一个字节的序号 确认号&#xff1a;希望收到对方下一个报文的第一个字节的序号 窗口&#xff1a;用于通知发送端&#xff0c;接收端可以接收的空间大小 TCP的流量控制是使用滑动窗口机制&…

C4d Octane渲染器内存满、卡顿、崩溃、缓慢、updating解决办法

最近碰到Octane渲染动画序列&#xff0c;总是会渲染一段时间后卡在某一张图片上&#xff0c;图片查看器左下角一直显示updating。 偶然发现在C4D界面点击octane工具栏的设置&#xff0c;它又会开始渲染&#xff0c;但渲染一些序列帧后又会卡在一张图上显示updating 点击octane工…

MyBatis-Plus01_简介、入门案例、BaseMapper与IService中的CRUD以及常用的注解

目录 ①. MyBatis-plus简介 ②. MyBatis-plus入门案例 ③. BaseMapper中的CRUD ④. 通用Service的CRUD ⑤. MyBatis-plus中常用注解TableName&#xff1a; ⑥. 常用注解TableId ⑦. 雪花算法 ⑧常用注解TableField ⑨. 常用注解TableLogic——逻辑删除专用注解 ①. M…

chatgpt赋能python:PythonUDS:让你的汽车掌握更多技能

Python UDS&#xff1a;让你的汽车掌握更多技能 UDS&#xff08;Unified Diagnostic Services&#xff09;是一种汽车电子控制单元&#xff08;ECU&#xff09;通信协议&#xff0c;用于车辆的诊断和测试。Python UDS是用Python编程语言实现的UDS客户端和服务器实现&#xff0…

【stable diffusion保姆级教程,左手ChatGPT之剑,右手stablediffusion之矛】

一、前言 哈喽&#xff0c;大家好&#xff0c;我是Tian-Feng&#xff0c;前面写过两篇文章&#xff0c;但是细节没认真写&#xff0c;除了介绍一些参数意思&#xff0c;和推荐模型插件&#xff0c;有一定基础的小伙伴应该是挺有用的&#xff0c;但如果是小白&#xff0c;可能还…

完全二叉树——堆的概念及实现

前言 堆(heap)&#xff1a;是堆内存的简称&#xff0c;堆是动态分配内存&#xff0c;内存大小不固定&#xff0c;也不会自动释放&#xff0c;堆——数据结构是一种无序的树状结构&#xff0c;同时它还满足key-value键值对的存储方式。 1. 堆的概念及结构 如果有一个关键码的…

BFC与IFC

概念 块级元素在BFC布局&#xff08;块级格式化上下文&#xff09; 行内级元素在IFC布局&#xff08;行内级格式化上下文&#xff09; BFC 形成BFC的情况 BFC规则 在BFC中box在垂直方向排列在同一个BFC中&#xff0c;相邻box垂直方向外边距塌陷在BFC中box左边缘紧贴包含块的…

Python数据结构与算法篇(十五)-- 二叉树的遍历:深度优先搜索与广度优先搜索

本篇开始总结二叉树的常用解题技巧&#xff0c;二叉树的顺序遍历和层序遍历刚好对应深度优先搜索和广度优先搜索。 1 顺序遍历 题目列表 144. 前序遍历145. 二叉树的后序遍历 94. 二叉树的中序遍历 144. 二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它…

程序员开发之“留一手“

很多乙方公司为了顺利获得项目的尾款&#xff0c;或者有些项目的封装整合的逻辑比较多&#xff0c;通常会把项目的业务逻辑代码及架构进行打包成线上NuGet包。 一、 NuGet包 其实就是线上的.dll文件 &#xff0c;在本地编译后上传是NuGet 1、首先注册NuGet 2、记住API Key …

chatgpt赋能python:Python*a:提高代码效率的利器

Python *a&#xff1a;提高代码效率的利器 Python是一种高层次、通用性编程语言。Python的简洁语法和宽松语义&#xff0c;让它成为了软件开发、数据分析、科学计算等领域的首选语言之一。Python也因其易学、易读、易部署的特点而被全球越来越多的开发者所喜爱。在这篇文章中&…

Redis事务及网络处理

一 Redis事务 redis开启事务后&#xff0c;会把接下来的所有命令缓存到一个单独的队列中&#xff0c;在提交事务时&#xff0c;使这些命令不可被分割的一起执行完成。 如果使用了watch命令监视某一个key&#xff0c;如果在开启事务之后&#xff0c;提交事务之前&#xff0c;有…

超级牛散也踩雷!这A股宣布大消息

公司被债权人申请重整一事被法院正式立案7个多月后&#xff0c;5月24日&#xff0c;*ST搜特收到了法院的终结预重整程序通知书和不予受理重整申请裁定书。 消息曝出后&#xff0c;*ST搜特股吧则瞬间炸锅&#xff0c;投资者纷纷留言“完了”、“没盼头了”、“最后的希望终究还…

浅谈IAM——OAuth2.0攻击方法总结

一、OAuth协议介绍 OAuth是一种标准授权协议&#xff0c;它允许用户在不需要向第三方网站或应用提供密码的情况下向第三方网站或应用授予对存储于其他网站或应用上的信息的委托访问权限。OAuth通过访问令牌来实现这一功能。 1.发展历史 OAuth协议始于2006年Twitter公司OpenI…

车载以太网 - SomeIP - 协议用例 - BehaviorBasic

目录 Service Discovery Communication Behavior 1、验证DUT启动后的重复报文阶段,2帧offer报文之间的时间间隔为上次的2倍<