【分布式】分布式ID

这篇具有很好参考价值的文章主要介绍了【分布式】分布式ID。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

分布式场景下,一张表可能分散到多个数据结点上。因此需要一些分布式ID的解决方案。

分布式ID需要有几个特点:

  • 全局唯一(必要) :在多个库的主键放在一起也不会重复
  • 有序(必要) :避免频繁触发索引重建
  • 信息安全:ID连续,可以根据订单编号计算一天的单量,造成信息泄露
  • 包含时间戳:能够快速根据ID得知生成时间

下面几种方案按推荐顺序排序,越推荐使用越靠前。

一、雪花算法snowflake

64 位的 long 类型的唯一 id
【分布式】分布式ID,# 分布式,分布式

1. 组成

1)1位不用

带符号整数第1位是符号位,正数是0,ID一般为正数,此位不用。

2)41位毫秒级时间戳

41位存储当前时间截 – 开始时间截得到的差值,可以表示 2 41 2^{41} 241个毫秒的值,转化成单位年则是: 2 41 1000 ∗ 60 ∗ 60 ∗ 24 ∗ 365 = 69 年 \frac{2^{41}}{1000∗60∗60∗24∗365}=69年 1000606024365241=69

注:开始时间截由程序指定,一般是id生成器开始使用的时间,设置好后避免更改。依赖服务器时间,服务器时钟回拨时可能会生成重复 id。

3)10位机器ID

生成ID的服务可以部署在1024台机器上

4)12位序列号
能够表示4096个序列号。

因此,某一毫秒,同一台机器,最多能生成4096个序号。理论上单机QPS最大为4096*1000=409.6w/s

2. 优缺点

优点:

  • ID不重复:用时间戳+机器+序号生成不重复ID
  • 性能高:在内存中生成
  • 有序

缺点:

依赖服务器时间,存在时钟回拨的问题。

3. 时钟回拨怎么解决

时钟回拨可能产生重复ID进而影响关联系统。

a. 时钟回拨

什么是时钟回拨: 服务器上的时间倒退回之前的时间

哪些情况造成时钟回拨:

  • 人为修改服务器时间
  • 时钟同步后,由于机器之间时间不同,可能产生时钟回拨

b. 解决方案

算法中会记录当前服务上次生成ID的最后时间,只需要保证我下次生成ID的时间大于上次最后时间即可。根据回拨后时间距离上次生成最后时间大小,可以有不同的解决方案。

  • 相差0~100ms :等待直至当前时间超过上次最后生活时间
  • 相差100ms~1s:采用等待方式可能导致接口超时。可以记录已生成ID的最大ID,在这个基础上++。(预留扩展位,在扩展位上增加。回拨后又回拨可能有问题)
  • 相差1s~5s:采用最大ID增加的方式,时间过长可能导致范围溢出。可以生成ID服务响应异常,由调用方例如基于Ribbon调用其他生成ID服务。
  • 相差超过5s:采用Ribbon循环调用的方式,下次访问到时钟回拨的服务可能还没达到上次生成最后时间,浪费时间。可以让超过5s的服务主动下线,并通知运维,人工介入,等待时钟正常后再重启。

4. 项目中如何使用

【分布式】分布式ID,# 分布式,分布式

时钟回拨的处理逻辑在nextId()里的if (timestamp < lastTimestamp) 逻辑下。这里直接抛出异常。

public class SnowflakeIdWorker {

    // ==============================Fields===========================================
    /** 开始时间截 (2020-01-01) */
    private final long twepoch = 1577808000000L;

    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;

    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;

    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;

    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;

    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作机器ID(0~31) */
    private long workerId;

    /** 数据中心ID(0~31) */
    private long datacenterId;

    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;

    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;

    //==============================Constructors=====================================
    /**
     * 构造函数
     * @param workerId 工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public  SnowflakeIdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    // ==============================Methods==========================================
    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        //如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    //==============================Test=============================================
    /** 测试 */
    public static void main(String[] args) {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);

        for (int i = 0; i < 100; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
    }
}

二、基于Redis

三、基于Zookeeper

四、号段模式

数据库中保存号段表,每次从数据库获取一个号段范围,由服务在内存中自增生成ID,直到达到号段范围再去获取。

id 业务类型 最大可用ID 号段长度 版本号
1 xxx 2000 1000 0
updateset 最大可用ID = 3000, version = version + 1 where version = 0 and biz_type = xxx

采用乐观锁的方式避免长时间锁表。

优点:

  • ID有序递增
  • 对数据库压力比较小

缺点:

  • 存在安全问题,用ID可以判断数据量
  • 存在单点问题,集群实现困难

五、指定步长的自增ID

多主集群模式下,表主键设置自增ID,多节点之间会有重复ID。需要采用指定初始值的自增步长
举例:两台数据库A,B。A初始值和步长为(1,2),B初始值和步长为(2,2)。两张表生成的主键分别为
A:1,3,5,7…
B:2,4,6,8…

设置方法:

set @@auto_increment_offset = 1;     -- 起始值
set @@auto_increment_increment = 2;  -- 步长

优点:

  • 简单、解决单点问题

缺点:

  • 扩容困难

六、UUID

128位长的字符标识串,由32个16进制数字组成,用-连接,共36个字符。如:03425604-5462-11ee-80ad-80fa5b8732b1

生成算法与重复性:

  • 基于随机数:不重复
  • 基于MAC地址:不重复
  • 基于时间戳:可能重复

作为分布式ID的优缺点:

  • 优点:本地生成,性能高,无网络损耗
  • 缺点:
    • 无序:造成索引重建,入库性能差
    • 字符串长:需要36字符

参考

  • UUID会重复吗?
  • 雪花算法视频
  • 9种分布式ID生成方式

六、扩展

  • 百度:UidGenerator
  • 美团:Leaf

总结

优点 缺点
uuid 实现简单 连续性差,作为主键每次新增数据都会触发索引重建。
分布式环境中可能重复
雪花算法 性能好,有序 依赖服务器时间,时钟回拨可能生成重复ID
号段模式
redis/zookeeper Redis基于INCR 命令生成 分布式全局唯一id
zookeeper一种通过节点,一种通过节点的版本号

基因算法文章来源地址https://www.toymoban.com/news/detail-731866.html

到了这里,关于【分布式】分布式ID的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 架构设计-分布式ID

    1.不要用主键ID作为业务单号的唯一标识,因为一是数据同步麻烦,第二一旦业务数据扩张涉及到分库分表则数据维护麻烦,因为此时主键ID容易造成重复 。 2.对于有相似属性的业务ID如直播或者录播ID存储在业务表中的一个字段,一旦程序员哪天状态不好忘记区分类型,就很

    2024年02月03日
    浏览(88)
  • 算法、语言混编、分布式锁与分布式ID、IO模型

    数据结构和算法是程序的基石。我们使用的所有数据类型就是一种数据结构(数据的组织形式),写的程序逻辑就是算法。 算法是指用来操作数据、解决程序问题的一组方法。 对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源(空间

    2024年02月08日
    浏览(46)
  • 分布式ID系统设计(2)

    https://editor.csdn.net/md/?articleId=133988963 应用举例 mongoDB ObjectID 就是一个典型的实现。 以MySQL举例 利用给字段设置AUTO-INCREMENT来保证ID自增,每次业务使用SQL拿到MySQL的ID 这种方案的优缺点: 优点 1 简单。利用数据库实现 成本小,有专业的DBA维护 2 ID单调递增。用来实现一些对于ID有

    2024年02月06日
    浏览(42)
  • 分布式ID系统设计(1)

    我们姑且把它叫做id-server 。那么这么个id-server的设计和考虑需要什么 全局唯一:不能出现重复的id号 最基本要求。 趋势递增: 在innodb中使用的是聚集索引。B+Tree的pk最好是有序的 单调递增:保证下一个id一定要大于上一个id 安全:如果ID是连续的 被爬虫的可能性能就很大。有一些

    2024年02月08日
    浏览(44)
  • 分布式ID系统设计(3)

    第二集说了id-service-Segment-DB可以生成趋势递增的ID,但是ID号是可以计算的。不太适用于一些订单ID生成的场景。因为存在数据暴露的风险 比如我可以对比两天的订单ID号来大致计算出公司一天的订单量。这个有点危险。 所以我们需要id-serviceSnowFlake方案。 id-service-snowFlake完全沿

    2024年02月06日
    浏览(47)
  • MongoDB的分布式ID

    MongoDB ObjectID是MongoDB数据库中的一种数据类型,用于表示一个文档(document)在集合(collection)中的唯一标识符。每个ObjectID值是一个12字节的字符串,其中前四个字节表示时间戳,后三个字节表示机器编号,后两个字节表示进程编号,最后一个字节表示随机数。由于MongoDB O

    2024年02月16日
    浏览(45)
  • 分布式—雪花算法生成ID

    由64个Bit(比特)位组成的long类型的数字 0 | 0000000000 0000000000 0000000000 000000000 | 00000 | 00000 | 000000000000 1个bit:符号位,始终为0。 41个bit:时间戳,精确到毫秒级别,可以使用69年。 10个bit:工作机器ID,可以部署在1024个节点上。 12个bit:序列号,每个节点每毫秒内最多可以生成

    2024年02月11日
    浏览(44)
  • 分布式唯一ID 雪花算法

           📝个人主页:五敷有你        🔥系列专栏:算法分析与设计 ⛺️稳中求进,晒太阳 雪花算法是 64 位 的二进制,一共包含了四部分: 1位是符号位,也就是最高位,始终是0,没有任何意义,因为要是唯一计算机二进制补码中就是负数,0才是正数。 41位是时间戳

    2024年04月10日
    浏览(46)
  • 76、分布式id生成方案

    1,当前日期和时间 时间戳 2,时钟序列。 计数器 3,全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。 优点: 代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽略) 缺点: 每次生成的ID都是无序的,

    2024年02月16日
    浏览(49)
  • 分布式id的概述与实现

    随着业务的增长,数据表可能要占用很大的物理存储空间,为了解决该问题,后期使用数据库分片技术。将一个数据库进行拆分,通过数据库中间件连接。如果数据库中该表选用ID自增策略,则可能产生重复的ID,此时应该使用分布式ID生成策略来生成ID。 提示:以下是本篇文

    2024年02月07日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包