• 分布式下的 ID 实现


    分布式 —— 全局唯一 ID 实现方案

    为什么需要全局唯一 ID?分布式服务架构模式下分库分表的设计,使得多个库或多个表存储相同的业务数据。这种情况根据数据库的自增ID就会产生相同ID的情况,不能保证主键的唯一性

    业务系统对 ID 的要求有哪些呢?

    1. 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求
    2. 趋势递增:在 MySQL InnoDB 引擎中使用的是聚集索引,由于多数 RDBMS 使用 B-tree 的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能
    3. 单调递增:保证下一个 ID 一定大于上一个 ID,例如事务版本号、IM增量消息、排序等特殊需求
    4. 信息安全:如果 ID 是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要 ID 无规则、不规则
    5. 高可用、高性能:如果 ID 生成系统不可用,则所有下游服务都不可用

    对于 3、4 而言,需求是互斥的,无法使用同一个方案解决

    通常使用号段模式满足 3,雪花算法满足 4

    前后端联调的坑(long 传给前端接受的坑):JS Number 采用的 IEEE 754 规范,也就是相当于 Java 的 double;double 最大允许的正整数为 2 53 − 1 2^{53} - 1 2531,long 最大值为 2 63 − 1 2^{63} - 1 2631,所以可能造成数据精度的丢失

    号段模式

    号段模式:一次性向数据库申请一批连续的 ID 放入 JVM 缓存,当用完时再从数据库中拉取

    号段模式的优点:本机单调递增、全局趋势递增

    号段模式的问题:

    1. 步长的选取。步长太长,服务重启则整个号段作废;步长太短,频繁访问数据库,数据库压力很大
    2. 并发问题:多服务器同时获取号段,可能分配同一号段
    3. 线程阻塞:当号段消费完时,需要向数据库拉取号段,在此期间服务不可用
    4. 单点故障:号段拉取完全依赖于数据库,当数据库不可用时整个服务不可用

    上诉问题优化:

    1. 自适应步长,步长初始值很小,当成功消费完所有补偿后还没达到阈值则加倍拉取,若中途服务重启,则将步长设为初始值
    2. 加锁。悲观锁(数据库行锁),乐观锁(版本号)
    3. 双号段缓存。在号段快用完时,提前异步加载下一个号段
    4. 多数据库实例支持:当其中一个库不可用时,使用另外一个库拉取 id

    多实例支持将引入新的问题,可能造成拉取重复 id,如何避免?

    引入内步长的概念。如有三台数据库实例,则将内步长置为 3。起始序列号设 A 为 1,B 为 2,C 为 3

    则每次向不同实例拉取时,id + 内步长

    此时 A 的 增长趋势为 1 4 7 10 …

    此时 B 的 增长趋势为 2 5 8 11 …

    此时 C 的 增长趋势为 3 6 9 12 …

    号段模式业界经典实现:美团 Leaf-segment,滴滴 tinyid

    雪花算法

    雪花算法组成:1 + 41 + 10 + 12;1 是符号位始终为 0 保证 id 为正数;41 是时间戳具体到毫秒(从 1970 年开始)理论上限为 69 年;10 是所允许机器 ID 数,最大允许 1024 台机器;12 是同一时间点上最大允许生成的 id 数,即每毫秒最多分配 4096 个 ID

    雪花算法优化:时间戳从 1970 年开始,白白浪费几十年,可以从实际投入使用时间开始,如 2022 年;雪花算法各个组成都可以按需进行分配

    雪花算法的优点:趋势递增,不依赖第三方组件,按需分配 bit

    为什么使用雪花算法,而不是使用号段模式?出于安全性考虑,如:通过 id 猜测订单数量。严禁 id 号生成单调递增

    为什么使用雪花算法,而不是使用 UUID?出于性能考虑,插入数据库时主键乱序则可能导致页分裂,严重影响插入效率

    雪花算法的缺陷:时间回拨;因为雪花算法生成的 ID 是基于时间的,现实世界的时间是单调的,但对于计算机而言却不是

    时间回拨:服务器时间突然倒退到之前的时间,则可能产生重复 ID

    造成时间回拨的原因:1. 手动改服务器时间(一般不会);2. 服务器开启 NTP 服务做时间校准(重点)

    什么是 ntp?网络时间协议,用以同步分布式场景下各服务器时间;基于 UDP 协议

    为什么需要 ntp,而不是使用服务器自己的时间?长时间允许的服务器会造成不可容忍的误差,所以需要同步时钟

    解决时间回拨方案:等待时钟同步,如果当前生成的 ID 时间戳小于前一个就不是有效的 ID,需要循环生成直到为有效 ID;此法可能造成长时间的空转,所以需要设置最大容忍阈值

    雪环算法经典业界实现:MongoDB ObjectID,美团 Leaf-snowflake,百度 UidGenerator

    参考资料

  • 相关阅读:
    数组模拟堆实现堆排序
    算法项目(1)—— LSTM+CNN+四种注意力对比的股票预测
    python第九节:类的使用(1)
    Delphi Controls (控件)和Components (组件)的异同
    vue环境搭建
    C/C++数字与字符串互相转换
    【计算机组成原理】IEEE 754
    python输出小数控制的方法
    [案例] java项目故障诊断和性能调优(Linux版)
    因果推断,入门代码解读
  • 原文地址:https://blog.csdn.net/u013570834/article/details/126328592