首页 > 编程语言 >雪花算法

雪花算法

时间:2023-08-28 23:33:02浏览次数:34  
标签:雪花 long public 算法 时间 id

在项目中,需要给请求一个唯一标识,用来标识一个请求和响应的关联关系,要求请求的id必须唯一,且不能占用过大的空间,可用的方案如下:

1、自增id,单机的自增id不能解决不重复的问题,微服务情况下我们需要一个稳定的发号服务才能保证,但是这样做性能偏低。

2、uuid,将uuid作为唯一标识占用空间太大

3、雪花算法,最优解。

1、简介

雪花算法(snowflake)最早是twitter内部使用分布式环境下的唯一ID生成算法,他使用64位long类型的数据存储id,具体如下:

0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000

符号位 时间戳 机器码 序列号

最高位表示符号位,其中0代表整数,1代表负数,而id一般都是正数,所以最高位为0。当然知道了这个理论,甚至可以自由设定属于自己的雪花算法。

  • 41位存储毫秒级时间戳,这个时间戳不是存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 开始时间戳) * 得到的值),这样我们可以存储一个相对更长的时间。
  • 10位存储机器码,最多支持1024台机器,当并发量非常高,同时有多个请求在同一毫秒到达,可以根据机器码进行第二次生成。机器码可以根据实际需求进行二次划分,比如两个机房操作可以一个机房分配5位机器码。
  • 12位存储序列号,当同一毫秒有多个请求访问到了同一台机器后,此时序列号就派上了用场,为这些请求进行第三次创建,最多每毫秒每台机器产生2的12次方也就是4096个id,满足了大部分场景的需求。

总的来说雪花算法有以下几个优点:

  • 能满足高并发分布式系统环境下ID不重复
  • 基于时间戳,可以保证基本有序递增
  • 不依赖第三方的库或者中间件
  • 生成效率极高

2、手撸雪花

编写一个自己的雪花算法,可能并没有完全按照雪花算法的定义去实现,但是这正是程序的魅力,如我们的机房不需要那么多我们可以将时间戳的位数变的多一点,但事实上雪花算法是经过twitter等公司进行的最佳实践:

public class IdGenerator {

    // 雪花算法 -- 世界上没有一个片雪花是一样的
    // 机房号(数据中心) 5bit  32
    // 机器号           5bit  32
    // 时间戳(long 1970-1-1) 原本64位表示的时间,
    // 现在由41位构成,(我们整个案例中使用了42位,实时上有点问题,会出现负数,所有要看我们的业务需求)
    // 自由选择一个比较近的时间,比如我们公司成立的时间戳,项目启动的时间戳等
    // 同一个机房的同一个机器号的同一个时间可以因为并发量很大需要多个id
    // 序列号   12bit      5+5+42+12 = 64
    
    // 起始时间戳
    public static final long START_STAMP = DateUtil.get("2022-1-1").getTime();
    //
    public static final long DATA_CENTER_BIT = 5L;
    public static final long MACHINE_BIT = 5L;
    public static final long SEQUENCE_BIT = 12L;
    
    // 最大值 Math.pow(2,5) -1
    public static final long DATA_CENTER_MAX = ~(-1L << DATA_CENTER_BIT);
    public static final long MACHINE_MAX = ~(-1L << MACHINE_BIT);
    public static final long SEQUENCE_MAX = ~(-1L << SEQUENCE_BIT);
    
    
    // 时间戳 (42) 机房号 (5) 机器号 (5) 序列号 (12)
    // 101010101010101010101010101010101010101011 10101 10101 101011010101
    public static final long TIMESTAMP_LEFT = DATA_CENTER_BIT + MACHINE_BIT + SEQUENCE_BIT;
    public static final long DATA_CENTER_LEFT = MACHINE_BIT + SEQUENCE_BIT;
    public static final long MACHINE_LEFT = SEQUENCE_BIT;
    
    private long dataCenterId;
    private long machineId;
    private LongAdder sequenceId = new LongAdder();
    // 时钟回拨的问题,我们需要去处理
    private long lastTimeStamp = -1L;
    
    public IdGenerator(long dataCenterId, long machineId) {
        // 判断传世的参数是否合法
        if(dataCenterId > DATA_CENTER_MAX || machineId > MACHINE_MAX){
            throw new IllegalArgumentException("你传入的数据中心编号或机器号不合法.");
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }
    
    public long getId(){
        // 第一步:处理时间戳的问题
        long currentTime = System.currentTimeMillis();
        
        long timeStamp = currentTime - START_STAMP;
        
        // 判断时钟回拨
        if(timeStamp < lastTimeStamp){
            throw new RuntimeException("您的服务器进行了时钟回调.");
        }
        
        // sequenceId需要做一些处理,如果是同一个时间节点,必须自增
        if (timeStamp == lastTimeStamp){
            sequenceId.increment();
            if(sequenceId.sum() >= SEQUENCE_MAX){
                timeStamp = getNextTimeStamp();
                sequenceId.reset();
            }
        } else {
            sequenceId.reset();
        }
        
        // 执行结束将时间戳赋值给lastTimeStamp
        lastTimeStamp = timeStamp;
        long sequence = sequenceId.sum();
        return timeStamp << TIMESTAMP_LEFT |  dataCenterId << DATA_CENTER_LEFT
            | machineId << MACHINE_LEFT | sequence;
        
    }
    
    private long getNextTimeStamp() {
        // 获取当前的时间戳
        long current = System.currentTimeMillis() - START_STAMP;
        // 如果一样就一直循环,直到下一个时间戳
        while (current == lastTimeStamp){
            current = System.currentTimeMillis() - START_STAMP;
        }
        return current;
    }
    
    public static void main(String[] args) {
        IdGenerator idGenerator = new IdGenerator(1,2);
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> System.out.println(idGenerator.getId())).start();
        }
    }
    
}


标签:雪花,long,public,算法,时间,id
From: https://blog.51cto.com/u_10956218/7267757

相关文章

  • 数据结构与算法之美读书笔记
    读书笔记链接 时间复杂度分析只关注执行次数最多的一段代码加法法则:总复杂度等于量级最大的那段代码的复杂度乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积 最好、最坏、平均时间复杂度 数组内存中一块连续的存储空间,有效使用CPU的缓存机制,可以很方便......
  • 二叉树的存储结构和操作算法
    二叉树的存储结构和操作算法二叉树的存储结构1.顺序存储结构(完全二叉树/满二叉树)2.链式存储结构(一般二叉树).顺序存储结构按照满二叉树的结点层次编号,然依次后储存在数组当中如果该二叉树中位置是空的再对应到数组中的时候就使用0来填充.二叉树顺序存储结构的缺点......
  • 【C++STL基础入门】vector运算和遍历、排序、乱序算法
    @TOC前言C++标准库提供了丰富的容器和算法,其中vector是最常用的容器之一。它以动态数组的形式存储元素,并提供了许多方便的运算符和算法来操作和处理数据。本文将介绍vector的基本运算、遍历方法、排序算法以及乱序算法。通过学习这些内容,您将能够更加灵活、高效地使用vector容器。......
  • Lnton羚通视频算法算力云平台关于pandas 处理什么样的数据?
    pandas数据表格的表示 想存储一些 Titanic 乘客数据,知道姓名,年龄,性别等;df=pd.DataFrame({"Name":["Braund,Mr.OwenHarris","Allen,Mr.WilliamHenry","Bonnell,Miss.Elizabeth",......
  • Lnton羚通智能分析算法智慧校园AI视频智能分析算法
    智慧校园AI视频智能分析算法是一种利用人工智能和计算机视觉技术对校园监控视频进行实时分析和处理的算法。它可以通过自动检测、识别和分析视频中的各种目标、行为和事件,提供学校管理者和安全人员有关校园安全、教育管理和学生行为的重要信息。下面是一些常见的智慧校园AI视频智......
  • LRU算法
    思路LRU算法,访问/更新/插入都会将数据置于队尾(假设队头淘汰)。看3种情况的变化:插入:简单置于队尾即可。更新:删除原有节点,新增节点置于队尾。访问:将原节点提至队尾。除了插入只需要简单接到链表尾部以外,更新和访问都是可能操作链表中间的,所以自然地就需要引入Map来快速找到对......
  • 空间密度算法DBSCAN和K-means聚类算法有什么区别和联系
    DBSCAN(Density-BasedSpatialClusteringofApplicationswithNoise)和K-means是两种常见的聚类算法,它们有一些区别和联系。区别:原理:K-means是基于距离的划分聚类算法,通过最小化数据点与聚类中心之间的平方误差来进行聚类。DBSCAN是基于密度的聚类算法,通过将密度相连接的数据......
  • 遗传算法解决航路规划问题(MATLAB)
    遗传算法文章部分图片和思路来自司守奎,孙兆亮《数学建模算法与应用》第二版定义:遗传算法是一种基于自然选择原理和自然遗传机制的搜索(寻优)算法,模拟自然界中的声明进化机制,在人工系统中实现特定目标的优化。本质其实就是群体搜索技术,根据适者生存的原则逐代进化,最终得到最优解......
  • [代码随想录]Day29-贪心算法part03
    题目:1005.K次取反后最大化的数组和思路:思路是:先把负数从小到大变成正数(即绝对值由大到小)如果还需要变化(k>0),就变化最小的数在第一步变化的同时顺便记录一个数组和,那么结束之后会有三种情况:k==0;也就是说负数的个数大于等于k,直接返回结果k%2==0;此时全是正整数,......
  • Lnton羚通视频算法算力云平台【PyTorch】教程:torch.nn.ELU
    在PyTorch中,torch.nn.ELU代表指数线性单元(ExponentialLinearUnit),是一种激活函数。ELU函数可以用来增加神经网络的非线性表达能力,使其具备更强的适应性。ELU函数的定义如下:elu(x)=xifx>=0alpha*(exp(x)-1)ifx<0其中,x是输入,alpha是一个正数超参数,控制ELU......