雪花算法记录

2023-11-05

引子

伴随着业务的日渐庞大,单库单表的数据库可能无法支持业务的读写,需要对数据库进行分库分表。
原来数据库中,通常使用自增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());
        }

    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

雪花算法记录 的相关文章

随机推荐

  • Typora--图片上传方案Typora+PicGo+Gitee

    目录 一 简介 二 步骤 1 Gitee的部署 2 PicGo的设置 3 Typora的设置 三 其他 一 简介 当使用Typora的MarkDown编辑软件来做学习记录 上传图片时 都要创建一个文件夹来存放图片 这样子一来很不方便 有没有
  • 使用JDBC数据迁移把Mysql数据到另一个库中

    数据迁移 简介 整体思路链接2个需要迁移的数据库 根据sql 进行查询 判断什么样的数据需要迁移 什么样的数据需要过滤掉 数据重复或者出错的情况输出到某个文件中 如果数据可以整体迁移而且不出格式差异的情况也可以直接导出sql文件进行迁移 此
  • Binder 连接池的学习

    利用AIDL方式能很方便地进行客户端和服务端的跨进程通信 但是 我们想一下 如果按照我们之前的使用方法 必须满足一个AIDL接口对应一个service 那么问题来了 假如我们的应用 有很多业务场景 而每一个业务场景都需要和服务端通讯 那么我
  • MyBatis-Plus深入 —— 条件构造器与插件管理

    前言 在前面的文章中 荔枝梳理了一个MyBatis Plus的基本使用 配置和通用Service接口 我们发现在MyBatis Plus的辅助增强下我们不再需要通过配置xml文件中的sql语句来实现基本的sql操作了 不愧是最佳搭档 在这篇
  • keil5编译出现Error: L6411E:的解决办法

    出现这个问题很大的可能是keil5与ads产生冲突 此时只需要删除ads的环境变量即可 如下图 将其值含有ads的系统变量和用户变量全部删除 然后新建一个用户变量 变量名为ARMCC5LIB 其值要看你keil的安装路径 我的值是Y sof
  • android查看app是platform_app,system_app还是priv_app

    untrusted app 第三方app 没有Android平台签名 没有system权限 platform app 有android平台签名 没有system权限 system app 有android平台签名和system权限 从上面划
  • Windows10下载安装openjdk11及配置环境变量

    Windows10下载安装openjdk11及配置环境变量 下载JDK 首先我们需要下载java开发工具包JDK 下载地址 https cn azul com downloads zulu community version java 11
  • UE4外包团队:更新一下UE4和Unity3D案例

    全部的贴图都是用出的法线贴图构建的话只用了阳光和天光 都是静态光源 视角是第一人称模板最后的效果嘛就是全4K 120帧 0错误0警告 场景小是小了点但是效果还不错 工作活有时间更新 欢迎有UE4和Unity项目外包的联系我们 谢谢 转载于
  • CNN卷积核

    接着呢 我们需要处理我们的xs 把xs的形状变成 1 28 28 1 1代表先不考虑输入的图片例子多少这个维度 后面的1是channel的数量 因为我们输入的图片是黑白的 因此channel是1 例如如果是RGB图像 那么channel就是
  • Kafka - a simple consumer demo - c++

    Kafka 如果有 kafka 基础的同学可以不用看前面的废话 可以从第五条 配置 开始看起 代码在第七条 前言 官网比我这标准多了 官网跳转 大家可以先完成quickStart部分kafka单机生产消费 一 概念简介 Kafka 是一个分
  • 谷歌语法(详解+举例)

    一 谷歌语法是什么 谷歌语法就是利用搜索引擎在渗透测试过程搜索到特定页面的一种语法 二 如何利用谷歌语法 谷歌语法基础的符号 xxx 将要搜索的关键字用引号括起来 表示完全匹配 即关键词不能分开 顺序也不能变 例如 腾讯课堂如果不加 的话
  • 内存时延效能

    时延 Latency 小张一看到这个图 不禁大叫 太复杂了 看得我都犯密集恐惧症了 看不懂 没关系 我们拆开了一个个看 1 CL CAS Latency CL是指CAS发出之后 仍要经过一定的时间才能有数据输出 从CAS与读取命令发出到第一
  • 【华为OD统一考试A卷

    华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一修改为OD统一考试 A卷 和OD统一考试 B卷 你收到的链接上面会标注A卷还是B卷 请注意 根据反馈 目前大部分收到的都是
  • 在云服务器存储数据的10个好处

    本文编辑 富哥 云服务器已经成为最适合在线存储数据的选项 在较早时期 大多数公司依靠内部服务器来存储他们不断增长的数据和在线文件 但今天将数据存储在在线的云服务器中已经成为新的趋势 因为它允许无限存储 将所有数据存储在云中最好的方面是确保可
  • Java中类的构成

    Java中的每个类一般包含属性 构造器 块 方法 内部类五部分 属性 用来定义对象的数据 构造器 构造器也是方法 每一个类中都一定会有构造器 包含有参构造器和无参构造器每一个对象在创建的时候都会调用构造器 如果没有构造器 系统将提供一个默认
  • 十大经典排序算法总结

    https blog csdn net hellozhxy article details 79911867
  • AI嵌入式全景:各厂商、系列和开发工具的综合概览

    要看几个方面 1 算力 2 支持何种模型 3 是否支持可视化的窗口系统 一般而言各个平台均采用linux操作系统 官方提供对应SDK 安装好后可使用硬件加速资源 而且如果要使用其硬件加速 一般都要完成模型转换 将模型转为该平台所特有的格式
  • Spring Boot 学习研究笔记(十) -SpringBoot JAP 踩坑总结

    SpringBoot JAP 踩坑总结 一 JSON 字段映射处理流程 1 实现类型转换接口 package com call show common utils import com fasterxml jackson core Json
  • 最简单的鼠标放置悬停显示省略的内容,为标签的title赋值

    翻了一下午没看到能看懂的代码 对于我这个后台开发实在天书一般 原始需求为 内容过长显示为省略号 鼠标放置时再将全部内容悬浮展示出来 内容是放置在p标签中的 设置一下style即可 注意这四个属性缺一不可 p style width 100
  • 雪花算法记录

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