雪花算法改造失败导致ID重复问题分享

背景

雪花算法是分布式应用中应用比较多的 ID 生成算法,某项目中使用该算法生成ID,近期被反馈算法生成的 ID 存在重复的情况,排了一天,终于找到问题根源了。

本文将总结这个 Bug ,顺便温故一下雪花算法及改造雪花算法支持更大的工作中心和机器码的注意事项。

雪花算法概览

雪花算法 (SnowFlake ),是 Twitter 开源的分布式 id 生成算法。
为什么叫雪花算法呢?据相关研究表示,一般的雪花大约由10的19次方个水分子组成。在雪花形成过程中,会形成不同的结构分支,所以说大自然中不存在两片完全一样的雪花,每一片雪花都拥有自己独特的形状。
雪花算法,顾名思义,算法生成的ID如雪花一般独一无二。

其核心思想是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的。

雪花算法的 64 位数据组成结构如下《原图来源》:
在这里插入图片描述

中间 10位=数据中心5位 + 机器码5位,最后12位存同一毫秒内的ID序号。
最后这三部分能表示的最大数值为:

数据中心个数:0-31
机器码个数:0-31
序号数:0-4095

算法可以保证一个工作中心的一台机器上,在同一毫秒内,生成一个唯一的 id。
同一毫秒内,由最后 12 位的序号累加生成不同 ID。

算法改造过程

项目对该算法做了改造,希望能配置更大的数据中心和机器码,所以把后三部分的长度调整为:

  1. 数据中心:9位,范围 0-511。
  2. 机器码:7位,范围 0-127。
  3. 相同毫秒内的序列号:10,范围 0-1024。

调整后,三部分总长就由算法推荐的 22 位调整为 26位,前面的时间戳部分长度就变成了 37 位。这导致了在不同毫秒、且相差几毫秒时生成的 ID 会重复。

打印某次测试时 ID 及当时的时间信息如下:

 Generate result is=5695462951227492352,timestamp=1720565011504,seq=0
 Generate result is=5695462951227492352,timestamp=1720565011505,seq=0
 Generate result is=5695462951227492352,timestamp=1720565011508,seq=0
 Generate result is=5695462951227492352,timestamp=1720565011509,seq=0

上面四次 ID 的时间戳相差1-5毫秒,但是按改造的算法却生成了4个相同的 ID ,判断根源在时间戳的存储长度不是算法推荐的 41 位导致的。

修正算法,保持时间戳长度为 41 位,其他三部分总长仍然是 22 位,生成的 ID 就不存在重复问题了,只是 数据中心+工作机器码占16 位后,序列号只有 6位,每毫秒只能生成 128 个 ID 了。

雪花算法实现类

雪花算法实现不过一百多行,还是比较简单的,到处都能搜到,《常见源码如下》:

public class IdWorker { 
    //因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
    //机器ID  2进制5位  32位减掉1位 31个
    private long workerId;
    //机房ID 2进制5位  32位减掉1位 31个
    private long datacenterId;
    //代表一毫秒内生成的多个id的最新序号  124096 -1 = 4095 个
    private long sequence;
    //设置一个时间初始值    2^41 - 1   差不多可以用69年
    private long twepoch = 1585644268888L;
    //5位的机器id
    private long workerIdBits = 5L;
    //5位的机房id
    private long datacenterIdBits = 5L;
    //每毫秒内产生的id数 212次方
    private long sequenceBits = 12L;
    // 这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 这个是一个意思,就是5 bit最多只能有31个数字,机房id最多只能是32以内
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 
    private long workerIdShift = sequenceBits;
    private long datacenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private long sequenceMask = -1L ^ (-1L << sequenceBits);
    //记录产生时间毫秒数,判断是否是同1毫秒
    private long lastTimestamp = -1L;
    public long getWorkerId(){
        return workerId;
    }
    public long getDatacenterId() {
        return datacenterId;
    }
    public long getTimestamp() {
        return System.currentTimeMillis();
    }
 
    public IdWorker(long workerId, long datacenterId, long sequence) {
 
        // 检查机房id和机器id是否超过31 不能小于0
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0",maxWorkerId));
        }
 
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
 
            throw new IllegalArgumentException(
                    String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }
 
    // 这个是核心方法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的id
    public synchronized long nextId() {
        // 这儿就是获取当前时间戳,单位是毫秒
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
 
            System.err.printf(
                    "clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(
                    String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
                            lastTimestamp - timestamp));
        }
 
        // 下面是说假设在同一个毫秒内,又发送了一个请求生成一个id
        // 这个时候就得把seqence序号给递增1,最多就是4096
        if (lastTimestamp == timestamp) {
 
            // 这个意思是说一个毫秒内最多只能有4096个数字,无论你传递多少进来,
            //这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
            sequence = (sequence + 1) & sequenceMask;
            //当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
 
        } else {
            sequence = 0;
        }
        // 这儿记录一下最近一次生成id的时间戳,单位是毫秒
        lastTimestamp = timestamp;
        // 这儿就是最核心的二进制位运算操作,生成一个64bit的id
        // 先将当前时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后12 bit
        // 最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型
        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) | sequence;
    }
 
    /**
     * 当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(long lastTimestamp) {
 
        long timestamp = timeGen();
 
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    //获取当前时间戳
    private long timeGen(){
        return System.currentTimeMillis();
    }
}

启示录

也没啥启示,就是想记录一下这个不成功的改造而已。

思考一个问题:雪花算法 32个工作中心、32台机器的这种默认配置,能否满足分布式环境下的 ID 生成的需求呢?1024 台机器的集群规模、每毫秒 4096 个 ID ,够不够用呢?

为什么要这样改造雪花算法,就是一个单机应用,改造数据中心参数到 9 位完全没必要。在ID生成不密集的情况下,ID很少重复,但是在 1秒内,密集的生成 ID 、且有毫秒及的时间差时,就产生 ID 重复了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mes系统在新材料行业中的应用价值

万界星空科技新材料MES系统是针对新材料制造行业的特定需求而设计的制造执行系统&#xff0c;它集成了生产计划、过程监控、质量管理、设备管理、库存管理等多个功能模块&#xff0c;以支持新材料生产的高效、稳定和可控。以下是新材料MES系统的具体功能介绍&#xff1a; 一、生…

创建 ComfyUI 自定义节点的基本指南

ComfyUI 自定义节点基础教程 开始前的准备理解 ComfyUI 节点创建自定义节点1. 定义节点参数2. 实现节点逻辑3. 与 ComfyUI 集成 测试和改进节点结论 ComfyUI 是一个多功能的Stable Diffusion图像/视频生成工具&#xff0c;能够让开发者设计并实现自定义节点&#xff0c;扩展功能…

无线领夹麦克风哪个牌子好,揭秘降噪领夹麦排行榜内幕!

在当今这个短视频如潮水般涌动的时代&#xff0c;人们的日常生活中掀起了一股新的潮流——用Vlog来捕捉生活的点点滴滴&#xff0c;许多博主在各种短视频和直播平台上开启了他们的副业之旅。这一现象催生了麦克风技术的飞速进步&#xff0c;使其从单一的录音工具转变为拥有多种…

私域运营从0到1冷启动

私域社群的冷启动是一个从无到有的过程&#xff0c;需要策略和耐心来吸引并维护用户。以下是一些步骤和策略&#xff0c;可以帮助你的私域社群实现从0到1的冷启动&#xff1a; 1. **明确目标和定位**&#xff1a; - 确定社群的目标用户和他们的需求。 - 明确社群的主题和…

一文清晰了解CSS——简单实例

首先一个小技巧&#xff1a; 一定要学会的vsCode格式化整理代码的快捷键&#xff0c;再也不用手动调格式了-腾讯云开发者社区-腾讯云 (tencent.com) CSS选择器用于选择要应用样式的HTML元素。常见的选择器包括&#xff1a; 类选择器&#xff1a;以.开头&#xff0c;用于选择具…

多个标签页中复用同一 QTableView

在 PyQt 中实现在多个标签页中复用同一个 QTableView 实例&#xff0c;复用同一个 QTableView 实例可以减少内存和资源的使用。每个 QTableView 实例都会消耗一定的内存和处理资源&#xff0c;如果每个标签页都创建一个新的实例&#xff0c;会增加系统的负担。通过复用实例&…

Hi3861鸿蒙开发环境搭建

1.1 安装配置Visual Studio Code 打开Download Visual Studio Code - Mac, Linux, Windows选择下载安装Windows系统的Visual Studio Code。 下载后进行安装。Visual Studio Code安装完成后&#xff0c;通过内置的插件市场搜索并安装开发所需的插件如图所示&#xff1a; 1.2 安…

2024 年最佳 Figma 字体

字体不仅仅是文本字符&#xff0c;它们还塑造了用户体验。从引导用户浏览界面到传达品牌个性&#xff0c;字体对于设计​​至关重要。然而&#xff0c;找到适合您的网站或应用风格的完美字体可能具有挑战性。 但不要害怕&#xff0c;我们会帮助您&#xff01;请继续关注&#x…

技术成神之路:设计模式(四)工厂方法模式

1.定义 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的接口&#xff0c;而不是通过具体类来实例化对象。工厂方法模式的主要作用是让子类决定实例化哪一个类&#xff0c;从而实现对象创建的延迟到具体子类…

EE trade:千足金是什么意思

千足金(也称足金999、足999、au999等)是一种金含量不低于999‰的黄金&#xff0c;这意味着在一千克的千足金中&#xff0c;至少有999克是纯金。由于其极高的纯度&#xff0c;千足金在市场上比普通的足金价格略高&#xff0c;虽然价格差距可能仅为每克几元&#xff0c;但在大批量…

轴心轨迹的绘制(包含降噪前处理,MATLAB)

由于旋转机械振动波形的噪声干扰大&#xff0c;直接对振动数据特征提取和选择的故障诊断方法&#xff0c;其精度容易受到噪声影响。当前&#xff0c;基于图像的旋转机械故障诊断技术已经得到飞速的发展。针对旋转机械的故障诊断问题&#xff0c;传统方法趋向于从振动数据中提取…

springboot美食分享平台-计算机毕业设计源码45429

基于Web美食分享平台的系统设计与实现 摘 要 本研究基于Spring Boot框架&#xff0c;设计并实现了一个Web美食分享平台&#xff0c;旨在为用户提供一个交流分享美食体验的社区平台。该平台涵盖了用户注册登录、美食制作方法分享发布、点赞评论互动等功能模块&#xff0c;致力于…

精简库存,避免售罄 零售商常见错误及策略

减少库存是库存管理中最容易被误解和管理不善的策略之一。但如果正确执行&#xff0c;精简运营可以大幅降低成本&#xff0c;同时减少缺货和新鲜产品的损坏。 问题是什么&#xff1f;太多企业在尝试精简库存时陷入了同样的陷阱。不依赖过剩库存的库存规划能够提供所需的灵活性…

七款知名电脑监控软件的介绍(2024年电脑监控软件整理推荐)

在信息化迅猛发展的今天&#xff0c;电脑监控软件成为企业管理和安全防护的重要工具。这类软件不仅有助于提高员工工作效率&#xff0c;还能防范数据泄露&#xff0c;保障企业的核心利益。以下是对几款知名电脑监控软件的介绍&#xff0c;它们在各自领域内都有出色表现。 固信…

【MPPT太阳能升压控制器方案】远翔升压恒流驱动芯片FP7209单节电池升压24V,30V,36V,42V,48V全系列方案,高转换效率,输出带短路保护功能

高转换效率&#xff0c;太阳能控制器方案——详解太阳能控制器PWM / MPPT极简方案其设计要点&#xff0c;升压30V&#xff0c;36V&#xff0c;42V&#xff0c;48V 使用单颗芯片FP7209即实现两级升压到30V&#xff0c;36V&#xff0c;42V&#xff0c;48V&#xff0c;相对于单极升…

Linux_网络编程_TCP

服务器客户端模型&#xff1a; client / server brow / ser b / s http p2p socket——tcp 1、模式 C/S 模式 》服务器/客户端模型 server :socket()-->bind()--->listen()-->accept()-->recv()-->close()client :socket()-->conn…

isaac sim 与 WLS2 ros2实现通信

Omniverse以及isaac还是windows下使用顺手一点&#xff0c;但是做跟ros相关的开发时候&#xff0c;基本就得迁移到ubuntu下了&#xff0c;windows下ros安装还是过于复杂&#xff0c;那不想用双系统或者ubuntu或者虚拟机&#xff0c;有啥别的好方法呢&#xff1f;这里想到了wind…

pcie 基础

1. 传输速率与带宽的关系 当我们谈论PCIe总线标准的传输速率时&#xff0c;我们使用GT/s&#xff08;Giga Transfers per second&#xff0c;千兆传输/秒&#xff09;来衡量&#xff0c;而不是Gbps&#xff08;Giga Bits Per Second&#xff0c;千兆位/秒&#xff09;。这是因为…

高德地图 key 和安全密钥使用

参考高德地图&#xff1a;JS API 安全密钥使用 高德地图 key 和安全密钥使用 一、通过明文方式设置参数查看如下成功后返回的信息 二、通过代理服务器转发实验&#xff1a;通过本地地址转发返回错的错误信息&#xff0c;如下通过正确的项目的的服务地址&#xff0c;返回正常参数…

基于Netty的自研流系统缓存实现挑战: 内存碎片与OOM困境

01 前言 Kafka 作为流处理平台&#xff0c;在实时流计算和在线业务场景&#xff0c;追尾读追求端到端低延迟。在离线批处理和削峰填谷场景&#xff0c;数据冷读追求高吞吐。两个场景都需要很好的数据缓存设计来支撑&#xff0c;Apache Kafka 的数据存储在本地文件&#xff0c…