【高并发编程】之分布式全局唯一 ID

一、分布式全局唯一 ID 问题


1、为什么需要分布式全局唯一 ID 以及分布式 ID 的业务需求

在复杂的分布式系统中,往往需要对大量的数据和消息进行唯一标识,如在美团点评的金融、支付、餐饮、酒店,猫眼电影等产品的系统中数据日益增长,对数据分库分表后需要有一个唯一 ID 来标识一条数据或消息;特别一点的如订单、骑手、优惠券也需要有唯一 ID 做标识,此时,一个能够生成全局唯一 ID 的系统是非常有必要的

2、ID 生成规则部分硬性要求

  1. 全局唯一:不能出现重复的 ID 号,既然是唯一标识,那么这就是最基本的要求
  2. 趋势递增:在 MySQL 的 innoDB 引擎中使用的是聚集索引,由于多数 RDBMS 使用 Btree 的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能
  3. 单调递增:保证下一个 ID 大于上一个 ID,例如事务版本号、IM 增量信息、排序等特殊需求
  4. 信息安全:如果 ID 是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定 URL 即可,所以在一些应用场景下,需要ID无规则,不规则让竞争对手不好猜
  5. 含时间戳:这样就能在开发中快速了解分布式 ID 的生成时间

3、ID 号生成系统的可用性要求

  • 高可用:发一个获取分布式 ID 的请求,服务器就要保证 99.999% 的情况下给我创建一个唯一分布式 ID
  • 低延迟:发一个获取分布式ID的请求,服务器就要快,极速
  • 搞QPS:假如并发一口气创建分布式 ID 请求同时杀过来,服务器要顶得住且一下子成功创建10万

二、一般通用方案


1、UUID

UUID(Universally Unique Identity)的标准型式包含32个16进制数字,一连字号分为五段,形式为 8-4-4-4-12 的36个字符,比如:560e8609-e78b-68d4-716b-223344660000

其特点是:性能非常高,本地生成,没有网络消耗。如果只考虑唯一性是没问题的。

但是由于其随机性的原因会导致入数据库性能差:

  • 1、无序,无法预测它的生成顺序,不能成功递增有序的数字
  • 2、主键, ID 作为主键时在特定的环境会存在一些问题,比如在 DB 主键的场景下,MySQL 官方就有明确的建议主键尽量越短越好,36个字符长度的 UUID 不符合要求
  • 3、索引,B+ 数索引的分裂:因为 UUID 数据是无序的,所以每一次 UUID 数据的插入都会对主键地域的 B+ 树进行很大的修改,这一点很不好。插入完全无序,不但会导致一些中间节点产生分裂,也会白白创造很多不饱和的节点,这样大大降低了数据库插入的性能。

2、数据库自增主键

在分布式里面,数据库的自增 ID 机制主要原理是:数据库自增 ID 是通过 MySQL 数据库的 replace into 实现的

这里的 replace into 和 insert into 功能类似,不同点在于:replace into 首先尝试插入数据列表中,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,在插入,否则直接插入新数据。

但数据库自增 ID 机制仍然不适合分布式 ID:

  • 1、系统水平扩展比较困难:比如定义好了步长和机器台数之后,如果要添加机器该怎么做?假设现在只有一台机器起始号是 1,2,3,4,5,6 (步长是1),这个时候需要扩容一台机器一台。可以这样做:把第二台机器的初始值设置得比第一台超过很多,貌似还好,现在想象一下,如果我们线上有 100 台机器,这个时候要扩容该怎么办?简直是噩梦。所以系统水平扩展方案复杂难以实现。
  • 2、数据库压力还是很大,每次获取 ID 都得读写一次数据库,非常影响性能,不符合分布式 ID 里面的低延迟和高 QPS 的规则(在高并发下,如果都区别数据库里面获取 ID,那是非常影响性能的)。

3、基于 Redis 生成全局 ID 策略

因为 Redis 是单线程的,天生保证原子性,可以使用原子操作 INCR 和 INCRBY 来实现。

但要注意:在 Redis 集群情况下,同样和 MySQL 一样需要设置不同的增长步长,同时 key 一定要设置有效期。

所以,可以使用 Redis 集群来获取更高的吞吐量,假如一个集群中有5台Redis,可以初始化每台 Redis 的值分别为 1,2,3,4,5,然后步长都是5,各个 Redis 生成的 ID 为:

  • A:1,6,11,16,21
  • B:2,7,12,17,22
  • C:3,8,13,18,23
  • D:4,9,14,19,24
  • E:5,10,15,20,25

虽然使用 Redis 可以解决高吞吐量的问题,但为了一个 ID 问题居然要额外维护一个 Redis 集群,显然,这是不切实际的。


三、雪花算法 SnowFlake


1、SnowFlake 简介

雪花算法 SnowFlake 是 Twitter 产品的分布式自增 ID 算法,具体源码可以参考官网:https://github.com/twitter-archive/snowflake

Twitter 的分布式雪花算法 SnowFlake ,经测试每秒能够生成26万个自增可排序的 ID

  • 1、生成的 ID 能够按照时间有序生成
  • 2、算法生成 ID 的结果是一个64位大小的整数,为一个 Long 型(转换成字符串后长度最多19个字符)
  • 3、分布式系统内不会产生 ID 碰撞(有 datacenter 和 workerId 作区分)并且效率较高

SnowFlake算法产生的ID是一个64位的整型,结构如下(每一部分用“-”符号分隔):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

1位标识部分,在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以为0;

41位时间戳部分,这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L 60 60 24 365) = 69年;

10位节点部分,Twitter实现中使用前5位作为数据中心标识,后5位作为机器标识,可以部署1024个节点;

12位序列号部分,支持同一毫秒内同一个节点可以生成4096个ID;

SnowFlake算法生成的ID大致上是按照时间递增的,用在分布式系统中时,需要注意数据中心标识和机器标识必须唯一,这样就能保证每个节点生成的ID都是唯一的。或许我们不一定都需要像上面那样使用5位作为数据中心标识,5位作为机器标识,可以根据我们业务的需要,灵活分配节点部分,如:若不需要数据中心,完全可以使用全部10位作为机器标识;若数据中心不多,也可以只使用3位作为数据中心,7位作为机器标识。

snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。

2、SnowFlake 工程落地经验

糊涂工具包就包含了 SnowFlake 的应用:

  • https://github.com/looly/hutool
  • https://hutool.cn/

SpringBoot 整合雪花算法:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-captcha</artifactId>
    <version>5.2.0</version>
</dependency>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值