SnowFlake 雪花算法详解与实现

article/2025/9/19 21:19:06

我是陈皮,一个在互联网 Coding 的 ITer,个人微信公众号「陈皮的JavaLib」关注第一时间阅读最新文章。

文章目录

    • 背景
    • SnowFlake 雪花算法
    • 算法实现
    • 算法验证
    • 算法优缺点
    • 注意事项

背景

现在的服务基本是分布式,微服务形式的,而且大数据量也导致分库分表的产生,对于水平分表就需要保证表中 id 的全局唯一性。

对于 MySQL 而言,一个表中的主键 id 一般使用自增的方式,但是如果进行水平分表之后,多个表中会生成重复的 id 值。那么如何保证水平分表后的多张表中的 id 是全局唯一性的呢?

如果还是借助数据库主键自增的形式,那么可以让不同表初始化一个不同的初始值,然后按指定的步长进行自增。例如有3张拆分表,初始主键值为1,2,3,自增步长为3。

当然也有人使用 UUID 来作为主键,但是 UUID 生成的是一个无序的字符串,对于 MySQL 推荐使用增长的数值类型值作为主键来说不适合。

也可以使用 Redis 的自增原子性来生成唯一 id,但是这种方式业内比较少用。

当然还有其他解决方案,不同互联网公司也有自己内部的实现方案。雪花算法是其中一个用于解决分布式 id 的高效方案,也是许多互联网公司在推荐使用的。

SnowFlake 雪花算法

SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。在2014年开源 scala 语言版本。

img

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

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

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

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

算法实现

package com.chenpi;import java.util.Set;
import java.util.TreeSet;/*** @author 陈皮* @version 1.0* @description 雪花算法ID生成器* @date 2022/4/3*/
public class SnowflakeIdGenerator {// 初始时间戳(纪年),可用雪花算法服务上线时间戳的值// 1649059688068:2022-04-04 16:08:08private static final long INIT_EPOCH = 1649059688068L;// 记录最后使用的毫秒时间戳,主要用于判断是否同一毫秒,以及用于服务器时钟回拨判断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 SnowflakeIdGenerator(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,注意这里使用synchronized同步** @return 唯一id*/public synchronized long nextId() {long currentTimeMillis = System.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 = tilNextMillis(lastTimeMillis);}} else { // 不在同一毫秒内,则序列号重新从0开始,序列号最大值为4095sequence = 0;}// 记录最后一次使用的毫秒时间戳lastTimeMillis = currentTimeMillis;// 核心算法,将不同部分的数值移动到指定的位置,然后进行或运行return ((currentTimeMillis - INIT_EPOCH) << TIMESTAMP_SHIFT) | (datacenterId<< DATA_CENTER_ID_SHIFT) | (workerId << WORK_ID_SHIFT) | sequence;}/*** 获取指定时间戳的接下来的时间戳,也可以说是下一毫秒** @param lastTimeMillis 指定毫秒时间戳* @return 时间戳*/private long tilNextMillis(long lastTimeMillis) {long currentTimeMillis = System.currentTimeMillis();while (currentTimeMillis <= lastTimeMillis) {currentTimeMillis = System.currentTimeMillis();}return currentTimeMillis;}public static void main(String[] args) {SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator(1, 2);// 生成50个idSet<Long> set = new TreeSet<>();for (int i = 0; i < 50; i++) {set.add(snowflakeIdGenerator.nextId());}System.out.println(set.size());System.out.println(set);// 验证生成100万个id需要多久long startTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {snowflakeIdGenerator.nextId();}System.out.println(System.currentTimeMillis() - startTime);}
}

算法验证

public static void main(String[] args) {SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator(1, 2);// 生成50个idSet<Long> set = new TreeSet<>();for (int i = 0; i < 50; i++) {set.add(snowflakeIdGenerator.nextId());}System.out.println(set.size());System.out.println(set);// 验证生成100万个id需要多久long startTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {snowflakeIdGenerator.nextId();}System.out.println(System.currentTimeMillis() - startTime);}

在我的笔记本,测试结果如下,生成的50个id是不重复的,而且 id 值是递增的。然后再测试生成100万个 id,只花费了262毫秒,可见是算法是及其高效的。

50
[268133605376, 268133605377, 268133605378, 268133605379, 268133605380, 268133605381, 268133605382, 268133605383, 268133605384, 268133605385, 268133605386, 268133605387, 268133605388, 268133605389, 268133605390, 268133605391, 268133605392, 268133605393, 268133605394, 268133605395, 268133605396, 268133605397, 268133605398, 268133605399, 268133605400, 268133605401, 268133605402, 268133605403, 268133605404, 268133605405, 268133605406, 268133605407, 268133605408, 268133605409, 268133605410, 268133605411, 268133605412, 268133605413, 268133605414, 268133605415, 268133605416, 268133605417, 268133605418, 268133605419, 268133605420, 268133605421, 268133605422, 268133605423, 268133605424, 268133605425]
262

算法优缺点

雪花算法有以下几个优点:

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

雪花算法有如下缺点:

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

注意事项

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

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

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


本次分享到此结束啦~~

如果觉得文章对你有帮助,点赞、收藏、关注、评论,您的支持就是我创作最大的动力!


http://chatgpt.dhexx.cn/article/bY6U0ZFU.shtml

相关文章

雪花算法(SnowFlake)

简介 现在的服务基本是分布式、微服务形式的&#xff0c;而且大数据量也导致分库分表的产生&#xff0c;对于水平分表就需要保证表中 id 的全局唯一性。 对于 MySQL 而言&#xff0c;一个表中的主键 id 一般使用自增的方式&#xff0c;但是如果进行水平分表之后&#xff0c;多…

二维反卷积 matlab,二维反卷积的实现(实际意义不明确)

前言 一维反卷积(deconv),可以很好的实现一维卷积的反过程!但是二维反卷积就很难恢复了!为什么呢?因为我们知道二维卷积计算的过程就是:卷积核不断滑动,卷积核不断与原始数据中的小矩阵做"点乘并求和";现假设卷积核为3x3,那么每一个和它点乘的小矩阵对应尺寸…

python 反卷积(DeConv) tensorflow反卷积(DeConv)(实现原理+手写)

Tensorflow反卷积&#xff08;DeConv&#xff09;实现原理手写python代码实现反卷积&#xff08;DeConv&#xff09; 理解&#xff1a; https://www.zhihu.com/question/43609045/answer/130868981 上一篇文章已经介绍过卷积的实现&#xff0c;这篇文章我们学习反卷积原理&am…

地震信号系列完结篇-反卷积方法

前言 本篇将详细地讲解地震信号中用到的反卷积方法。反卷积方法的作用在文章 地震信号的一些基本概念 中已经阐述过&#xff0c;简单的说就是&#xff1a;在压缩原信号的同时&#xff0c;对频谱进行补偿&#xff08;反卷积的输出信号&#xff09;。而在地震信号处理中&#xf…

反卷积的棋盘格效应

本文译自来自谷歌大脑的AUGUSTUS ODENA等人的文章: Deconvolution and Checkerboard Artifacts[1], 虽然是16年的博客了, 但是其对解释反卷积的棋盘效应已经如何规避都给出了非常好和到位的意见. 下面让我们开始~ 前言 当我们分析由神经网络生成的图片的时候, 常常会发觉有一种…

反卷积神经网络介绍

反卷积是指&#xff1a;通过测量输出和已经输入重构未知输入的过程。在神经网络中&#xff0c;反卷积过程并不具备学习的能力&#xff0c;仅仅是用于可视化一个已经训练好的卷积网络模型&#xff0c;没有学习训练的过程。 下图所示为VGG 16反卷积神经网络的结构&#xff0c;展示…

一文读懂什么是反卷积

反卷积&#xff08;Deconvolution&#xff09;的概念第一次出现是Zeiler在2010年发表的论文Deconvolutional networks中&#xff0c;但是并没有指定反卷积这个名字&#xff0c;反卷积这个术语正式的使用是在其之后的工作中(Adaptive deconvolutional networks for mid and high…

反卷积相关论文理解

关于反卷积原理&#xff0c;小编就不再赘述&#xff0c;在知乎中有详细的解释&#xff0c;很清晰&#xff0c;都是大佬。 链接如下&#xff1a;https://www.zhihu.com/question/43609045/answer/120266511 反卷积相对于卷积在神经网络结构的正向和反向传播中做相反的运算&…

反卷积理解和推导

参考 怎样通俗易懂地解释反卷积&#xff1f; - 知乎&#xff0c;【基础知识学习】卷积与反卷积学习笔记 - 知乎 1.概念 反卷积是一种特殊的正向卷积&#xff0c;先按照一定的比例通过补 0 来扩大输入图像的尺寸&#xff0c;接着旋转卷积核&#xff0c;再进行正向卷积。 图1 反…

生物信息学反卷积论文阅读

文章目录 反卷积的概念反卷积的具体方式反卷积预测RNA序列知识背景公式推导 亚硫酸氢盐测序知识背景公式推导 R包的使用RNA测序数据分析使用亚硫酸氢盐数据进行测序 反卷积的概念 由于许多组织样本不适合分解成单个细胞&#xff0c;因此不能利用单细胞RNA测序技术对它们的单个…

理解反卷积

先看看卷积&#xff0c;数字只是说明位置方便&#xff0c;不是具体数值&#xff0c;这里是valid卷积 &#xff0c;stride1 由CNN基础我们知道 17 这个点是由前面1 2 5 6 和卷积核运算得到的&#xff0c;那么反卷积就是要从17 反推1,2,5,6 &#xff0c;这是一个无穷解问题&#…

反卷积常用方法

反卷积 一个用于分类任务的深度神经网络通过卷积来不断抽象学习&#xff0c;实现分辨率的降低&#xff0c;最后得到一个较小的FeatureMap&#xff0c;即特征图&#xff0c;通常大小为 5 5 5\times5 55或者 7 7 7\times7 77。而图像分割任务需要恢复与原尺寸大小一样的图片&am…

声音反卷积matlab,用MATLAB做反卷积

关键词&#xff1a;反卷积 MATLAB fft 频移 分母中频谱零点 卷积核 % 代码如下&#xff1a; clear all;clc; h [1 1 1 1] % 要求 f [1 -2 3 -2] % 已知 g conv(h,f) % 已知 g h*f 这里卷积结果g知道&#xff0c;f知道&#xff0c;f视作卷积核&#xff0c;反卷积求h …

彻底搞懂CNN中的卷积和反卷积

前言 卷积和反卷积在CNN中经常被用到&#xff0c;想要彻底搞懂并不是那么容易。本文主要分三个部分来讲解卷积和反卷积&#xff0c;分别包括概念、工作过程、代码示例&#xff0c;其中代码实践部分主结合TensorFlow框架来进行实践。给大家介绍一个卷积过程的可视化工具&#x…

卷积与反卷积

1、卷积 上图展示了一个卷积的过程&#xff0c;其中蓝色的图片(4*4)表示的是进行卷积的图片&#xff0c;阴影的图片(3*3)表示的是卷积核&#xff0c;绿色的图片(2*2)表示是进行卷积计算之后的图片。在卷积操作中有几个比较重要的参数&#xff0c;输入图片的尺寸、步长、卷积核的…

反卷积原理

一 介绍 反卷积&#xff0c;可以理解为卷积操作的逆运算。这里千万不要当成反卷积操作可以复原卷积操作的输入值&#xff0c;反卷积并没有那个功能&#xff0c;它仅仅是将卷积变换过程中的步骤反向变换一次而已&#xff0c;通过将卷积核转置&#xff0c;与卷积后的结果再做一遍…

Tensorflow——反卷积

目标——主要用来进行上采样&#xff0c;使图像形状变大 函数&#xff1a;conv2d_transpose(value, filter, output_shape, strides, padding"SAME", data_format"NHWC", nameNone) Arg&#xff1a; value&#xff1a;指需要做反卷积的输入图像&#xff…

机器学习19:反卷积算法

机器学习19:反卷积算法&#xff08;转载和整理&#xff09; 在整理全卷积网络的过程中&#xff0c;被反卷积的概念困扰很久&#xff0c;于是将反卷积算法单独整理为一篇博客&#xff0c;本文主要转载和整理自知乎问题如何通俗易懂地解释反卷积&#xff1f;中的高票答案。 1.反…

卷积和反卷积详解

反卷积&#xff08;deconvolution&#xff09;&#xff0c;也叫转置卷积&#xff0c;分部卷积&#xff08;fractionally-strided convolution&#xff09;&#xff0c;在论文中也叫upconv。 1. Pytorch中2D卷积和反卷积的函数 class torch.nn.Conv2d(in_channels, out_channe…

什么是反卷积(快速理解)

什么是反卷积 参考博客 我们知道输入图像通过卷积神经网络&#xff08;CNN&#xff09;提取特征后&#xff0c;输出的尺寸往往会变小&#xff0c;而又是我们需要将图像恢复到原来的尺寸以便进行进一步的计算&#xff0c;整个扩大图像尺寸&#xff0c;实现图像由小分辨率到大分辨…