分布式中如何保证ID的全局唯一性


之前经常听到、看到关于分布式系统中,唯一ID如何选择的讨论,应用系统中常见的全局唯一的序列号有数据库表的主键、request ID、有业务含义的交易号。不论支持哪种论点,大多数论据都是业务层面的,这篇文章从理论开始,希望从最佳实践的角度看下如何选择ID的生成方式。

数据库主键

数据库索引为B+树,而主键也是一种索引。索引数据在 B+ 树中是有序排列的,ID的递增特性,插入数据时,数据库只需要把新增索引追加在后面即可;而如果需要在中间插入数据就会引起页分裂,产生数据移动开销。另外,机械磁盘完成随机写时,要让磁头找到对应的磁道,这个过程时非常耗时的。而顺序写就不需要寻道,大大提升索引的写入性能。
所以,如果是单库单表的场景下,我们可以使用数据库的自增字段作为 ID,因为这样最简单,对于开发人员来说也是透明的。

UUID

UUID(Universally Unique Identifier,通用唯一标识码)不依赖于任何第三方系统,所以在性能和可用性上都比较好,但我们一般会使用它生成 Request ID 来标记单次请求,如果用它来作为数据库主键,它会存在以下几点问题:

  1. UUID 不具备单调递增性
  2. UUID 是由 32 个 16 进制数字组成的字符串,作为主键使用比较耗费存储空间。

有业务含义的全局唯一序列号

Snowflake 的核心思想是将 64bit 的二进制数字分成若干部分,每一部分都存储有特定含义的数据,比如说时间戳、机器 ID、序列号等等,最终生成全局唯一的有序 ID。它的标准算法是这样的:

在这里插入图片描述

41 位的时间戳大概可以支撑 pow(2,41)/1000/60/60/24/365 年,约等于 69 年,对于一个系统是足够了。
如果你的系统部署在多个Data Center,那么 10 位的机器 ID 可以继续划分为 1~2 位的 IDC 标示(可以支撑 2 个或者 4 个 Data Center)和 8~9 位的机器 ID(支持 256-512 个实例);12 位的序列号代表着每个节点每毫秒最多可以生成 4096 的 ID。

一般我们会采用类Snowflake算法来生成。比如说减少序列号的位数增加机器 ID 的位数以支持单 IDC 更多的机器,也可以在其中加入业务 ID 字段来区分不同的业务。比方说我现在使用的发号器的组成规则就是:1 位兼容位恒为 0 + 41 位时间信息 + 6 位 IDC 信息(支持 64 个 IDC)+ 6 位业务信息(支持 64 个业务)+ 10 位自增信息(每毫秒支持 1024 个号)。业务信息指的是项目中哪个业务哪个组织使用,比如业务A生成的 ID,用户A生成的 ID,把它加入进来,一是希望不同业务发出来的 ID 可以不同,二是因为在出现问题时可以反解 ID,知道与其相关的用户及业务。

序列号生成

这个序列号最好也是一个有序递增的序列,避免了在对该序列号做计算比如hash后出现大量碰撞。
可以使用乐观锁的方式生成。每次序列号自增一定范围,比如1000;将该新增的seq_range放入缓存中,例如并发容器中防止多线程并发下重复读取。一个线程放入新的序列号后,其他线程直接从缓存中取这个序列号,直至用完再次更新乐观锁,并更新缓存。

重复的序列号是怎么回事?
出现的原因为生成的新的序列号后,系统产生消息并发出给了消息队列,但此时DB的事务还未提交系统被shutdown。即,乐观锁更新失败,但序列号生成成功并被使用,发送到下游系统。一旦应用短时间内重启,就会出现重复序列号的问题,导致后续交易失败。但这只是短时间内非常偶然事件,也是快速失败,基本无延迟,不会造成大量资源被占用无法释放,用户重试即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值