雪花算法的一些问题解析

前言

最近做项目,有些老旧项目,需要生成分布式唯一ID,不允许重复,此时如果要对其他中间件和数据库依赖小,那么就需要一套固定的ID生成规则,雪花算法就正当合适,当时Twitter就是用来存储数据库ID的,当然也可以对报文做ID,看具体的使用场景。但是坑就是使用过程中就埋下了隐患。

比如使用时间、数字长度等。 这些坑必须在SDK设计之初就有应对措施,否则很可能出现生成故障。

示例

根据github的Twitter地址:GitHub - twitter-archive/snowflake: Snowflake is a network service for generating unique ID numbers at high scale with some simple guarantees.

最开始的算法是scale写的(class类语言),原理实际上很简单

雪花算法使用long类型存储,64bit,8byte 。那么实际上数据为2的63次方,说明可能会出现负数。而且long的数字10进制位数会递增,递增随时间变化而变化,表现为开始快,后面进位慢的现象。

demo:来源于github,最初的作者已经找不到了。

package com.feng.snowflake.demo;public class SnowMaker {/** 开始时间截 (这个用自己业务系统上线的时间) */private final long twepoch = 1704038400000L;/** 机器id所占的位数 */private final long workerIdBits = 10L;/** 支持的最大机器id,结果是1023 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */private final long maxWorkerId = ~(-1L << workerIdBits);/** 序列在id中占的位数 */private final long sequenceBits = 12L;/** 机器ID向左移12位 */private final long workerIdShift = sequenceBits;/** 时间截向左移22位(10+12) */private final long timestampLeftShift = sequenceBits + workerIdBits;/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */private final long sequenceMask = ~(-1L << sequenceBits);/** 工作机器ID(0~1024) */private long workerId;/** 毫秒内序列(0~4095) */private long sequence = 0L;/** 上次生成ID的时间截 */private long lastTimestamp = -1L;//==============================Constructors=====================================/*** 构造函数* @param workerId 工作ID (0~1024)*/public SnowMaker(long workerId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId));}this.workerId = workerId;}// ==============================Methods==========================================/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public synchronized long nextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift) //时间戳位移| (workerId << workerIdShift) //机器码位移| sequence; //每次自增随机序号}/*** 阻塞到下一个毫秒,直到获得新的时间戳* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间* @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}
}

实际上,最核心的代码

return ((timestamp - twepoch) << timestampLeftShift) //
                | (workerId << workerIdShift) //
                | sequence;

时间戳位移固定22位(12位序列号+机器码10位),机器码固定位移12位。算法本身很正常,但是貌似时间戳没限制啊,很可能时间戳太大,直接占领符号位,从此变为负数,另外bit位存储是long-8字节,但是10进制可读数字,却是可变长的,正整数最大19位,负数最大20位

分析

 以上面的示例为例:以2024-1-1 00:00:00 000毫秒开始,计算10进制进位的可能性,以及69年的使用时间是怎么来的,实际上还可以负数,只是long存不下了,并不是真的只能使用69年,如果设计一种存储模式,把long的符号位加入存储,就可以存138年,实际上byte[]数组就是这么干的。

时间戳(毫秒)10进制位数10进制进位的数字值时间周期
1741943042024-01-01 00:00:00
38125829122024-01-01 00:00:00
2491006632962024-01-01 00:00:00
2391010024386562024-01-01 00:00:00
238511100034150402024-01-01 00:00:02
23842121000005959682024-01-01 00:00:23
2384191310000017653762024-01-01 00:03:58
238418614100000008765442024-01-01 00:39:44
23841858151000000003768322024-01-01 06:37:21
2384185801610000000037683202024-01-03 18:13:38
238418579217100000000041287682024-01-28 14:16:25
23841857911181000000000035389442024-10-02 22:44:17
2384185791021910000000000018350082031-07-22 11:22:59
21990232555511992233720368505815042093-09-06 15:47:35
219902325555220-92233720368547758082093-09-06 15:47:35
43980465111038-41943042163-05-15 07:35:11

可以看到雪花算法大概10个月多一点就会进位18位数字,但是在进19位时,需要7年左右,如果我们舍弃这7年,那么我们就可以得到一个固定数字位长度19位,但是只有62年左右的ID生成器。

同理69年的使用时间也是这么算出来的,因为long的设计,预留的41bit的时间戳,但是貌似没限制,如果我们代码没控制,那么69年后可以再得69年的负数。如果我们通过一个别的10进制符号位字符串标识,那么可以得到690年可以使用的不重复ID:

即20位字符串ID = 字符串0~9 + snowflake数字(最大后归0)。基本上符合绝大部分系统的设计。

存储解析

比如int的127和128和-1

可以看到127可以被byte[]的一个byte存储起来,但是128就使用了符号位,也是一个字节存储的,负数使用反码和补码来支持2进制存储,但是对于比如long,int等多个字节的byte[]存储,除了最大一位bit,其他位的byte实际上是没有符号位的,但是因为byte单个有符号位,所以,查看byte本身就使用负数存储了0~255的8bit的2进制整数。long同理,所以站在各种角度下有冲突的情况,因为所有的数据都是2进制的,反馈在输入输出流就说字节流,字符流实际上本质也是字节流。

总结

雪花算法实际上设计极为巧妙,通过时间戳,机器码,序列号(自增)来达到某个时间段(默认1毫秒)在某个并发下(并发超出自增ID就会重复或者阻塞等问题,不过我们一般达不到,且可以通过负载均衡增加资源规避),不重复ID。实现了加资源的方式来达到分布式ID不重复,且自增的特性。

但是雪花算法使用long存储,有自身限制,在以某个时间点为基线的情况,默认只能存储69年的ID,可以通过字符串扩展1位,实现600年的20位字符串ID,而且扩展的这1位可以提前的时间计算预警机制来实现进位和雪花的清0,因为我们可以精确的计算时间戳。

雪花算法高度依赖系统时间同步能力,有时间回拨的问题,这个很多解决思路。

雪花算法关键点,10进制的ID长度位数是变化的,变化的周期是可计算的,如果需要长度考虑,需要设计从19位开始,或者使用String.format("%020d", 10000000000000L)等方式,千万别认为是固定的。

雪花算法时间戳并没有限制归0,所以需要定制新的进制位字符串,或者重新更新时间戳计数基线,否则因为long的存储机制和时间戳的没限制bit,会出现负数。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JSP基础语法与指令

任何语言都有自己的语法&#xff0c;在java中有&#xff0c;JSP作为java技术的一种应用&#xff0c;它拥有一些自己扩充的语法(了解知道即可&#xff01;&#xff01;&#xff01;)&#xff0c; Java所有语法都支持&#xff01; JSP表达式 <html><head><title…

【Redis 初阶】初识 Redis

一、了解 Redis Redis 官网&#xff1a;Redis - The Real-time Data Platform Redis 是一种基于键值对&#xff08;key-value&#xff09;的 NoSQL 数据库。与很多键值对数据库不同的是&#xff0c;Redis 中的 key 都是 string&#xff08;字符串&#xff09;&#xff0c;值&a…

计算机毕业设计LSTM+Tensorflow股票分析预测 基金分析预测 股票爬虫 大数据毕业设计 深度学习 机器学习 数据可视化 人工智能

|-- 项目 |-- db.sqlite3 数据库相关 重要 想看数据&#xff0c;可以用navicat打开 |-- requirements.txt 项目依赖库&#xff0c;可以理解为部分技术栈之类的 |-- data 原始数据文件 |-- data 每个股票的模型保存位置 |-- app 主要代码文件夹 | |-- mod…

汽车辐射大?技术来救它:整车辐射抗扰发射天线仿真建模及性能预测

摘要 针对车辆电磁辐射抗扰度测试条件要求高、预测难度大的问题&#xff0c;通过仿真软件建立电磁抗扰度测试发射天线&#xff08;简称抗扰发射天线&#xff09;模型及无车情况下的电磁抗扰试验场强环境&#xff0c;为整车电磁辐射抗扰性能的预测搭建了一个仿真平台。 验证试验…

纹理映射学习笔记

概述 本文的纹理映射将三维曲面与二维的纹理建立对应关系。 曲面参数表达&#xff1a; x x ( s , t ) , y y ( s , t ) , z z ( s , t ) x x(s,t), y y(s,t), zz(s,t) xx(s,t),yy(s,t),zz(s,t) 即给定纹理坐标(s,t),我们能可以计算出曲面坐标(x,y,z) 映射 考虑由参数…

渲染技术如何帮助设计内容实现从平面到立体的转换

随着数字艺术和视觉特效的飞速发展&#xff0c;三维建模与渲染技术在影视、游戏、广告、工业设计、建筑可视化等多个领域展现出了其不可或缺的重要性。这一技术不仅实现了从平面到立体的跨越&#xff0c;还极大地丰富了视觉表达的层次感和真实感。 三维建模&#xff1a;构建虚…

ZYNQ 自定义IP端口映射

在做自定义IP时&#xff0c;对于总线接口&#xff0c;我们可以将其信号封装成接口&#xff0c;避免信号一个个地连接。在本实验中&#xff0c;需要封装axis slave接口&#xff0c;在Ports and Interfaces界面中&#xff0c;选择需要封装的信号&#xff0c;右键选择Add Bus Inte…

分享高效数据恢复工具:转转大师数据恢复软件等三款工具

哎&#xff0c;说起来都是泪啊&#xff0c;前阵子我那台陪伴了我无数个日夜的电脑&#xff0c;突然间就像跟我玩起了“躲猫猫”&#xff0c;一不留神&#xff0c;几个重要文件夹就这么悄无声息地“蒸发”了。心里那个急啊&#xff0c;就像热锅上的蚂蚁&#xff0c;团团转。好在…

Linux系统之部署记忆配对网页小游戏

Linux系统之部署记忆配对网页小游戏 一、小游戏介绍1.1 小游戏简介1.2 项目预览二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、检查本地环境3.1 检查系统版本3.2 检查系统内核版本3.3 检查软件源四、安装Apache24.1 安装Apache2软件4.2 启动apache2服务4.3 查看apache2…

【中项第三版】系统集成项目管理工程师 | 第 11 章 规划过程组⑥ | 11.15 - 11.17

前言 第11章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于10大管理的内容&#xff0c;学习要以教材为准。本章上午题分值预计在15分。 目录 11.15 规划资源管理 11.15.1 主要输入 11.15.2 主要工具与技术 11.15.3 主要输出 11.16 估算活动资源 11.1…

向有结果的人学习

有个朋友问我&#xff1a;我向有结果的人学习了&#xff0c;为何没有拿到结果&#xff1f;我觉得这个问题比较有代表性&#xff0c;决定写篇文章说说自己的看法。 现在比较流行一句话&#xff1a;向有结果的人学习。这句话本身没毛病&#xff0c;向有结果的人学习那是一定的&am…

Animate基本概念:补间动画

补间动画是Animate软件中比较重要的组成部分。 举例来说&#xff0c;假设第 1 帧和第 20 帧是属性关键帧&#xff0c;可以将舞台左侧的一个元件放在第 1 帧中&#xff0c;然后将其移至舞台右侧的第 20 帧中。在创建补间时&#xff0c;Animate 将计算影片剪辑在此中间的所有位置…

AI驱动的个性化招聘策略:重塑人才选拔的未来

一、引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已渗透到各行各业&#xff0c;为企业带来了前所未有的变革。在人力资源管理领域&#xff0c;AI的应用同样不容忽视。特别是在招聘环节&#xff0c;AI技术的引入不仅提高了效率&#xff0c;更通过数据…

coreDNS

1.概述 coreDNS的作用主要是作为DNS服务器&#xff0c;在集群内提供服务发现功能&#xff0c;也就是服务之间的互相定位的过程。他监听集群中service和pod的创建和销毁事件&#xff0c;当serivice或者pod被创建时&#xff0c;记录对应的解析记录。当其他pod通过域名来访问集群中…

css实现线条中间高亮,左右两边模糊(linear-gradient的运用)

效果&#xff1a; <div class"line"></div> .line {height: 1px;background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, #a9c2ff 50%, rgba(255, 255, 255, 0) 100%);border-radius: 4px 4px 4px 4px; } CSS实现边框底部渐变色的方法:(最简单…

如何开发属于自己直播平台的主播美颜SDK?

本篇文章&#xff0c;笔者将从需求分析、技术选型、开发流程等方面进行详细讲解。 一、需求分析 在开发美颜SDK之前&#xff0c;首先需要进行详细的需求分析。主要包括以下几个方面&#xff1a; 1.美颜功能的具体需求&#xff1a;确定美颜效果&#xff0c;包括磨皮、美白、瘦…

Go语言实战:基于Go1.19的站点模板爬虫技术解析与应用

一、引言 1.1 爬虫技术的背景与意义 在互联网高速发展的时代&#xff0c;数据已经成为新的石油&#xff0c;而爬虫技术则是获取这种“石油”的重要工具。爬虫&#xff0c;又称网络蜘蛛、网络机器人&#xff0c;是一种自动化获取网络上信息的程序。它广泛应用于搜索引擎、数据分…

docker安装与container基本使用

安装 Homebrew 的 Cask 已经支持 Docker for Mac, mac用户狂喜 brew install --cask --appdir/Applications docker其他入门用法可参考 Docker Hello World- 菜鸟教程 或网上自行搜索博客学习。本文主要记录我运行go-zero-mall用到的一些注意点。当然&#xff0c;gonivinck项…

【深度学习】语音,Mel频谱图的前世今生

Mel频谱图的前世今生 背景与基本概念 Mel频谱图是音频信号处理中的一种表示形式&#xff0c;用于将音频信号转换为二维图像&#xff0c;这种表示形式在语音识别、语音合成和音频分类等领域中广泛应用。要理解Mel频谱图&#xff0c;首先需要了解以下几个基本概念&#xff1a; …