在分布式场景下,特别是在高并发,高数据量的情况下,ID的生成策略尤其重要。一个分布式ID,首先要保证以下几个特点:
- 全局唯一性(分布式数据库,可能造成序列的重复)
- 有序性,可根据一定的特性进行排序
- 信息安全,如果ID是连续的,比如
1,2,3,4...
,那么第三方进行数据爬取的任务就非常容易,特别是订单号这种私密性较高的数据,禁止使用有明显规律的ID生成策略 - 高可用性
- 高并发
下面就针对以上几个方式,分析常见的几种分布式ID策略,并比较。
集群数据库保证seq的唯一性
分布式场景下,热点模块数据可能过多(比如订单库),导致一台数据库无法存储全量数据:
![](https://i-blog.csdnimg.cn/blog_migrate/0b5dbc7bc546a5eaac323ede63ae0266.png)
那么在生成订单时,如果使用sequence在每个库中生成订单ID,很容易造成订单ID的重复。首先想到的最简单的方式就是使用步长,错开id:
-- 数据库1的OrderId生成策略
create sequence seq_order_id start 1 step by 2;
-- 数据库2的OrderId生成策略
create sequence seq_order_id start 2 step by 2;
这种方式较为简单,其数据的唯一性是有保证的。但是如果再扩充一台机器,可能要面临数据迁移的情况,相对来说不是很灵活。当然,你可以将step增大,为扩充预留Id值。
使用全局唯一数据库生成ID
这种方式也较为简单,就是再整个环境中搭建一台专门用于生成ID的数据库:
![](https://i-blog.csdnimg.cn/blog_migrate/6a54aea8d21f2bebabe87a409ccc3070.png)
各个服务会从ID数据库中取出自己所需要的ID数据。ID数据的生成策略也有多种:
- 预先插入一堆唯一ID到ID数据库中,供其他服务读取使用
- ID数据库使用SEQ生成
全局唯一数据的缺点也很明显:
- 很难做到高可用,有单点故障
- 性能较低,扩展性较差
- 预插方式需要维护程序,并且每个服务的ID生成策略可能都不同
- 有多种数据库产品,每个数据库产品的相关操作可能不同,不方便程序扩展
使用UUID生成ID
Universally Unique Identifier, 通用唯一识别码。UUID在同一时间中生成的数字在所有机器中都是唯一的。它主要由以下几个部分组成:
- 当前日期时间
- 时钟序列
- 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。标准的UUID格式为:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
(8-4-4-4-12)
UUID发生碰撞的概率是极低的,他有以下几个特点:
- 唯一性好,全球唯一
- 使用简单
- 不可读
- 生成性能好,无需网络开销,数据库底层就可完成生成操作
- 传输性能差,较长,查询效率也较低
- 无序
- 有时间回拨问题(服务器时间如果回拨,产生的ID可能相同)
在Java中,UUID的生成也较为简单:
UUID.randomUUID();
使用Zookeeper的临时节点
Zookeeper具有高可用、原子性的特点,用于作为唯一的ID生成策略也是较为合适的:
- 有序,使用临时序号节点
- 使用节点的数据版本号
- 高并发下性能较差
使用Redis的incr
由于Redis的原子性以及高性能,使用Redis作为ID生成策略是很多公司采用的解决方案,主要采用incr
和incr by
,它具有以下特点:
- 高性能
- 数字ID,有序,分页较为方便
- 局部有序,全局无法保证有序
使用原生的分布式数据库
原生的分布式数据库,比如MongoDB,默认提供分布式ID的生成策略,比如:ObjectId()
Twitter snowflake
雪花算法,是Twitter开源分布式ID生成算法,结果是一个Long型的ID。
- 性能较好
- 单机上是递增的
- 严重依赖时钟,禁止时钟回拨