分布式唯一ID实现方案
不管是不是分布式系统,都有唯一ID的需求场景,而在分布式场景下,对 ID 的唯一性要求更严格,分布式ID有如下特性:
- 全局唯一性:确保生成的ID是全网唯一的。
- 有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
- 高可用性:确保任何时候都能正确的生成ID。
下面介绍几种分布式ID的生成方案。
# 数据库自增ID
以MySQL举例,利用给字段设置 auto_increment_increment和 auto_increment_offset来保证ID自增。
# 优点
- 数字ID天然排序,对分页或者需要排序的结果很有帮助。
# 缺点
- 在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。
- 分表分库的时候会有麻烦。
# 优化方案
针对主库单点,如果有多个Master库,则每个Master库设置的起始数字不一样,步长一样,可以是Master的个数,这样就可以有效生成集群中的唯一ID,也可以大大降低ID生成数据库操作的负载,但一旦把步长定好后,就无法扩容。
# UUID
UUID (Universally Unique Identifier) 的标准型式包含 32 个 16 进制数字,以连字号分为五段,形式为 8-4-4-4-12 的 36 个字符,示例:550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有 5 种方式生成 UUID。
# 优点
- 本地生成,性能非常高。
- 全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。
缺点
- UUID 太长,16 字节 128 位,通常以36长度的字符串表示,不易于存储和传输。
- 没有排序无法保证趋势递增。
- UUID往往是使用字符串存储,查询的效率比较低。
# Redis实现
Redis实现分布式唯一ID主要是通过提供像 INCR 和 INCRBY 这样的自增原子命令,由于Redis自身的单线程的特点所以能保证生成的 ID 肯定是唯一有序的。一般算法为:
年份 + 当天距当年第多少天 + 天数 + 小时 + redis自增
# 优点
- 不依赖于数据库,灵活方便,且性能优于数据库。
- 数字ID天然排序,对分页或者需要排序的结果很有帮助。
# 缺点
- 占用带宽,每次要向redis进行请求
# snowflake算法(雪花算法)
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。雪花算法将生成不高于19位的有序Long型整数,多用于分布式环境的数据主键。
其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。
# 优点
- 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
- 不依赖于数据库,灵活方便,且性能优于数据库。
# 缺点
- 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态,如果涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。
# zookeeper
根据zookeeper的有序递增临时节点的功能特性,可以保证某个znode节点下的所有序号递增,但是需要依赖zookeeper服务,而且高并发的时候,需要考虑分布式锁,这个时候性能就下降了。
# 如何选择
- 在分布式下,考虑到性能,存储效率和使用方便性,一般不会直接用UUID来做表唯一字段的ID的。另外UUID有可能泄露MAC地址。
- 如果没有分库的话,用数据库自增ID是不错的选择。
- 如果有分库可以使用不同步长的自增ID来避免冲突,如果还有继续扩容的可能的话,建议直接使用Redis或Snowflake的方案。
- 如果对连续性有要求的话,建议使用Redis生成方案,如果对连续性没有要求或者要求干净轻爽的方式的话,建议使用Snowflake方案。
- 另外连续ID有可能泄露业务信息,根据早晚的ID号,很容易推算出一天的业务量。