当前位置: 首页 > news >正文

【Spring Cloud系列】 雪花算法原理及实现

【Spring Cloud系列】 雪花算法原理及实现

文章目录

  • 【Spring Cloud系列】 雪花算法原理及实现
    • 一、概述
    • 二、生成ID规则部分硬性要求
    • 三、ID号生成系统可用性要求
    • 四、解决分布式ID通用方案
      • 4.1 UUID
      • 4.2 数据库自增主键
      • 4.3 基于Redis生成全局id策略
    • 五、SnowFlake(雪花算法)
      • 5.1 SnowFlake特点
      • 5.2 SnowFlake结构
      • 5.3 雪花算法原理
      • 5.4 算法实现
      • 5.4 雪花算法优点
      • 5.5 雪花算法缺点:
    • 六、总结

一、概述

分布式高并发的环境下,常见的就是12306节日订票,在大量用户同是抢购一个方向的票,毫秒级的时间下可能生成数万个订单,此时为确保生成订单ID的唯一性变得至关重要。此时秒杀环境下,不仅要保障ID唯一性,还得确保ID生成的优先度。

二、生成ID规则部分硬性要求

  1. 全局唯一:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
  2. 趋势递增:在MySQL的InnoDB引擎中适用的是聚集索引,由于多数RDBMS使用B+Tree的数据结构来存储索引数据,在主键的选择上我们尽量使用有序的主键保证写入性能。
  3. 单调递增:保证下一个ID一定大于上一个ID,如事务版本号、排序等特殊需求。
  4. 信息安全:如果ID是连续的,恶意用户的抓取工作就非常容易,直接按照顺序下载指定URL即可;如果是订单号就危险。
  5. 含有时间戳:生成的ID包含完整的时间戳信息。

三、ID号生成系统可用性要求

  1. 高可用:发一个获取分布式ID的请求,服务器就是保证99.9999%的情况下给我创建一个唯一分布式ID。
  2. 低延迟:发一个获取分布式ID的请求,服务器要快,极速。
  3. 高QPS:如果一次请求10万个分布式ID,服务器要顶住并成功创建10万个分布式ID。

四、解决分布式ID通用方案

4.1 UUID

UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为:8-4-4-4-12的36个字符,示例:1E785B2B-111C-752A-997B-3346E7495CE2;UUID性能非常高,不依赖网络,本地生成。

UUID缺点:

  1. 无序,无法预测它的生成顺序,不能生成递增有序的数字。在MySql官方推荐主键约短越好,UUID是一个32位的字符串,所以不推荐使用。

  2. 索引,B+Tree索引的分裂

    分布式Id是主键,主键是聚簇索引。Mysql的索引是B+Tree来实现的,每次新的UUID数据的插入,为了新的UUID数据的插入,为了查询的优化,都会对索引底部的B+Tree进行修改;因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键的聚簇索引做很大的修改,在做数据Insert时,会插入主键是无序的,会导致一些中间节点的产生分裂,会导致大量不饱和的节点。这样大大降低了数据库插入的性能。

4.2 数据库自增主键

单机

在分布式里面,数据库的自增ID机制的主要原理是:数据库自增ID和MySql数据库的replace into实现的。

Replace into的含义是插入一条纪录,如果表中唯一索引的值遇到冲突,则替换老数据。

在单体应用的时候,自增长ID使用,但是在集群分布式应用中单体应用就不适合。

  1. 系统水平扩展比较困难,比如定义好了增长步长和机器台数之后,在大量添加服务器时,需要重新设置初始值,这样可操作性差,所以系统水平扩展方案复杂度高难以实现。
  2. 数据库压力大,每次获取ID都需要读写一次数据库,非常影响性能,不符合分布式ID里面的延迟低和要高QPS的规则(在高并发下,如果都去数据库里面获取Id,非常影响性能的。)

4.3 基于Redis生成全局id策略

在Redis集群情况下,同样和MySql一样需要设置不同的增长步长,同时key一定要设置有效期。可以使用Redis集群来获取更高的吞吐量。

五、SnowFlake(雪花算法)

而Twitter的SnowFlake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra(由Facebook开发一套开源分布式NoSQL数据库系统) 因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。SnowFlake每秒能产生26万个自增可排序的ID。

5.1 SnowFlake特点

  1. Twitter的SnowFlake生成ID能够按照时间有序生成。
  2. SnowFlake算法生成Id的结果是一个64bit大小的整数,为一个Long型(转换成字符串后长度最多19)。
  3. 分布式系统内不会产生ID碰撞(由datacenter和workerid作为区分)并且效率较高。

5.2 SnowFlake结构

在这里插入图片描述

5.3 雪花算法原理

雪花算法的原理就是生成一个的64位比特位的long类型的唯一id

  1. 最高1位固定值0,因为生成的id是正整数,如果是1就是负值。
  2. 紧接着是41位存储毫秒级时间戳,2^41/(1000 * 60 * 24 * 365) = 69 ,大概可以使用69年。
  3. 接下来10位存储机器码,包括5位DataCenterId和5位WorkerId,最多可以部署2^10=1024台机器。
  4. 最后12位存储序列号,同一毫秒时间戳时,通过这个递增的序列号来区分,即对于同一台机器而言,同一毫秒级时间戳下,可以生成2^12=4096个不重复id。

可以将雪花算法作为一个单独的服务进行部署,然后需要全局唯一id的系统,请求雪花算法服务获取id即可。

对于每一个雪花算法服务,需要先指定10位的机器码,这个根据自身业务进行设定即可。例如机房号+机器号,机器号+服务号,或者时其他区别标识的10位比特位的整数都行。

5.4 算法实现

package com.goyeer;
import java.util.Date;/*** @ClassName: SnowFlakeUtil* @Author: goyeer* @Date: 2023/09/09 19:34* @Description:*/
public class SnowFlakeUtil {private static SnowFlakeUtil snowFlakeUtil;static {snowFlakeUtil = new SnowFlakeUtil();}// 初始时间戳(纪年),可用雪花算法服务上线时间戳的值//private static final long INIT_EPOCH = 1694263918335L;// 时间位取&private static final long TIME_BIT = 0b1111111111111111111111111111111111111111110000000000000000000000L;// 记录最后使用的毫秒时间戳,主要用于判断是否同一毫秒,以及用于服务器时钟回拨判断private long lastTimeMillis = -1L;// dataCenterId占用的位数private static final long DATA_CENTER_ID_BITS = 5L;// dataCenterId占用5个比特位,最大值31// 0000000000000000000000000000000000000000000000000000000000011111private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);// dataCenterIdprivate long dataCenterId;// workId占用的位数private static final long WORKER_ID_BITS = 5L;// workId占用5个比特位,最大值31// 0000000000000000000000000000000000000000000000000000000000011111private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);// workIdprivate long workerId;// 最后12位,代表每毫秒内可产生最大序列号,即 2^12 - 1 = 4095private static final long SEQUENCE_BITS = 12L;// 掩码(最低12位为1,高位都为0),主要用于与自增后的序列号进行位与,如果值为0,则代表自增后的序列号超过了4095// 0000000000000000000000000000000000000000000000000000111111111111private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 同一毫秒内的最新序号,最大值可为 2^12 - 1 = 4095private long sequence;// workId位需要左移的位数 12private static final long WORK_ID_SHIFT = SEQUENCE_BITS;// dataCenterId位需要左移的位数 12+5private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;// 时间戳需要左移的位数 12+5+5private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;/*** 无参构造*/public SnowFlakeUtil() {this(1, 1);}/*** 有参构造* @param dataCenterId* @param workerId*/public SnowFlakeUtil(long dataCenterId, long workerId) {// 检查dataCenterId的合法值if (dataCenterId < 0 || dataCenterId > MAX_DATA_CENTER_ID) {throw new IllegalArgumentException(String.format("dataCenterId 值必须大于 0 并且小于 %d", MAX_DATA_CENTER_ID));}// 检查workId的合法值if (workerId < 0 || workerId > MAX_WORKER_ID) {throw new IllegalArgumentException(String.format("workId 值必须大于 0 并且小于 %d", MAX_WORKER_ID));}this.workerId = workerId;this.dataCenterId = dataCenterId;}/*** 获取唯一ID* @return*/public static Long getSnowFlakeId() {return snowFlakeUtil.nextId();}/*** 通过雪花算法生成下一个id,注意这里使用synchronized同步* @return 唯一id*/public synchronized long nextId() {long currentTimeMillis = System.currentTimeMillis();System.out.println(currentTimeMillis);// 当前时间小于上一次生成id使用的时间,可能出现服务器时钟回拨问题if (currentTimeMillis < lastTimeMillis) {throw new RuntimeException(String.format("可能出现服务器时钟回拨问题,请检查服务器时间。当前服务器时间戳:%d,上一次使用时间戳:%d", currentTimeMillis,lastTimeMillis));}if (currentTimeMillis == lastTimeMillis) {// 还是在同一毫秒内,则将序列号递增1,序列号最大值为4095// 序列号的最大值是4095,使用掩码(最低12位为1,高位都为0)进行位与运行后如果值为0,则自增后的序列号超过了4095// 那么就使用新的时间戳sequence = (sequence + 1) & SEQUENCE_MASK;if (sequence == 0) {currentTimeMillis = getNextMillis(lastTimeMillis);}} else { // 不在同一毫秒内,则序列号重新从0开始,序列号最大值为4095sequence = 0;}// 记录最后一次使用的毫秒时间戳lastTimeMillis = currentTimeMillis;// 核心算法,将不同部分的数值移动到指定的位置,然后进行或运行// <<:左移运算符, 1 << 2 即将二进制的 1 扩大 2^2 倍// |:位或运算符, 是把某两个数中, 只要其中一个的某一位为1, 则结果的该位就为1// 优先级:<< > |return// 时间戳部分((currentTimeMillis - INIT_EPOCH) << TIMESTAMP_SHIFT)// 数据中心部分| (dataCenterId << DATA_CENTER_ID_SHIFT)// 机器表示部分| (workerId << WORK_ID_SHIFT)// 序列号部分| sequence;}/*** 获取指定时间戳的接下来的时间戳,也可以说是下一毫秒* @param lastTimeMillis 指定毫秒时间戳* @return 时间戳*/private long getNextMillis(long lastTimeMillis) {long currentTimeMillis = System.currentTimeMillis();while (currentTimeMillis <= lastTimeMillis) {currentTimeMillis = System.currentTimeMillis();}return currentTimeMillis;}/*** 获取随机字符串,length=13* @return*/public static String getRandomStr() {return Long.toString(getSnowFlakeId());}/*** 从ID中获取时间* @param id 由此类生成的ID* @return*/public static Date getTimeBySnowFlakeId(long id) {return new Date(((TIME_BIT & id) >> 22) + INIT_EPOCH);}public static void main(String[] args) {SnowFlakeUtil snowFlakeUtil = new SnowFlakeUtil();long id = snowFlakeUtil.nextId();System.out.println(id);Date date = SnowFlakeUtil.getTimeBySnowFlakeId(id);System.out.println(date);long time = date.getTime();System.out.println(time);System.out.println(getRandomStr());}}

5.4 雪花算法优点

  1. 高并发分布式环境下生成不重复 id,每秒可生成百万个不重复 id。
  2. 基于时间戳,以及同一时间戳下序列号自增,基本保证 id 有序递增。
  3. 不依赖第三方库或者中间件。
  4. 算法简单,在内存中进行,效率高。

5.5 雪花算法缺点:

  1. 依赖服务器时间,服务器时钟回拨时可能会生成重复 id。算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。

六、总结

其实雪花算法每一部分占用的比特位数量并不是固定死的。例如你的业务可能达不到 69 年之久,那么可用减少时间戳占用的位数,雪花算法服务需要部署的节点超过1024 台,那么可将减少的位数补充给机器码用。

注意,雪花算法中 41 位比特位不是直接用来存储当前服务器毫秒时间戳的,而是需要当前服务器时间戳减去某一个初始时间戳值,一般可以使用服务上线时间作为初始时间戳值。

对于机器码,可根据自身情况做调整,例如机房号,服务器号,业务号,机器 IP 等都是可使用的。对于部署的不同雪花算法服务中,最后计算出来的机器码能区分开来即可。

相关文章:

【Spring Cloud系列】 雪花算法原理及实现

【Spring Cloud系列】 雪花算法原理及实现 文章目录 【Spring Cloud系列】 雪花算法原理及实现一、概述二、生成ID规则部分硬性要求三、ID号生成系统可用性要求四、解决分布式ID通用方案4.1 UUID4.2 数据库自增主键4.3 基于Redis生成全局id策略 五、SnowFlake&#xff08;雪花算…...

Postgresql 阿里云部署排雷

启动服务bug&#xff1a; 根据你的输出&#xff0c;可以看到 PostgreSQL 服务启动失败&#xff0c;并且显示以下错误消息&#xff1a; pg_ctl: cannot be run as root Please log in (using, e.g., "su") as the (unprivileged) user that will own the server proc…...

l8-d10 TCP协议是如何实现可靠传输的

一、TCP主要特点 TCP 是面向连接的运输层协议&#xff0c;在无连接的、不可靠的 IP 网络服务基础之上提供可靠交付的服务。为此&#xff0c;在 IP 的数据报服务基础之上&#xff0c;增加了保证可靠性的一系列措施。 TCP主要特点 1.TCP 是面向连接的运输层协议。 每一条 TCP 连…...

9月9日扒面经

堆和栈的区别&#xff1f; 分配方式&#xff1a;堆内存是由程序员手动分配和释放的&#xff0c;而栈内存是由编译器自动分配和释放的。内存管理&#xff1a;堆内存需要手动管理内存的分配和释放&#xff0c;程序员需要显式地调用malloc()或new来分配内存&#xff0c;并使用fre…...

项目实战:ES的增加数据和查询数据

文章目录 背景在ES中增加数据新建索引删除索引 在ES中查询数据查询数据总数量 项目具体使用&#xff08;实战&#xff09;引入依赖方式一&#xff1a;使用配置类连接对应的es服务器创建配置类编写业务逻辑----根据关键字查询相关的聊天内容在ES中插入数据 总结提升 背景 最近需…...

vs code调试rust乱码问题解决方案

在terminal中 用chcp 65001 修改一下字符集&#xff0c;就行了。有的博主推荐 修改 区域中的设置&#xff0c;这会引来很大的问题。千万不要修改如下设置&#xff1a;...

大数据课程K22——Spark的SparkSQL的API调用

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Spark的通过api使用SparkSQL; 一、通过api使用SparkSQL 1. 实现步骤 1. 打开scala IDE开发环境,创建一个scala工程。 2. 导入spark相关依赖jar包。 3. 创建包路径以object类。 4.…...

数据结构学习系列之顺序表的两种删除方式

方式1&#xff1a;在顺序表的末端删除所存储的数据元素&#xff0c;代码如下&#xff1a;示例代码&#xff1a; int delete_seq_list_1(list_t *seq_list){if(NULL seq_list){printf("入参为NULL\n");return -1;}if(0 seq_list->count){printf("顺序表为空…...

机器学习笔记之最优化理论与方法(七)无约束优化问题——常用求解方法(上)

机器学习笔记之最优化理论与方法——基于无约束优化问题的常用求解方法[上] 引言总体介绍回顾&#xff1a;线搜索下降算法收敛速度的衡量方式线性收敛范围高阶收敛范围 二次终止性朴素算法&#xff1a;坐标轴交替下降法最速下降法(梯度下降法)梯度下降法的特点 针对最速下降法缺…...

ES-索引管理

前言 数据类型 ​ 搜索引擎是对数据的检索&#xff0c;所以我们先从生活中的数据说起。我们生活中的数据总体分为两种&#xff1a; 结构化数据非结构化数据 结构化数据&#xff1a; 也称作行数据&#xff0c;是由二维表结构来逻辑表达和实现的数据&#xff0c;严格地遵循数…...

linux中常用shell脚本整理

linux常见shell脚本整理 备份日志 #!/bin/bash # 每日创建新的备份日志-根据日期备份 tar -czf log-date %Y%m%d.tar.gz /var/log # 通过crontab 每日定时启动 00 03 * * 5 /root/logbak.sh 监控内存和磁盘容量&#xff0c;小于给定值时报警 #!/bin/bash # 实…...

介绍PHP

PHP是一种流行的服务器端编程语言&#xff0c;用于开发Web应用程序。它是一种开源的编程语言&#xff0c;具有易学易用的语法和强大的功能。PHP支持在服务器上运行的动态网页和Web应用程序的快速开发。 PHP可以与HTML标记语言结合使用&#xff0c;从而能够生成动态的Web页面&a…...

selenium+find_elements用法

1、假如我们遇到多个标签的class一样&#xff0c;比如像下面这样的 我们可以采用js语法去定位&#xff0c;比如&#xff1a; document.getElementsByClassName("ant-calendar-picker-input ant-input")[0]...

1DM+下载器_11.2.1魔改增强版下载

1DM「原&#xff1a;IDM」下载器是一款安卓端的下载工具&#xff0c;多语言解锁版直安装版本&#xff0c;号称是目前 Android 平台最快、最先进的下载管理器应用「支持通过Torrent下载」&#xff0c;而这个版本是改线程的最新idm版本&#xff0c;可用来下载视频、音乐、电影、T…...

vue3:3、项目目录和关键文件

关于vsvode的更改 <!-- 加上setup允许在script中直接编写组合式api --> <script setup> // 组件引入后直接用 import HelloWorld from ./components/HelloWorld.vue import TheWelcome from ./components/TheWelcome.vue</script><!-- 1、js放在最上面&am…...

ChatGPT实战与私有化大模型落地

文章目录 大模型现状baseline底座选择数据构造迁移方法评价思考 领域大模型训练技巧Tokenizer分布式深度学习数据并行管道并行向量并行分布式框架——Megatron-LM分布式深度学习框架——Colossal-AI分布式深度学习框架——DeepSpeedP-tuning 微调 资源消耗模型推理加速模型推理…...

10分钟从实现和使用场景聊聊并发包下的阻塞队列

上篇文章12分钟从Executor自顶向下彻底搞懂线程池中我们聊到线程池&#xff0c;而线程池中包含阻塞队列 这篇文章我们主要聊聊并发包下的阻塞队列 阻塞队列 什么是队列&#xff1f; 队列的实现可以是数组、也可以是链表&#xff0c;可以实现先进先出的顺序队列&#xff0c;…...

Python入门学习13(面向对象)

一、类的定义和使用 类的使用语法&#xff1a; 创建类对象的语法&#xff1a; ​​​​​​​ class Student:name None #学生的名字age None #学生的年龄def say_hi(self):print(f"Hi大家好&#xff0c;我是{self.name}")stu Student() stu.name &q…...

哈工大计算机网络课程网络安全基本原理之:身份认证

哈工大计算机网络课程网络安全基本原理之&#xff1a;身份认证 在日常生活中&#xff0c;在很多场景下我们都需要对当前身份做认证&#xff0c;比如使用密码、人脸识别、指纹识别等&#xff0c;这些都是身份认证的常用方式。本节介绍的身份认证&#xff0c;是在计算机网络安全…...

海外代购系统/代购网站怎么搭建

搭建海外代购系统/代购网站的详细步骤涉及到的内容非常多&#xff0c;本文将分为以下几个部分进行详细介绍&#xff1a;前端开发、后端管理系统的开发、数据库设计和代购流程的设计与实现。 一、前端开发 前端开发是整个代购网站的门面&#xff0c;它直接面向用户&#xff0c…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...