雪花算法SnowFlake 细致易懂 Java/Springboot实现

这篇具有很好参考价值的文章主要介绍了雪花算法SnowFlake 细致易懂 Java/Springboot实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、雪花算法是什么?

分布式中生成唯一性ID的一种算法 。

为啥不用数据库的自增主键呢?

  • 唯一性: 如果数据库数据特别多,你会同一张表建立不同节点上,数据也在不同节点上存,那么如果俩ID都是 001 违背主键 定义吗?

  • 顺序性: 雪花算法计算出来ID 有顺序 如果你了解数据库 B+树 ,对于索引来说 字段是 数字类型 ,有顺序, 唯一 在查找以及插入效率很高 而UUID是字符串没顺序不适合做数据库主键了

二、雪花算法的构成

雪花算法 由64位构成ID,对应java数据类型的话 long类型 这里位是二进制位

  • 最左一位(图中没有标出)都是 0 因为二进制中 最左符号位 1代表负数 0是正数 而我们生成ID肯定是正数所以是0
  • timestamp : 时间戳 我们一般情况给初始时间 用系统当前时间 减去 初始时间 这个差值的时间戳作为ID的时间戳也就是timestamp占用41位。至于为啥差值作为时间戳 1.减小时间戳长度 2.时钟回拨处理
  • instance :这个表示机器个数 分布式系统中 多个节点 可以左边5位是机器的ID 右边5位 数据中心ID(机房ID)加起来是10位
  • sequence:序列号 这个主要用途 并发执行代码 有时候获取时间一样的 那么区分ID 用序列号自增进行区分

springboot 雪花算法,java,算法,spring boot
时钟回拨: 一种情况管理员手动把时间调整当前系统之前时间,这样的话生成ID和之前ID可能冲突了。虽然上边时间戳插值减轻该问题但是插值仍有可能为正值,

三、雪花算法实现思路

3.1 如何把 时间戳,机器ID,序列号等合在一起变成一个long类型数字?

3.2 如果并发访问 同一时间对于 要生成ID多于2的12次方个 也就是多余4096个ID如何处理?

3.3 发生时钟回拨如何处理?


3.1的思路是

  • 首先移动好说的 。数据中心ID datacenterID (属于instance) 假设 1L 号 用long表示可以 (二进制)
    0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
    将它左移动12位(序列号最长位)代码 datacenterID << 12 结果
    0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000

  • 机器ID 左移动 12位(序列号) + 5位(数据中心ID)= 17 workID << 17
    0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0000 0000 0000 0000

  • 我们随便用时间戳差值 1110 0101 1010 0001 0001 10111 11 那么他图中右移12 + 5 + 5 = 22位
    0000 0000 0000 0000 1110 0101 1010 0001 0001 10111 1100 0000 0000 0000 0000 0000

  • 序列号最右边所以不用移动
    0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

接下来如何合并时间戳,机器ID,数据中心ID,序列号呢?
可以将两个long类型 按位或 进行合并 (按位或 只要有1 结果1 否则是0)
拿数据中心ID和机器ID举例子

  • 数据ID 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000
  • 机器ID 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0000 0000 0000 0000

(或 | )结果 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0001 0000 0000 0000
这样进行合并了 3.1问题解决了。


3.2 问题思路

0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
该时间再生成ID就是
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000
对于这个我们构建一个类似于掩码数字 二进制表示
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
进行按位与 (&)两个都为1结果是1 否则为0
上边两个& 结果刚好 0 这个结果可以判断是否溢出
对于构建掩码那个数字 可以 -1 ^ (-1 << 12) ^ 是异或符号 两个二进制位 不同为1 相同为0
-1 的二进制如下
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111


3.3 问题思路

  • 可以记录上一次时间字段,如果当前时间 < 上一次记录时间 发送时钟回拨 抛异常提示
  • 可以进行等待,时间过了上一次记录时间进行ID生成
  • 也可以采用更高时间精度来生成时间戳
Clock clock = Clock.systemUTC();
long currentTimestamp = clock.millis();

三、Java代码实现

代码如下(示例):

package com;
public class SnowFlakeUtil{
    //起始时间戳
    private static long startTimeStamp;
    //机器ID
    private static long workID;
    //数据中心ID
    private static long  dataCenterID;
    //序列号
    private static long sequence;
    //数据中心ID移动位数
    private static long dataCenterIndex;
    //机器ID移动位数
    private static long workIDIndex;
    //时间戳移动位数
    private static long timeStampIndex;
    //记录上一次时间戳
    private static long lastTimeStamp;
    //序列号掩码
    private static long sequenceMask;
    //序列号长度12位
    private static long sequenceLength;

    //初始化数据
    static {
        startTimeStamp = 1577808000000L;
        //设置机器编号 1
        workID = 1L;
        //设置数据中心ID 1
        dataCenterID = 1L;
        //起始序列号 0开始
        sequence = 0L;
        //数据中心位移位数
        dataCenterIndex = 12L;
        //机器ID位移位数
        workIDIndex = 17L;
        //时间戳位移位数
        timeStampIndex = 22L;
        //记录上次时间戳
        lastTimeStamp = -1L;
        //序列号长度
        sequenceLength = 12L;
        //序列号掩码
        sequenceMask = -1L ^ (-1L << sequenceLength);
    }
    public synchronized static long getID(){
        //获得当前时间
        long now = System.currentTimeMillis();
        //当前系统时间小于上一次记录时间
        if (now < lastTimeStamp){
            throw new RuntimeException("时钟回拨异常");
        }
        //相同时间 要序列号进制增量
        if (now == lastTimeStamp){
            //防止溢出
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0L){
                //溢出处理
                try {
                    Thread.sleep(1L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //获取下一毫秒时间 (有锁)
                now = System.currentTimeMillis();
            }
        }else{
        	//置0 之前序列号同一时间并发后自增 到这里说明时间不同了 版本号所以置0
        	sequence = 0L;
        }
        //记录当前时间
        lastTimeStamp = now;
        //当前时间和起始时间插值 右移 22位
        long handleTimeStamp = (now - startTimeStamp) << timeStampIndex;
        // 数据中心数值 右移动 17位 并且 按位或
        long handleWorkID = (dataCenterID << dataCenterIndex) | handleTimeStamp;
        // 机器ID数值 右移动 12位 并且 按位或
        long handleDataCenterID = (workID << workIDIndex) | handleWorkID;
        // 序列号 按位或
        long ID = handleDataCenterID | sequence;
        return ID;
    }
}
//用法调用工具类方法即可
long id = SnowFlakeUtil.getID();

四、Springboot代码实现

4.1 Maven引入依赖

    <!--  开始属性绑定 配置文件有提示  -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>

4.2 创建 SnowFlakeProperties java文件

注意包名和你项目包路径要一致 该文件放在启动类同级目录或者子目录

package com;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "sf")
public class SnowFlakeProperties {
    //起始时间戳
    private long startTimeStamp;
    //机器ID
    private long workID;
    //数据中心ID
    private long dataCenterID;
    //序列号
    private long sequence;
    //数据中心ID移动位数
    private long dataCenterIndex;
    //机器ID移动位数
    private long workIDIndex;
    //时间戳移动位数
    private long timeStampIndex;
    //记录上一次时间戳
    private long lastTimeStamp;
    //序列号掩码
    private long sequenceMask;
    //序列号长度12位
    private long sequenceLength;

    public SnowFlakeProperties() {
      //默认时间 2020-1-1 00:00:00
      this.startTimeStamp = 1577808000L;
      //设置机器编号 1
      this.workID = 1L;
      //设置数据中心ID 1
      this.dataCenterID = 1;
      //起始序列号 0开始
      this.sequence = 0L;
      //数据中心位移位数
      this.dataCenterIndex = 12L;
      //机器ID位移位数
      this.workIDIndex = 17L;
      //时间戳位移位数
      this.timeStampIndex = 22L;
      //记录上次时间戳
      this.lastTimeStamp =  -1L;
      //序列号长度
      this.sequenceLength = 12L;
      //序列号掩码 等同于 -1L ^ (-1L << 12)
      this.sequenceMask = ~ (-1L << sequenceLength);
    }

    public SnowFlakeProperties(long startTimeStamp, long workID, long dataCenterID, long sequence,
                               long dataCenterIndex, long workIDIndex, long timeStampIndex, long lastTimeStamp,
                               long sequenceMask, long sequenceLength) {
      this.startTimeStamp = startTimeStamp;
      this.workID = workID;
      this.dataCenterID = dataCenterID;
      this.sequence = sequence;
      this.dataCenterIndex = dataCenterIndex;
      this.workIDIndex = workIDIndex;
      this.timeStampIndex = timeStampIndex;
      this.lastTimeStamp = lastTimeStamp;
      this.sequenceMask = sequenceMask;
      this.sequenceLength = sequenceLength;
    }

    public long getStartTimeStamp() {
      return startTimeStamp;
    }

    public void setStartTimeStamp(long startTimeStamp) {
      this.startTimeStamp = startTimeStamp;
    }

    public long getWorkID() {
      return workID;
    }

    public void setWorkID(long workID) {
      this.workID = workID;
    }

    public long getDataCenterID() {
      return dataCenterID;
    }

    public void setDataCenterID(long dataCenterID) {
      this.dataCenterID = dataCenterID;
    }

    public long getSequence() {
      return sequence;
    }

    public void setSequence(long sequence) {
      this.sequence = sequence;
    }

    public long getDataCenterIndex() {
      return dataCenterIndex;
    }

    public void setDataCenterIndex(long dataCenterIndex) {
      this.dataCenterIndex = dataCenterIndex;
    }

    public long getWorkIDIndex() {
      return workIDIndex;
    }

    public void setWorkIDIndex(long workIDIndex) {
      this.workIDIndex = workIDIndex;
    }

    public long getTimeStampIndex() {
      return timeStampIndex;
    }

    public void setTimeStampIndex(long timeStampIndex) {
      this.timeStampIndex = timeStampIndex;
    }

    public long getLastTimeStamp() {
      return lastTimeStamp;
    }

    public void setLastTimeStamp(long lastTimeStamp) {
      this.lastTimeStamp = lastTimeStamp;
    }

    public long getSequenceMask() {
      return sequenceMask;
    }

    public void setSequenceMask(long sequenceMask) {
      this.sequenceMask = sequenceMask;
    }

    public long getSequenceLength() {
      return sequenceLength;
    }

    public void setSequenceLength(long sequenceLength) {
      this.sequenceLength = sequenceLength;
    }
}

4.3 创建 SnowFlake java文件

package com;
public class SnowFlake {
    private SnowFlakeProperties properties;

    public SnowFlake() {
    }

    public SnowFlake(SnowFlakeProperties properties) {
      this.properties = properties;
    }

  public synchronized  long getID(){
    //获得当前时间
    long now = System.currentTimeMillis();
    long lastTimeStamp = properties.getLastTimeStamp();
    //当前系统时间小于上一次记录时间
    if (now < lastTimeStamp){
      throw new RuntimeException("时钟回拨异常");
    }
    //相同时间 要序列号进制增量
    if (now == lastTimeStamp){
      //防止溢出
      long sequence = properties.getSequence();
      sequence = (sequence + 1) & properties.getSequenceMask();
      //更新sequence 的值
      properties.setSequence(sequence);
      if (sequence == 0L){
        //溢出处理
        try {
          Thread.sleep(1L);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
        //获取下一毫秒时间 (有锁)
        now = System.currentTimeMillis();
      }
    }else {
      //置0
      properties.setSequence(0L);
    }
    //记录当前时间
    properties.setLastTimeStamp(now);
    return ((now - properties.getStartTimeStamp()) << properties.getTimeStampIndex()) |
        (properties.getDataCenterID() << properties.getDataCenterIndex())|
        (properties.getWorkID() << properties.getWorkIDIndex()) | properties.getSequence();
  }
}

4.4 在任意@Configuration 或者启动各类加上注解

@EnableConfigurationProperties(SnowFlakeProperties.class)

配置类里这样写即可

	@Bean
    public SnowFlake snowFlake(SnowFlakeProperties properties) {
      return new SnowFlake(properties);
    }

我是这样写的

package com;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(SnowFlakeProperties.class)
public class Config {

    @Bean
    public SnowFlake snowFlake(SnowFlakeProperties properties) {
      return new SnowFlake(properties);
    }

}
package com;

import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {
  //注入即可
  @Autowired
  public SnowFlake snowFlake;


  @RequestMapping("/hello")
  public Map getID(){
    //这里直接调用该方法 获取雪花算法生成ID
    long id = snowFlake.getID();
    HashMap<Object, Object> map = new HashMap<>();
    map.put("ID",id);
    map.put("Binary",Long.toBinaryString(id));
    map.put("BinaryLength",Long.toBinaryString(id).length());
    return map;
  }
}

如何设置机器ID,数据中心ID呢?

当然你不设置也可以 默认1号机器 1号数据中心
配置文件
springboot 雪花算法,java,算法,spring boot
如果没有提示重新构建项目
springboot 雪花算法,java,算法,spring boot

由于前端(Web) 最大支持53位(二进制)数字 所以后端传给前端转换字符串来存 否则精度会损失

可以通过配置文件来设置
雪花算法 41位时间戳 不要动 剩下(53-41=12) 剩下12位 可以分6位的序列号 ,数据中心分2位 机器ID分4位 配置文件如

sf:
  work-i-d: 3
  #序列号长度  
  sequence-length: 6
  #数据中心位移移 位数  
  data-center-index: 6
  #机器ID位移移 位数  
  work-i-d-index: 8
  #时间戳位移移 位数  
  time-stamp-index: 12

结果如图
springboot 雪花算法,java,算法,spring boot

测试

  • 这里机器ID1 数据中心1 序列号0
    springboot 雪花算法,java,算法,spring boot
  • 用JMeter 100个请求并发 查询时间相同请求
    springboot 雪花算法,java,算法,spring boot

springboot 雪花算法,java,算法,spring boot

springboot 雪花算法,java,算法,spring boot
可以看到序列号进行递增

结尾

参考:博客 请叫我小叶子

如果觉得有帮助 求一个攒赞 感谢!!!文章来源地址https://www.toymoban.com/news/detail-855623.html

到了这里,关于雪花算法SnowFlake 细致易懂 Java/Springboot实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • hutool工具包 中的雪花算法Snowflake 获取Long类型id 或者String 类型id(全局唯一id解决方案)

    1.引入pom依赖 2.源码 3. 注入 使用 4优缺点:

    2024年02月14日
    浏览(42)
  • 实现高性能ID生成器:详解Java雪花算法

    Java中的雪花算法(Snowflake Algorithm)是一种用于生成唯一ID的算法,可以在分布式系统环境中防止ID重复。这种算法最初由Twitter开发,用于生成Twitter的唯一ID,由于其简单易懂和高效,已成为目前最常用的生成唯一ID的算法之一。 雪花算法生成的ID是一个64位的长整型数字,可

    2023年04月27日
    浏览(35)
  • Java工具集 Hex、Hmac算法(MD5、SHA1、SHA256、SHA384、SHA512)、雪花算法SnowflakeId、redis基于Springboot工具类

    🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄 🌹简历模板、学习资料、面试题库、技术互助 🌹文末获取联系方式 📝 专栏 描述 Java项目实战 介绍Java组件安装、使用;手写框架等 Aws服务器实战 Aws Linux服务器上操作nginx、git、JDK、Vue Jav

    2024年04月09日
    浏览(77)
  • 雪花算法的使用(java)

    雪花算法( Snowflake )是一种分布式唯一 ID 生成算法,能够生成唯一的、有序的、高可用的 ID,常用于分布式系统中作为全局唯一标识符(GUID)。雪花算法生成的 ID 是一个 64 位的整数,其中高位是时间戳,中间位是机器 ID,低位是序列号。 雪花算法生成的 ID 包含以下信息

    2024年02月01日
    浏览(66)
  • 通俗易懂实现功能强大的实战项目 springboot+java+vue+mysql 汽车租赁管理系统

    ✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 | SpringBoot/SSM Python实战项目 | Django 微信小

    2024年01月19日
    浏览(47)
  • 通俗易懂实现功能强大的实战项目 springboot+java+vue+mysql 汽车服务管理系统

    ✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 | SpringBoot/SSM Python实战项目 | Django 微信小

    2024年01月16日
    浏览(38)
  • 【Java笔记】分布式id生成-雪花算法

    随着业务的增长,有些表可能要占用很大的物理存储空间,为了解决该问题,后期使用数据库分片技术。将一个数据库进行拆分,通过数据库中间件连接。如果数据库中该表选用ID自增策略,则可能产生重复的ID,此时应该使用分布式ID生成策略来生成ID。 snowflake是Twitter开源的

    2024年02月11日
    浏览(43)
  • 【Spring Cloud系列】 雪花算法原理及实现

    分布式高并发的环境下,常见的就是12306节日订票,在大量用户同是抢购一个方向的票,毫秒级的时间下可能生成数万个订单,此时为确保生成订单ID的唯一性变得至关重要。此时秒杀环境下,不仅要保障ID唯一性,还得确保ID生成的优先度。 全局唯一 :不能出现重复的ID号,

    2024年02月09日
    浏览(35)
  • JAVA实用工具: 改良版雪花算法-分布式唯一ID神器

    Seata内置了一个分布式UUID生成器,用于辅助生成全局事务ID和分支事务ID。具体如下特点: 高性能 全局唯一 趋势递增 这个分布式UUID生成器是基于雪花算法进行改良的,本文针对改良的方法、目的等进行总结 改良版雪花算法的实现原理参考如下: Seata基于改良版雪花算法的分

    2024年02月14日
    浏览(41)
  • 开箱即用轻量级雪花算法id生成器Java工具类

    在 Java后端研发过程中,对于分布式微服务来说,一般需要分布式 id生成. 这里分享一个非常好用且大多数情况下都可用的开箱即用轻量级雪花算法id生成器Java工具类。 这种方式生成的雪花算法生成器生成的唯一主键id,好处是不依赖第三方组件,轻量级,缺点是服务器的时钟

    2024年02月07日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包