分布式ID生成系统之雪花算法详解

在当今的云计算和微服务架构盛行的时代,分布式系统已成为软件开发的重要组成部分。随着系统规模的扩大和业务的复杂化,对数据一致性和唯一性的要求也越来越高,尤其是在全局唯一标识符(ID)的生成上。因此,分布式ID生成系统应运而生,成为保证数据唯一性和提高系统可扩展性的关键技术之一。雪花算法(Snowflake)是Twitter开源的一种算法,用于生成64位的全局唯一ID,非常适用于分布式系统中生成唯一标识符。下面我们将深入探讨雪花算法的原理、结构和实现方式。

snowflake.jpg

分布式ID的需求背景

在单体应用中,生成唯一ID相对简单,如数据库的自增ID、UUID等方法可以轻易满足需求。然而,在分布式系统中,由于数据可能分布在不同的节点上,传统的ID生成方法面临着多方面的挑战:

  • 高可用性:系统任何一个部分的故障都不能影响ID的生成和使用。
  • 高并发:在大规模并发的场景下,ID生成系统必须能够快速响应。
  • 有序性:在某些业务场景中,需要ID具有一定的可排序性,以便于后续处理。
  • 低延迟:ID生成的延迟必须足够低,以满足实时性要求。

推特的雪花系统

Twitter开源的Snowflake算法是目前最流行的分布式ID生成方案之一。它通过结合时间戳、机器标识和序列号来生成64位的长整型ID,既保证了全局唯一性,又具有良好的有序性,非常适合高并发的场景。

如下展示了一个64为ID的构成:

snowflake.png

每个部分的含义如下:

  • 符号位(1位)

    始终为0,预留位,将来有可能用来区分有符号数和无符号数。

  • 时间戳(41位)

    它是纪元或者自定义纪元开始以来的毫秒数,Snowflake使用的是2010-11-04 01:42:54 的时间戳作为默认纪元的,我们也可以自定义。即时间戳为当前时间的毫秒数 - 自定义的纪元的毫秒数。

  • 数据中心id(5位)

    最多可以有 2 5 2^5 25个数据中心,取值范围为 0~31。

  • 机器id(5位)

    最多可以有 2 5 2^5 25台机器,取值范围为 0~31。

  • 序列号(12位)

    对于某个服务,每一个毫秒内生成一个id,序列号就加1,这个数字在每毫秒开始时都会被重置为0。即一个毫秒内单个服务可以生成 2 12 2^{12} 212即4096个id。

    我们可以根据服务的具体情况调整下id各部分的长度,比如,对于并发量低,单次生成id数量大的应用,我们可以适当减少数据中心id和机器id的位数,增加序列号位数来提高每个毫秒内id的生成数量。

Java实现雪花雪花系统

使用Java语言实现雪花算法的ID生成器,可以参考以下代码。这个实现同样遵循了雪花算法的基本结构,包括1位符号位、41位时间戳、10位机器标识(5位数据中心ID和5位工作机器ID)以及12位序列号。我们将这些位数放在了配置文件中,家人们可以根据实际情况进行调整。在这个代码中,我们提供了单id生成接口和批量id生成接口。代码如下:

  • 配置信息 application.yml
server:
  port: 8000

snowflake:
  #数据中心id位数
  datacenterBits: 5
  # 机器id位数
  workerBits: 5
  # 序列id所占位数
  sequenceBits: 12
  # 数据中心id,范围0-2^5-1
  datacenterId: 1
  # 机器id,范围0-2^5-1
  workerId: 1
  # 时间戳起始点(2024-01-01 00::00:00 的毫秒数)
  twepoch: 1704038400000
  #单次批量生成id的最大数量  默认10万
  maxBatchCount: 100000
  • SnowflakeProperties
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix ="snowflake")
@Data
public class SnowflakeProperties {
    //数据中心id
    private Long datacenterId;
    //数据中心id位数
    private Long datacenterBits;
    //机器id
    private Long workerId;
    //机器id位数
    private Long workerBits;
    //序列id所占位数
    private Long sequenceBits;
    // 时间戳起始点(毫秒)
    private  Long twepoch;
    //单次批量生成id的最大数量
    private Integer maxBatchCount;
}

  • SnowflakeIdGenerator
package cn.xj.snowflake.generator;


import cn.xj.snowflake.config.SnowflakeProperties;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class SnowflakeIdGenerator {


    //数据中心id
    private final long datacenterId;
    //数据中心id位数
    private final long datacenterBits;
    //机器id
    private final long workerId;
    //机器id位数
    private final long workerBits;
    //序列id所占位数
    private final  long sequenceBits;
    // 时间戳起始点(毫秒)
    private final long twepoch;

    //数据中心最大id
    private final long maxDatacenterId;
    //机器最大id
    private final long maxWorkerId;
    //最大序列号
    private final long maxSequence;

    //机器id左移位数
    private final long workerIdShift;
    //数据中心id左移位数
    private final long datacenterIdShift;
    //毫秒数左移位数
    private final long timestampLeftShift;

    //单次批量生成id的最大数量
    private final int maxBatchCount;

    // 序列号
    private long sequence = 0L;
    // 上一次时间戳
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(SnowflakeProperties properties) {
        //数据中心id
        this.datacenterId = properties.getDatacenterId();
        //数据中心id位数
        this.datacenterBits = properties.getDatacenterBits();
        //机器id
        this.workerId = properties.getWorkerId();
        //机器id位数
        this.workerBits = properties.getWorkerBits();
        //序列id所占位数
        this.sequenceBits = properties.getSequenceBits();
        // 时间戳起始点(毫秒)
        this.twepoch = properties.getTwepoch();
        //数据中心最大id
        this.maxDatacenterId = -1L ^ (-1L << properties.getDatacenterBits());
        //机器最大id
        this.maxWorkerId = -1L ^ (-1L << properties.getWorkerBits());
        //最大序列号
        this.maxSequence = -1L ^ (-1L << properties.getSequenceBits());

        this.workerIdShift = properties.getSequenceBits();
        //数据中心id左移位数
        this.datacenterIdShift = properties.getSequenceBits() + properties.getWorkerBits();
        //毫秒数左移位数
        this.timestampLeftShift = properties.getSequenceBits() + properties.getWorkerBits() + properties.getSequenceBits();
        //单次批量生成id的最大数量
        this.maxBatchCount = properties.getMaxBatchCount();


        // 校验datacenterId和workerId是否超出最大值
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("数据中心Id不能大于%d或小于0", maxDatacenterId));
        }
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("机器Id不能大于%d或小于0", maxWorkerId));
        }
    }



    /**
     * id生成方法(单个)
     * @return
     */
    public synchronized long nextId() {
        //获取当前时间的毫秒数
        long timestamp = currentTime();

        //判断时钟是否回拨
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("时钟回拨,回拨毫秒数:%d", lastTimestamp - timestamp));
        }

        //设置序列号
        if (lastTimestamp == timestamp) {
            //设置序列号递增,如果当前毫秒内序列号已经达到最大值,则直到下一毫秒在重新从0开始计算序列号
            sequence = (sequence + 1) & maxSequence;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        //计算id
        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }


    /**
     * id生成方法(批量)
     * @return
     */
    public synchronized List<Long> nextIds(int count) {
        if (count > maxBatchCount || count < 0) {
            throw new IllegalArgumentException(String.format("批量生成id的数量不能大于%d或小于0", maxBatchCount));
        }

        List<Long> ids = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            ids.add(nextId());
        }
        return ids;
    }

    /**
     * 循环等待直至获取到新的毫秒时间戳
     * 确保生成的时间戳总是向前移动的,即使在相同的毫秒内请求多个ID时也能保持唯一性。
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = currentTime();
        // 循环等待直至获取到新的毫秒时间戳
        while (timestamp <= lastTimestamp) {
            timestamp = currentTime();
        }
        return timestamp;
    }

    /**
     * 获取当前时间的毫秒数
     */
    private long currentTime() {
        return System.currentTimeMillis();
    }

}

这个Java类SnowflakeIdWorker封装了雪花算法的核心逻辑。它允许通过构造函数指定数据中心ID和机器ID,并提供了nextId()和nextIds()方法用于生成唯一的ID。该方法通过同步关键字synchronized保证了线程安全。

  • SnowflakeApi

import cn.xj.snowflake.generator.SnowflakeIdGenerator;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class SnowflakeApi {


    @Resource
    private SnowflakeIdGenerator snowflakeIdGenerator;

    @PostMapping("/snowflake/api/nextId")
    public Long nextId(){
        return snowflakeIdGenerator.nextId();
    }

    @PostMapping("/snowflake/api/nextIds")
    public List<Long> nextIds(@RequestBody int count){
        return snowflakeIdGenerator.nextIds(count);
    }
}

接口调用详情

单个id生成接口nextId:

_20240303211115.jpg

批量id生成接口nextIds:我们此处生成了10万条id,响应时长不到1s
_20240303211339.jpg

雪花算法的开源代码或者优秀代码示例有很多,但思想基本是一样的。这有篇美团的文章,大家也可以参考下美团的leaf [Leaf——美团点评分布式ID生成系统: https://tech.meituan.com/2017/04/21/mt-leaf.html](https://tech.meituan.com/2017/04/21/mt-leaf.html)

总结

雪花算法作为一种高效、简单的分布式系统ID生成方案,已经被广泛应用于各种互联网产品和系统中。它解决了分布式环境下ID生成的唯一性、时序性和高性能的问题。随着互联网技术的不断进步和发展,分布式ID生成系统将继续演化,但雪花算法作为其中的经典之作,其核心思想和设计理念将长久影响这一领域。

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

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

相关文章

【鸿蒙开发】第十五章 ArkTS基础类库-并发

1 简述 并发是指在同一时间段内&#xff0c;能够处理多个任务的能力。为了提升应用的响应速度与帧率&#xff0c;以及防止耗时任务对主线程的干扰&#xff0c;OpenHarmony系统提供了异步并发和多线程并发两种处理策略&#xff0c;ArkTS支持异步并发和多线程并发。并发能力在多…

FRM模型十四:FRA估值

什么是FRA FRA&#xff08;Forward rate agrreement&#xff09;远期利率协议&#xff0c;是一种场外衍生品。FRA在0时刻确定&#xff0c;在未来时刻进行交易的协议。例如FRA3,6表示双方约定在3个月后以Rk的利率水平借款3个月。 应用场景&#xff1a;某公司未来3个月有融资需…

Django官网项目

项目准备 使用VSCODE做IDE。 检查Python版本。 sudo apt install sudo apt update python3 --version创建项目路径&#xff0c;创建虚拟环境&#xff0c;创建项目 路径 \mysite 进入路径&#xff0c;运行VSCODE 运行 "code ." 创建虚拟环境。 选择 >python: c…

CommandLineRunner的使用

背景 在项目启动时需要做一些数据预加载或者某些操作&#xff0c;需要怎么办呢&#xff0c;方法其实有好几种&#xff0c;这里主要讲一下SpringBoot提供的CommandLineRunner接口的使用。一、案例说明以及实现 1.实现CommandLineRunner接口 定义一个类实现CommandLineRunner接…

PyTorch-卷积神经网络

卷积神经网络 基本结构 首先解释一下什么是卷积&#xff0c;这个卷积当然不是数学上的卷积&#xff0c;这里的卷积其实表示的是一个三维的权重&#xff0c;这么解释起来可能不太理解&#xff0c;我们先看看卷积网络的基本结构。 通过上面的图我们清楚地了解到卷积网络和一般网…

剪辑调色软件有哪些 会声会影视频剪辑软件 会声会影和剪映

视频调色做不好&#xff0c;可能不是操作的问题&#xff0c;而是剪辑软件没选对。大师级的画面感&#xff0c;就要用大师级的视频剪辑软件。不用费时费力苦心钻研&#xff0c;也无须死记硬背各种参数的软件&#xff0c;才是真正适合自己的剪辑调色软件。有关剪辑调色软件有哪些…

节省时间,创造价值:人工智能在工作中的实际应用

AI时代的工作流程&#xff1a;智能化操作&#xff0c;创新不止步 在当前的人工智能技术领域&#xff0c;无论是国内研发还是国际上的先进大型模型&#xff0c;本质上均采用了GPT&#xff0c;即生成式预训练Transformer模型。该模型的核心能力在于基于已学习的知识库生成回答。其…

用Java语言创建的Spring Boot项目中,如何传递数组呢??

问题&#xff1a; 用Java语言创建的Spring Boot项目中&#xff0c;如何传递数组呢&#xff1f;&#xff1f; 在这个思路中&#xff0c;其实&#xff0c;Java作为一个后端开发的语言&#xff0c;没必要着重于如何传入&#xff0c;我们主要做的便是对传入的数组数据进行处理即可…

李沐动手学习深度学习——3.3练习

欢迎讨论 1. 如果将小批量的总损失替换为小批量损失的平均值&#xff0c;需要如何更改学习率&#xff1f; 找到相关的函数介绍nn.MSELoss 默认api nn.MSELoss中是小批量损失的平均值&#xff0c;所以学习率为0.03 拿到对应的batch loss细节如下&#xff1a; 当学习率为0.0…

TOMCAT的安装与基本信息

一、TOMCAT简介 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP 程序的首选。对于一个初学者来说&#xff0c;可以这样认为&#xff0c…

在Jupyter-lab中使用RDKit画分子2D图

在Jupyter-lab中使用RDKit画分子2D图 在做完分子对接后&#xff0c;想看看筛选后的分子的结构。因此想利用Jupyter-lab来画分子的2D图。 1. 安装Jupyter-lab与RDKit 系统&#xff1a;Win11已安装conda RDKit 是一个功能强大、灵活易用的化学信息学工具包&#xff0c;广泛应…

【详识JAVA语言】面向对象程序三大特性之一:封装

封装的概念 面向对象程序三大特性&#xff1a;封装、继承、多态。而类和对象阶段&#xff0c;主要研究的就是封装特性。何为封装呢&#xff1f;简单来说 就是套壳屏蔽细节。 比如&#xff1a;对于电脑这样一个复杂的设备&#xff0c;提供给用户的就只是&#xff1a;开关机、通…

JetCache源码解析——API实现(持续更新中……)

在JetCache中不仅可以通过在类和接口的函数上使用注解Cached、CacheUpdate和CacheInvalidate等实现缓存加载、更新和删除操作&#xff0c;也支持通过调用API接口的形式来实现缓存的加载、更新和删除操作。 缓存接口 缓存接口的定义如下&#xff1a; /*** 缓存接口&#xff0…

力扣周赛387

第一题 代码 package Competition.The387Competitioin;public class Demo1 {public static void main(String[] args) {}public int[] resultArray(int[] nums) {int ans[]new int[nums.length];int arr1[]new int[nums.length];int arr2[]new int[nums.length];if(nums.leng…

SUPER 4PCS配准

一、下载编译 1、下载 链接&#xff1a;STORM-IRIT/OpenGR: OpenGR: A C library for 3D Global Registration (github.com) 不过有部分代码不能成功下载&#xff0c;要到该文件夹再次下载&#xff0c;就是下面标黄的两个文件&#xff0c;下载之后首先解压OpenGR-master.zip&…

python笔记_程序流程控制

A&#xff0c;顺序控制 程序从上到下逐行执行 python定义变量时&#xff0c;会合法地向前引用 age 1 age2 age 1 age2 age 1 age 1 ——>错误&#xff0c;age应在age2之前 B&#xff0c;分支控制 1&#xff0c;单分支if 语法 if 条件表达式 &#xff1a; 代码块 说明…

matplotlib条形图

matplotlib条形图 假设你获取到了2017年内地电影票房前20的电影(列表a)和电影票房数据(列表b), 那么如何更加直观的展示该数据? from matplotlib import pyplot as plta ["Wolf Warrior 2", "Fast and Furious 8", "Kung Fu Yoga", "Jo…

【Godot4自学手册】第十九节敌人的血量显示及掉血特效

这一节&#xff0c;我主要学习敌人的血量显示、掉血显示和死亡效果。敌人的血量显示和主人公的血量显示有所不同&#xff0c;主要是在敌人头顶有个红色的血条&#xff0c;受到攻击敌人的血条会减少&#xff0c;并且有掉血数量的文字显示&#xff0c;效果如下&#xff1a; 一、…

写时复制简介

写时复制技术(Copy on Write)是比较常用的一种技术&#xff0c;它的主要目的是延迟减少以及延迟内存的分配&#xff0c;增加执行效率&#xff0c;只有在真正进行写操作的过程中才会真正分配物理资源。同时&#xff0c;也可以保护数据在系统崩溃时出现的丢失。比如&#xff0c;我…

2.2 mul、div、and、or乘除指令及所有寄存器英文名

汇编语言 1. mul乘指令 两个相乘的数&#xff0c;要么都是8位&#xff0c;要么都是16位 两个8位数相乘 一个默认放在al中&#xff0c;另一个放在8位reg或内存字节单元中8位乘法&#xff0c;结果默认放在ax中例如&#xff1a;计算100*10 100和10小于255&#xff0c;可以做8位…