首页 > 其他分享 >分布式ID生成

分布式ID生成

时间:2024-01-01 23:22:45浏览次数:24  
标签:雪花 生成 算法 protected bit id ID 分布式

王富贵 (lmlx66.top)

为什么要用分布式 ID

随着业务数据量的增长,存储在数据库中的数据越来越多,当索引占用的空间超出可用内存大小后,就会通过磁盘索引来查找数据,这样就会极大的降低数据查询速度。如何解决这样的问题呢?一般我们首先通过分库分表来解决,分库分表后就无法使用数据库自增 ID 来作为数据的唯一编号,那么就需要使用分布式 ID 来做唯一编号了。

分布式id解决方案

分类

我们所能够了解到的解决方案有以下几种

  • UUID
  • 数据库自增ID
  • 数据库多主模式
  • 号段模式
  • Redis原子性自增
  • 雪花算法(SnowFlake)
  • 滴滴出品(TinyID)
  • 百度 (Uidgenerator)
  • 美团(Leaf)

事实上,实用的方案总结起来就是三种

  1. 数据库自增
  2. 数据库自增演变的号段模式
  3. 雪花算法

雪花算法介绍(SnowFlake)

什么是雪花算法

雪花算法是Twitter公司发明的一种算法,主要目的是解决在分布式环境下,ID怎样生成的问题

特点:

  1. 分布式情况下多机器生成id不重复(id不能重复)
  2. long类型单调自增(单调递增的整形便于索引)
  3. 高并发(并发的情况下也能够使用)
  4. id不连续(如果连续,容易被计算出数量,比如用户数量)

其优点

  1. 经测试snowflake每秒能生成26万个自增可排序的ID
  2. snowflake生成的ID结果是一个64bit大小的整数,为一个Long型 (转换成字符串后长度最多19)
  3. 分布式系统内不会产生ID碰撞(datacenter和workerId作区分)并且效率高
  4. 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也非常高,可以根据自身业务分配bit位,非常灵活

雪花算法结构

  1. 第一个部分,是1个bit:0,这个是无意义的
    • 因为二进制里第一个bit为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个bit统一都是0
  2. 第二个部分是 41 个 bit:表示的是时间戳
    • 41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间
  3. 第三个部分是10个bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。
    • 但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可以代表 2 ^ 5 个机器(32 台机器),也可以根据自己公司的实际情况确定。
  4. 第五个部分是 12 个 bit:这个是用来记录同一个毫秒内产生的不同id
    • 12 bit 可以代表的最大正整数是 2 ^ 12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id

为了保证id的唯一性,我们是通过第二个部分的时间戳来保证了。那么这一部分的时间戳为当前的时间减去我们设置的时间的差值。比如说我设置起始时间为2022年1月1日,那么使用当前时间减去2022年1月1日时间戳,来保证每个毫秒生成的id唯一。但是这里会出现一个问题,那就是如果我动了设置的其实时间,那么时间差值就会发送改变,而可能造成生成id重复。这就是传统雪花算法所没有解决的问题,时间回拨

雪花算法问题

  1. 生成的ID太长。
    • 主键值太长,超过前端 js Number 类型最大值,须把 Long 型转换为 String 型
  2. 瞬时并发量不够。
  3. 不能解决时间回拨问题。
  4. 不支持后补生成前序ID(时间回拨问题)。
  5. 可能依赖外部存储系统。

优化雪花算法

事实上,传统的雪花算法是有一些缺点的,那么我们可以优化传统雪花算法的缺点么?

1、SnowFlake IdGenerator雪花算法

2、seata优化在开源项目中看到一个改良版的雪花算法,现在它是你的了。 - 知乎 (zhihu.com)

实现seata概念版雪花算法,主要改良的核心

img

package corecode.core;

import properties.IdGeneratorOptions;

import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 雪花
 *
 * @author zl
 * @date 2024/01/01
 */
public class SnowFlake {
    /**
     * 起始时间戳
     */
    protected final long StartStamp;
    /**
     * 时间戳
     */
    protected AtomicLong TimeStamp;
    /**
     * 节点ID
     */
    protected final  long WordId;
    /**
     * 节点ID长度
     */
    protected final byte WordIdBitLength;
    /**
     * 序列号长度
     */
    protected final byte SeqBitLength;
    /**
     * 节点ID最大值
     */
    protected final long MaxWordIdNum;
    /**
     * 序列号最大值
     */
    protected final long MaxSeqBitNum;
    protected final byte TimeStampLength=41;
    // 每部分向左的位移
    protected final  long WordIdLeft;
    protected final long TimeStampLeft;
    public SnowFlake(IdGeneratorOptions options){
        StartStamp= options.StartStamp<=0L?options.StartStamp:1704038400000L;
        WordIdBitLength=options.WordIdBitLength<=0?10:options.WordIdBitLength;
        WordId=options.WordId==-1?initWorkId(WordIdBitLength): options.WordId;
        SeqBitLength=options.SeqBitLength<=0?12:options.SeqBitLength;
        MaxWordIdNum=-1L ^ (-1L << WordIdBitLength);
        MaxSeqBitNum=-1L^(-1L<<SeqBitLength);
        TimeStampLeft=SeqBitLength;
        WordIdLeft=TimeStampLength+SeqBitLength;
        iniTimeStamp();
    }

    /**
     * INI 时间 Stamp
     *
     * @return long
     */
    private void iniTimeStamp() {
        long currentTimeStamp=System.currentTimeMillis()-StartStamp;
        long newTimeStamp = currentTimeStamp << SeqBitLength;
        TimeStamp = new AtomicLong(newTimeStamp);
    }

    /**
     * 初始化工作 ID
     *
     * @param wordIdBitLength 字 ID 位长度
     * @return short
     */
    private long initWorkId(byte wordIdBitLength) {
        //根据seata现在的方案是获取Mac值
         try{
             return generateWordIdByMac(wordIdBitLength);
         }catch (Exception e){
             return generateRandomWordId(wordIdBitLength);
         }
    }

    /**
     * 生成随机词ID
     *
     * @param wordIdBitLength 字 ID 位长度
     * @return short
     */
    private synchronized long generateRandomWordId(byte wordIdBitLength) {
        return  ThreadLocalRandom.current().nextLong(1<<wordIdBitLength);
    }

    /**
     * 通过 Mac 生成 Word ID
     *
     * @param wordIdBitLength 字 ID 位长度
     * @return short
     */
    private long generateWordIdByMac(byte wordIdBitLength) throws SocketException {
        Enumeration<NetworkInterface> network = NetworkInterface.getNetworkInterfaces();
        while(network.hasMoreElements()){
            NetworkInterface networkInterface = network.nextElement();
            boolean isLoopback = networkInterface.isLoopback();
            boolean isVirtual = networkInterface.isVirtual();
            if(isLoopback||isVirtual){
                continue;
            }
            byte[] mac = networkInterface.getHardwareAddress();
            return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF)&MaxWordIdNum;
        }
        throw new RuntimeException("没有发现可用的mac");
    }


    public long nextId(){
        long next = TimeStamp.incrementAndGet();
        return WordId<<WordIdLeft|next;
    }
}

package properties;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;


/**
 * @author: 王富贵
 * @description: 雪花算法配置参数
 * @createTime: 2022年05月19日 11:19:59
 * 雪花算法使用的参数
 * 参数说明,参考 README.md 的 “配置参数” 章节。
 */
@Data
@ApiModel("雪花算法配置信息实体")
public class IdGeneratorOptions {
    /**
     * 起始时间戳
     */
    @ApiModelProperty(value = "起始时间戳",notes = "默认为2024-01-01 00:00:00,不能超过当前系统时间")
    public  long StartStamp=1704038400000L;
    /**
     * 节点ID
     */
    @ApiModelProperty(value = "机器码", notes = "该实例机器码,必须唯一,必须由外部设定,最大值 2^WorkerIdBitLength-1")
    public long WordId=-1;
    /**
     * 节点ID长度
     */
    @ApiModelProperty(value = "机器码位长", notes = "决定项目集群能使用id最大机器数, 默认值10,取值范围 [1, 10](要求:序列数位长+机器码位长不超过22)")
    public byte WordIdBitLength=6;
    /**
     * 序列号长度
     */
    @ApiModelProperty(value = "序列号长度", notes = "决定项目集群能使用id最大机器数, 默认值12,取值范围 [1, 15](要求:序列数位长+机器码位长不超过22)")
    public  byte SeqBitLength=6;
}

标签:雪花,生成,算法,protected,bit,id,ID,分布式
From: https://www.cnblogs.com/wzl66/p/17939463

相关文章

  • 浏览器文档对象模型是如何生成的
    从网络传给渲染引擎的HTML文件字节流是无法直接被渲染引擎理解的,所以要将其转化为渲染引擎能够理解的内部结构,这个结构就是DOM。DOM提供了对HTML文档结构化的表述。在渲染引擎中,DOM有三个层面的作用。从页面的视角来看,DOM是生成页面的基础数据结构。从JavaScript脚本视角......
  • [Mac软件]ColorWell For Mac 7.4.0调色板生成器
    美丽而直观的调色板和调色板生成器是任何Web或应用程序开发人员工具包的必要补充!创建无限数量的调色板,快速访问所有颜色信息和代码生成,用于应用程序开发,非常简单。可编辑调色板数据库允许您存档和恢复任何调色板,以便稍后通过超快速搜索使用。您所有精心创建的调色板都与macOS调色板......
  • idea插件开发(2)-插件详细配置
    一、工程结构    本章详细讲解下插件工程的配置,官方在github上提供了一个idea插件开发的模板工程,可在https://github.com/JetBrains/intellij-platform-plugin-template下载,此工程模板与idea中创建的工程最大不同之处就是配置了github的ci-action流程以及junit-test,其它方......
  • java 生成自增流水号
    Java生成自增流水号的实现1.概述在开发中,生成自增流水号是一个常见的需求。自增流水号可以用于唯一标识每个对象或记录,确保数据的准确性和完整性。本文将介绍如何使用Java实现生成自增流水号的功能。2.实现步骤下面是生成自增流水号的实现步骤,我们将使用一个简单的例子来说明......
  • java 生成一个8位数的字母加数字的唯一id
    生成一个8位数的字母加数字的唯一ID在开发中,经常需要生成一个唯一的ID来标识某个对象或者实体。这个ID可以用来作为数据库的主键、文件名、URL参数等等。在本文中,我们将介绍如何使用Java生成一个8位数的字母加数字的唯一ID,并提供相应的代码示例。生成唯一ID的基本要求生成唯一ID......
  • java 生成不重复的订单编号
    Java生成不重复的订单编号简介在开发中,我们经常会遇到需要生成不重复的订单编号的需求。本文将介绍如何使用Java实现生成不重复的订单编号,并通过表格和代码示例来详细说明每一步需要做什么。流程下面是生成不重复订单编号的流程:步骤描述1.初始化订单编号将订单编号......
  • Java基础-JavaDoc生成文档
    JavaDocjavadoc命令是用来生成自己API文档的 参数信息@author作者名@version版本号@since指明需要最早使用的jdk版本@param参数名@return返回值情况@throws异常抛出情况 packagecom.wang.base;​/***@outhorWang*@version1.0*......
  • python 编写一个脚本 输入字符串 返回其长度 生成api接口
    python编写一个脚本输入字符串返回其长度生成api接口fromflaskimportFlask,request,jsonifyapp=Flask(__name__)@app.route('/get_length',methods=['POST'])defget_length():data=request.get_json()if'input_string'indata:......
  • 《安卓期末大作业(AndroidStudio开发),垃圾分类app,代码有注释,能正常运行》
    1 项目基本信息1.1 项目名称垃圾分类助手APP的设计与实现1.2开发运行环境开发语言:Java开发工具:AndroidStudio模拟器:雷电模拟器9数据库:SQLite  1.3使用的核心类及组件Activity:作为实现界面的窗体类BaseAdapter:适配器类SqliteOpenHelper:数据库类Intent:页面跳转传值Fragmen......
  • 安卓期末大作业(AndroidStudio开发),日记本app,代码注释详细,能正常运行
    安卓期末大作业-日记本app(附下载链接)压缩包内包含源代码,运行各个界面截图,一条日记可以记录2000字符以下的文本、最多8张配图和最多8个视频。每条日记都可以以评论的形式或转发引用的形式追更,评论的最大长度也是2000字符。日记还可以同时记录所处位置和当时的天气情况(当然,现在并......