雪花算法(Snowflake)介绍和Java实现

1、雪花算法介绍

(1) 雪花算法(SnowFlake)是分布式微服务下生成全局唯一ID,并且可以做到去中心化的常用算法,最早是Twitter公司在其内部的分布式环境下生成ID的方式。 雪花算法的名字可以这么理解,世界上没有两片完全相同的雪花,而雪花算法希望自己生成的ID是独一无二的。

去中心化可以理解成不需要依赖某一个中间件,比如可以用Redis来生成全局唯一的ID,但Redis此时就属于中心,同时还会需要依赖网络。 而雪花算法通过10位bit的本地标识实现去中心化。

(2) 雪花算法生成的ID特点

  • 64bit位的正整数,即java中的long类型;
  • 整体结构是有序的。

(3) 64个bit位

  • 最高位:0, 代表是 一个正整数;
  • 41位:存储毫秒级的时间戳,在java中可以使用 System.currentMillons()获取,并且保证了自增特性;
  • 10位:存储机房/机器/操作系统/容器/服务的ID;
  • 12位:存储一个自增的序列。

说明:雪花算法内部的bit位数可以进行微调,比如5位机器id和5位服务id组合成10位。

2、Java方式实现雪花算法

(1)整体实现逻辑

64个bit位的long类型的值

第一位:占 1 个bit位,就是0

第二位:占 41 个bit位, 代表时间戳

第三位:占 5 个bit位, 代表机器id (这里将 10 bit 位 做了调整)

第四位:占 5 个bit位,服务id

第五位:占 12 个bit位, 自增序列

(2) 具备知识

  • java实现bit位移运算以及异或运算,用于计算固定bit位代表的最大数值,以及将bit位移动到固定位置。

例如:java中41个bit位的最大数值

long max41Bit = (1L << 41) - 1; // 41 bit 位 可以表示的最大数值 2的42次方减1, 即 1往左移41位减1

(3) 核心逻辑

  1. 先是定义5个位数对应的变量, 以及对应的偏移量,因为需要通过偏移后变量的bit位才能到达固定的位置;
  2. 再是计算自定义的机器id和服务id的最大值,用于严谨校验;
  3. 分别拿到对应的值,然后做位移运算;
  4. 做位移运算时的时间戳不从1900-01-01开始算起,因为41bit位的时间戳大概可用70年。

逻辑难点:在同一毫秒生成多个ID时,当前时间戳 与 自增序列的关系

  1. 拿到当前系统的毫秒值,记录生成上一个ID的毫秒值;
  2. 如果是同一毫秒生成ID,则自增序列递增(递增时要注意不能超出递增序列允许的最大值,超出需要等待下一毫秒);不同毫秒自增序列还原为初始值;
  3. 对于时针回拨问题,需要注意将当前的时间戳与生成的上一个ID的时间戳进行比较。

(4) Java代码具体实现


import org.springframework.beans.factory.annotation.Value;
import javax.annotation.PostConstruct;/*** 雪花算法生成全局唯一的ID* 64个bit位的long类型的值* 第一位:占 1 个bit位,就是0* 第二位:占 41 个bit位, 代表时间戳* 第三位:占 5 个bit位, 代表机器id (这里将 10 bit 位 做了调整)* 第四位:占 5 个bit位,服务id* 第五位:占 12 个bit位, 自增序列*/
public class SnowFlakeUtil {/*** 41 个bit位存储时间戳, 从0开始计算, 最多可以存储 69.7年。* 如果从默认使用, 从1970年到现在,最多可以用到2040年。* 按照从 2023-12-28号开始计算,存储41个bit位, 最多可以使用到2093年*/private long timeStart = 1703692800000L;/*** 机器id, 通过yml配置的方式声明*/@Value("${snowflake.machineId:0}")private long machineId = 0;/*** 服务id, 通过yml配置的方式声明*/@Value("${snowflake.serviceId:0}")private long serviceId = 0;/*** 自增序列*/private long sequence;// 需要做机器id和服务id的兼容性校验, 不能超过了5位的最大值/*** 机器id占用的bit位数*/private long machineIdBits = 5L;/*** 服务id占用的bit位数*/private long serviceIdBits = 5L;/*** 序列占用的bit位数*/private long sequenceBits = 12L;/*** 计算出机器id的最大值 -1 往左移 machineIdBits 位, 再做亦或运算*/private long maxMachineId = -1 ^ (-1 << machineIdBits); // -1 往左移 machineIdBits 位, 再做亦或运算// 11111111 11111111 11111111 11111111 11111111// 11111111 11111111 11111111 11111111 11100000// 00000000 00000000 00000000 00000000 00011111/*** 计算出服务id的最大值*/private long maxServiceId = -1 ^ (-1 << serviceIdBits);/*** 校验 机器id 和 服务id 是否超过最大范围值*/@PostConstructpublic void init() {if (machineId > maxMachineId || serviceId > maxServiceId) {System.out.println("机器id或服务id超过最大范围值");}}/*** 服务id需要位移的位数, 即从右侧开始, 将数字左移 sequenceBits 到固定的位置*/private long serviceIdShift = sequenceBits;/*** 机器id需要位移的位数, 即从右侧开始, 将数字左移 sequenceBits + serviceIdBits  到固定的位置*/private long machineIdShift = sequenceBits + serviceIdBits;/*** 时间戳需要位移的位数, 即从右侧开始, 将数字左移 sequenceBits + serviceIdBits + machineIdBits 到固定的位置*/private long timestampShift = sequenceBits + serviceIdBits + machineIdBits;/*** 序列的最大值 -1 往左移 sequenceBits 位, 再做亦或运算*/private long maxSequenceId = -1 ^ (-1 << sequenceBits);/*** 记录最近一次获取id的时间*/private long lastTimestamp = -1;/*** 拿到当前系统时间的毫秒值** @return*/private long timeGen() {return System.currentTimeMillis();}/*** 生成全局唯一id* 因为有很多服务调用这个方法, 所以需要加sychronized锁*/public synchronized long nextId() {//1. 拿到当前系统时间的毫秒值long timestamp = timeGen();// 避免时间回拨造成出现重复的idif (timestamp < lastTimestamp) {// 说明出现了时间回拨System.out.println("当前服务出现时间回拨");}//2. 41个bit的时间知道了存什么了, 但是序列也需要计算一下。 如果是同一毫秒,序列就需要 还原 或者 ++// 判读当前生成的id的时间 和 上一次生成的时间if (timestamp == lastTimestamp) {// 同一毫秒值生成idsequence = (sequence + 1) & maxSequenceId; // 加1最大值进行与运算, 结果是如果超过了maxSequenceId则为0, 小于则不变if (sequence == 0) {// 进到这个if,说明已经超出了sequence序列的最大取值范围// 需要等到下一个毫秒值再回来生成具体的值timestamp = timeGen();// 写 <= 而不 写 == 是为了避免出现时间回拨的问题while (timestamp <= lastTimestamp) {// 时间还没动timestamp = timeGen();}}} else {// 另一个时间点生成idsequence = 0;}//3. 重新给 lastTimestamp 赋值lastTimestamp = timestamp;//4. 计算id,将几位值拼接起来, 41bit位的时间, 5位的机器, 5位的服务, 12位的序列return ((timestamp - timeStart) << timestampShift) | // 相减的差值 往左移  timestampShift(machineId << machineIdShift) |  // machineId 往左移  machineIdShift(serviceId << serviceIdShift) |  // serviceId 往左移  serviceIdShiftsequence &Long.MAX_VALUE;}
}

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

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

相关文章

Select缺点及代码示例

一、Select缺点 二、服务器端 #include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/select.h>int main() {// 创建socketint lfd socket(PF_INET, SOCK_STREAM, 0)…

006、函数

1. 一个小技巧 在前面文章中&#xff0c;我们提到&#xff0c;在黑窗口中输入 code . 命令可以快速在 Visual Studio Code 中打开新建的项目&#xff0c;这个是你刚刚新建了项目&#xff0c;并且黑窗口正好是打开的情况下。 如果是之前创建的项目&#xff0c;用上面的方法就会有…

git(安装,常用命令,分支操作,gitee,IDEA集成git,IDEA集成gitee,IDEA集成github,远程仓库操作)

文章目录 1. Git概述1.1 何为版本控制1.2 为什么需要版本控制1.3 版本控制工具1.4 Git简史1.5 Git工作机制1.6 Git和代码托管中心 2. Git安装3. Git常用命令3.1 设置用户签名3.1.1 说明3.1.2 语法3.1.3 案例实操 3.2 初始化本地库3.2.1 基本语法3.2.2 案例实操3.2.3 结果查看 3…

【Java】log4j和slf4j区别

log4j&#xff1a;Apache Software Foundation 开源 slf4j&#xff1a;不支持日志滚动等高级功能 在开源库或内部库中使用 SLF4J&#xff0c;将使其独立于任何特定的日志记录实现&#xff0c;这意味着无需为多个库管理多个日志记录配置&#xff0c;您的客户端将会很需要这一点…

【k8s】deamonset文件和说明

目录 deamonset的相关命令 deamonset的定义 deamonset的使用场景 deamonset的例子 deamonset字段说明 serviceAccountName DaemonSet的结构及其各个部分的作用 deamonset的相关命令 #查看<name-space>空间内有哪些deamonset kubectl get DaemonSet -n <na…

Django 学习教程- Django 入门案例

Django学习教程系列 Django学习教程-介绍与安装 前言 本教程是为 Django 5.0 编写的&#xff0c;它支持 Python 3.10 至以上。如果 Django 版本不匹配&#xff0c;可以参考教程 使用右下角的版本切换器来获取你的 Django 版本 &#xff0c;或将 Django 更新到最新版本。如果…

Winclone Pro 10 for Mac:轻松备份和还原你的Windows系统

Winclone Pro 10 for Mac是一款专为Mac用户设计的备份和还原软件&#xff0c;旨在帮助用户轻松管理和保护他们的Windows系统。无论是为了数据安全还是系统的稳定性&#xff0c;Winclone Pro 10都能提供全面的解决方案。 这款软件具备强大的备份功能&#xff0c;能够快速而准确…

Java流程控制语句(if语句,switch语句,for循环,while循环,do...while循环,三种循环的区别)

文章目录 第一章 流程控制语句1.1 流程控制语句分类1.2 顺序结构 第二章 判断语句&#xff1a;if语句2.1 if语句格式1练习1&#xff1a;老丈人选女婿练习2&#xff1a;考试奖励第一种格式的细节&#xff1a; 2.2 if语句格式2练习1&#xff1a;吃饭练习2&#xff1a;影院选座 2.…

AI产品经理 - 如何做一款软硬协同AI产品

【背景】从0做一款软硬协同的AI产品&#xff0c;以智能医药保温箱 1.以智能医药保温箱 2.调研定义市场方向 地点&#xff1a;医药、实验室 场景&#xff1a;长宽高/装箱/运输/实验室 3.需求挖掘 4.如何进行软硬件AI产品工作 软硬件产品设计&#xff1a;功能/硬件外观设计、…

SetWindowsHookEx: 全局钩子实现键盘记录器

简介 SetWindowsHookEx 钩子(Hook)&#xff0c;是Windows消息处理机制的一个平台&#xff0c;应用程序可以在上面设置子程以监视指定窗口的某种消息&#xff0c;而且所监视的窗口可以是其他进程所创建的。当消息到达后&#xff0c;在目标窗口处理函数之前处理它。钩子机制允许应…

Hive生产调优介绍

1.Fetch抓取 Fetch抓取是指&#xff0c;Hive中对某些情况的查询可以不必使用MapReduce计算。例如&#xff1a;SELECT * FROM employees;在这种情况下&#xff0c;Hive可以简单地读取employee对应的存储目录下的文件&#xff0c;然后输出查询结果到控制台。 在hive-default.xml…

云卷云舒:构建业务型电信智能运维方法

1 引言 智能运维&#xff08;AIOps-Algorithmic IT Operations基于算法的IT运维&#xff09;是人工智能技术在IT运维领域的运用&#xff0c;引用Gartner 的报告的一段话“未来几年&#xff0c;将近50%的企业将会在他们的业务和IT运维方面采用AIOps&#xff0c;远远高于今天的10…

php的laravel权限问题

1.这是我新建的一个路由&#xff0c;然后就是说每新建一个路由都要给他开个权限&#xff01;&#xff01;&#xff01;&#xff01; 2.这个是组内大佬写的&#xff1a; 我们也可以在里面加&#xff0c;也可以在浏览器的页面手动加&#xff08;对我们新手来说还是浏览器的页面…

matlab导出高清图片,须经修改后放入latex(例如添加文字说明,matlab画图不易操作)

一、背景 我们在写文章时&#xff0c;使用matlab画图后&#xff0c;如果不需要对图片进行额外修改或调整&#xff0c;例如添加文字说明&#xff0c;即可直接从matlab导出eps格式图片&#xff0c;然后插入到latex使用。 通常latex添加图片&#xff0c;是需要eps格式的。 但很…

微服务实战系列之Dubbo(下)

前言 眼看着2023即将走远&#xff0c;心里想着似乎还有啥&#xff0c;需要再跟各位盆友叨叨。这不说曹操&#xff0c;曹操就来了。趁着上一篇Dubbo博文的余温尚在&#xff0c;博主兴匆匆地“赶制”了Dubbo的下集&#xff0c;以飨读者。 上一篇博主依然从Dubbo的内核出发&#…

原型链补充

1.什么是原型对象 函数的独有属性,他用prototype来表示,可以在函数的prototype上挂载一些公用的属性和方法,供实例化对象来访问。 2.__proto__属性 这个属性每一个对象都有,实例化对象就是通过这个属性,来访问原型对象上的属性和方法的。 3.三者之间的关系 1.在构造函数的原型…

PTA——计算火车运行时间

本题要求根据火车的出发时间和达到时间&#xff0c;编写程序计算整个旅途所用的时间。 输入格式&#xff1a; 输入在一行中给出2个4位正整数&#xff0c;其间以空格分隔&#xff0c;分别表示火车的出发时间和到达时间。每个时间的格式为2位小时数&#xff08;00-23&#xff0…

鸿蒙 DevEco Studio 3.1 入门指南

本文主要记录开发者入门&#xff0c;从软件安装到项目运行&#xff0c;以及后续的学习 1&#xff0c;配置开发环境 1.1 下载安装包 官网下载链接 点击立即下载找到对应版版本 下载完成&#xff0c;按照提示默认安装即可 1.2 下载SDK及工具链 运行已安装的DevEco Studio&…

SPI机制原理+使用

一、概述 SPI全称&#xff08;Service Provider Interface&#xff09;&#xff0c;是JDK内置的一种服务提供发现机制&#xff1b;SPI机制提供了组件发现和注册方式&#xff0c;可以为应用程序提供灵活的插件机制&#xff0c; 主要原理&#xff1a;接口 反射 配置文件。 二、…

Rust学习笔记002: 猜字游戏

version0.1 // 导入标准库中的 io 模块&#xff0c;它包含了输入输出相关的功能 use std::io;// 程序的入口点 fn main() {println!("Guess the number!");println!("Please input your guess.");// 创建一个可变的字符串变量 guess&#xff0c;用于存储用…