分布式主键ID生成方式-snowflake雪花算法

news2025/1/10 9:53:37

这里写自定义目录标题

  • 一、业务场景
  • 二、技术选型
    • 1、UUID方案
    • 2、Leaf方案-美团(基于数据库自增id)
    • 3、Snowflake雪花算法方案
  • 总结

一、业务场景

大量的业务数据需要保存到数据库中,原来的单库单表的方式扛不住大数据量、高并发,需要分库分表;这样原来的数据库自增id作为主键就不能满足业务需求,需要有一个在分库分表中的唯一标识id作为主键,这个id需要有如下要求:

  • 全局唯一性
  • 趋势递增:在MySQL的InnoDB中使用的是聚焦索引,用的是B-tree的数据结构来存储索引数据,所以要尽量选用有序的主键来保证写入性能
  • 信息安全:不能通过主键看出业务信息

二、技术选型

1、UUID方案

uuid是32位数的16进制数字所构成,以连字号分为五段,总共有 36个字符(即三十二个英数字母和四个连字号),550e8400-e29b-41d4-a716-446655440000,所以理论上uuid的总数有16^32,基本用不完。
优点: 性能非常强,本地内存生成。
缺点: 36个字符串存储,太长了,而且生成的id是无序的,MySQL要求主键越短越好,同时要有序,保证索引的写入性能。

2、Leaf方案-美团(基于数据库自增id)

  1. 在数据库中设计一张表用于生成自增id
    | biz_tag | maxId | step
    | user_tab | 2000 | 1000
    | home_tab | 3000 | 2000
    biz_tag是业务表名用来区分业务,maxId是目前所被分配的id号段的最大值,step是每次分配的长度。
  2. Leaf微服务从数据库中一次取step个号码端,比如step为1000,则每次取1000个到Leaf服务内存中,用于应用层调用接口获取主键id,每调一次加一,内存中的1000个用完后,Leaf再去数据库取一次号码段(取的时候maxId也会相应更新)。
  3. 这样Leaf服务和数据库交互频率就大大减少,性能瓶颈就不在数据库,而在于Leaf微服务,而Leaf服务是无状态的,因此可以根据实际需求横向扩展,可以部署多个Leaf微服务用于获取主键id
    架构图
    优点:
  • 扩展性好,可以随着业务的发展线性扩展多个Leaf服务
  • 生成的主键id是趋势递增的8byte的64位数,符合数据库存储的主键id要求
  • 容灾性好,即使DB宕机一会,Leaf服务内存中缓存的号码段可以支撑一段时间等待DB恢复
  • maxId可以自定义大小,方便其他业务ID迁移到Leaf服务。
    缺点:
  • ID不够随机,安全性不够
  • TP999性能波动大,当某一时刻,多个Leaf服务的号码段都使用到999最后一个时,同时调用数据库获取号码端,会造成偶尔的突刺,导致获取主键ID延迟。
  • DB数据库长时间宕机会导致整个服务不可用。
    优化:
    争对TP999,可以采用提前获取号码段(双buffer)的方式,当Leaf内存中还剩下指定的号码时(eg:800),就提前获取下1000个号码段放到内存中,即Leaf服务内部有两个号段缓存区segment,这样当数据库调用延迟,需要等待时,Leaf服务还有号码段可以对外提供服务。
    DB数据库可以采用一主两从的方式,或者用多机房,提高容灾性。

3、Snowflake雪花算法方案

Snowflake算法可以生成64位的ID,刚好可以用Long型存储,并且生成的ID有大致的顺序;它以划分命名空间的方式,讲64-bit位划分为4个部分:
0 - 41位时间戳 - 10位机器id - 12位序列号
示意图

  1. 第一位是符号位,不用。
  2. 第二部分是41位的时间戳,可以表示2^41个数,每个数代表毫秒(ms)。
  3. 第三部分是10位的机器id,即2^10=1024台机器,实际中用不到这么多机器,可以进一步细分,加上机房信息,或者业务信息。
  4. 第四部分是12位的自增序列,2^12=4096个数,即理论上1ms内一台机器支持4096个请求。

Java实现:

/**
 * twitter的snowflake算法 -- java实现
 * 
 */
public class SnowFlake {

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12; //序列号占用的位数
    private final static long MACHINE_BIT = 5;   //机器标识占用的位数
    private final static long DATACENTER_BIT = 5;//数据中心占用的位数

    /**
     * 每一部分的最大值
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    private long datacenterId;  //数据中心
    private long machineId;     //机器标识
    private long sequence = 0L; //序列号
    private long lastStmp = -1L;//上一次时间戳

    public SnowFlake(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    /**
     * 产生下一个ID
     *
     * @return
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            // 时钟回拨问题怎么处理?
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currStmp == lastStmp) {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            //不同毫秒内,序列号置为0
            sequence = 0L;
        }

        lastStmp = currStmp;

        return (currStmp << TIMESTMP_LEFT) //时间戳部分
                | (datacenterId << DATACENTER_LEFT)       //数据中心部分
                | (machineId << MACHINE_LEFT)             //机器标识部分
                | sequence;                             //序列号部分
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        SnowFlake snowFlake = new SnowFlake(1, 1);

        for (int i = 0; i < (1 << 12); i++) {
            System.out.println(snowFlake.nextId());
        }

    }
}

时钟回拨问题:
当某台机器的时间出现了问题,回到了前几秒,则调用该机器雪花算法时,会生成重复的id,因为前几秒是时间已经生成过id了。
根据实际的业务和机器的情况不同,有几种解决方案:

  1. 当回拨的时间不长,比如不到100ms,小于接口调用超时时间,则可以用sleep方法等待时间正常。
  2. 当回拨时间适中,比如100ms~1s内,等待的话会接口超时,这种情况,可以将前一秒的每个ms的最大序列号维护在缓存中,然后maxId+1。
  3. 当回拨时间比较长,可以重试调用其他机器生成id,等过一段时间再调用该机器。或者直接把这个机器下线掉。

雪花算法生成架构:
在这里插入图片描述
部署多个服务,通过服务发现被应用服务调用,同时机器id可以动态通过zookeeper(可以生成自增序列)获取。

总结

分布式主键id的生成方式有很多种,最重要的是根据自己的实际业务情况来选择最合适自己的一种方式。

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

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

相关文章

创建基本的 Electron 应用项目的详细步骤

创建一个基本的 Electron 应用项目的详细步骤。我们将从安装 Node.js 开始&#xff0c;然后创建项目文件夹并初始化 Electron 项目。 1. 安装 Node.js 首先&#xff0c;确保你已经安装了 Node.js 和 npm。你可以在终端中运行以下命令来检查是否已经安装&#xff1a; node -v…

对话新晋 Apache SeaTunnel Committer:张圣航的开源之路与技术洞察

近日&#xff0c;张圣航被推选为 Apache SeaTunnel 的 Committer成员。带着对技术的热情和社区的责任&#xff0c;他将如何跟随 Apache SeaTunnel 社区迈向新的高度&#xff1f;让我们一起来聆听他的故事。 自我介绍 请您简单介绍一下自己&#xff0c;包括职业背景、当前的工作…

超完整Docker学习记录,Docker常用命令详解

前言 关于国内拉取不到docker镜像的问题&#xff0c;可以利用Github Action将需要的镜像转存到阿里云私有仓库&#xff0c;然后再通过阿里云私有仓库去拉取就可以了。 参考项目地址&#xff1a;使用Github Action将国外的Docker镜像转存到阿里云私有仓库 一、Docker简介 Do…

JVM实战—OOM的定位和解决

1.如何对系统的OOM异常进行监控和报警 (1)最佳的解决方案 最佳的OOM监控方案就是&#xff1a;建立一套监控平台&#xff0c;比如搭建Zabbix、Open-Falcon之类的监控平台。如果有监控平台&#xff0c;就可以接入系统异常的监控和报警&#xff0c;可以设置当系统出现OOM异常&…

你知道智能家居与fpc有哪些关联吗?【新立电子】

智能家居&#xff0c;作为现代科技与家居生活深度融合的产物&#xff0c;它不仅仅是一种技术革新&#xff0c;更是一种生活理念的升级&#xff0c;将家居环境打造成为一个更加智能、舒适和安全的生活空间。 智能家居的核心在于其通过互联网、物联网、人工智能等技术手段&#…

STM32 : PWM 基本结构

这张图展示了PWM&#xff08;脉冲宽度调制&#xff09;的基本结构和工作流程。PWM是一种用于控制功率转换器输出电压的技术&#xff0c;通过调整信号的占空比来实现对负载的精确控制。以下是详细讲解&#xff1a; PWM 基本结构 1. 时基单元 ARR (Auto-reload register): 自动…

STM32之一种双通路CAN总线消息备份冗余处理方法(十三)

STM32F407 系列文章 - Dual-CANBus-ProMethod&#xff08;十三&#xff09; 目录 前言 一、现状分析 二、解决思路 1.应用场景网络结构图 2.数据发送流程 3.数据接收流程 4.用到的模块 1.CAN网络速率及时间片分配 2.CAN网络消息ID组成 3.设备节点定义 4.数据格式说明…

内网穿透的应用-Ubuntu本地Docker部署Leantime项目管理工具随时随地在线管理项目

文章目录 前言1.关于Leantime2.本地部署Leantime3.Leantime简单实用4.安装内网穿透5.配置Leantime公网地址6. 配置固定公网地址 前言 本文主要介绍如何在本地Linux系统使用Docker部署Leantime&#xff0c;并结合cpolar内网穿透工具轻松实现随时随地查看浏览器页面&#xff0c;…

VulnHub-Acid(1/100)

参考链接&#xff1a; ​​​​​​​【VulnHub】Acid靶场复盘-CSDN博客 靶场渗透&#xff08;二&#xff09;——Acid渗透_ambassador 靶场渗透-CSDN博客 网络安全从0到0.5之Acid靶机实战渗透测试 | CN-SEC 中文网 Vulnhub靶场渗透练习(四) Acid - 紅人 - 博客园 红日团队…

HTML5实现好看的端午节网页源码

HTML5实现好看的端午节网页源码 前言一、设计来源1.1 网站首页界面1.2 登录注册界面1.3 端午节由来界面1.4 端午节习俗界面1.5 端午节文化界面1.6 端午节美食界面1.7 端午节故事界面1.8 端午节民谣界面1.9 联系我们界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 H…

git merge与rebase区别以及实际应用

在 Git 中&#xff0c;merge 和 rebase 是两种将分支的更改合并到一起的常用方法。虽然它们都可以实现类似的目标&#xff0c;但它们的工作方式和效果有所不同。 1. Git Merge 定义&#xff1a;git merge 是将两个分支的历史合并在一起的一种操作。当你执行 git merge 时&…

Matlab APP Designer

我想给聚类的代码加一个图形化界面&#xff0c;需要输入一些数据和一些参数并输出聚类后的图像和一些评价指标的值。 gpt说 可以用 app designer 界面元素设计 在 设计视图 中直接拖动即可 如图1&#xff0c;我拖进去一个 按钮 &#xff0c;图2 红色部分 出现一行 Button 图…

PyCharm 引用其他路径下的文件报错 ModuleNotFound 或报红

PyCharm 中引用其他路径下的文件提示 ModuleNotFound&#xff0c;将被引用目录添加到系统路径&#xff1a; # # 获取当前目录 dir_path os.path.dirname(os.path.realpath(__file__)) # # 获取上级目录 parent_dir_path os.path.abspath(os.path.join(dir_path, os.pardir))…

【HarmonyOS NEXT】鸿蒙应用点9图的处理(draw9patch)

【HarmonyOS NEXT】鸿蒙应用点9图的处理&#xff08;draw9patch&#xff09; 一、前言&#xff1a; 首先在鸿蒙中是不支持安卓 .9图的图片直接使用。只有类似拉伸的处理方案&#xff0c;鸿蒙提供的Image组件有与点九图相同功能的API设置。 可以通过设置resizable属性来设置R…

SOLID原则学习,开闭原则

文章目录 1. 定义2. 开闭原则的详细解释3. 实现开闭原则的方法4. 总结 1. 定义 开闭原则&#xff08;Open-Closed Principle&#xff0c;OCP&#xff09;是面向对象设计中的五大原则&#xff08;SOLID&#xff09;之一&#xff0c;由Bertrand Meyer提出。开闭原则的核心思想是…

【Vue3中使用crypto-js】crypto-js加密解密用法

目录 1、安装crypto2、创建crypto.js文件3、在main.js主文件中进行引用4、页面中进行使用5、实现效果展示6、加密模式解析以及iv参数使用 1、安装crypto npm install crypto-js 如果是在Typescript版本需要再安装 npm install --save types/crypto-js2、创建crypto.js文件 注…

跨界融合:人工智能与区块链如何重新定义数据安全?

引言&#xff1a;数据安全的挑战与现状 在信息化驱动的数字化时代&#xff0c;数据已成为企业和个人最重要的资产之一。然而&#xff0c;随着网络技术的逐步优化和数据量的爆发式增长&#xff0c;数据安全问题也愈变突出。 数据安全现状&#xff1a;– 数据泄露驱动相关事件驱…

简单易用的PDF工具箱

软件介绍 PDF24 Creator是一款简单易用的PDF工具箱&#xff0c;而且完全免费&#xff0c;没有任何功能限制。既可以访问官网在线使用各种PDF工具&#xff0c;也可以下载软件离线使用各种PDF工具。 软件功能 1、PDF转换 支持将多种文件格式&#xff08;Word、PowerPoint、Exc…

低秩信息收集_0109

系列博客目录 文章目录 系列博客目录LoRA: Low-Rank Adaptation of Large Language Models传统模型适配的局限性&#xff1a;尽管研究界致力于通过添加适配器层或优化输入层激活来提高模型适配效率&#xff0c;这些方法在大型模型和延迟敏感的环境中存在局限。适配器层尽管参数…

C语言与ASCII码应用之简单加密

加密是什么&#xff1f;什么是加密通话&#xff1f;用人话说就是一句有含义的话&#xff0c;经过一定的特殊规则把里面的每个字按照这个规则进行改变&#xff0c;但是这个规则只有你和你想让知道这条信息的人知道 今天我们来用ASCII码编写一个简单加密与解密的程序&#xff0c…