分布式ID-ID生成器解决方案

1,679 阅读7分钟

欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈

在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。概括下来,那业务系统对ID号的要求有哪些呢?

  1. 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
  2. 趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
  3. 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
  4. 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。
  5. 带时间:ID里面包含时间,一眼扫过去就知道哪天的交易。

上述123对应三类不同的场景,3和4需求还是互斥的,无法使用同一个方案满足。

同时除了对ID号码自身的要求,业务还对ID号生成系统的可用性要求极高,想象一下,如果ID生成系统瘫痪,整个美团点评支付、优惠券发券、骑手派单等关键动作都无法执行,这就会带来一场灾难。

由此总结下一个ID生成系统应该做到如下几点:

  1. 平响:平均延迟和TP999延迟都要尽可能低;
  2. 高可用:可用性5个9;
  3. 高QPS

1. 系统时间毫秒数

我们可以使用当前系统时间精确到毫秒数+业务属性+用户属性+随机数+…等参数组合形式来确保ID的唯一性. 缺点:ID的有序性难以保证,要保证有序性就要依赖数据库或者其他中间存储媒介。

2. UUID

UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有5种方式生成UUID,详情见IETF发布的UUID规范 A Universally Unique IDentifier (UUID) URN Namespace**

优点:

  • 性能非常高:本地生成,没有网络消耗。

缺点:

  • 不包含时间、业务数据可读性太差了,而且也不能ID的有序递增

  • 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。

  • 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

  • ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用:

    ① MySQL官方有明确的建议主键要尽量越短越好,36个字符长度的UUID不符合要求。

    All indexes other than the clustered index are known as secondary indexes. In InnoDB, each record in a secondary index contains the primary key columns for the row, as well as the columns specified for the secondary index. InnoDB uses this primary key value to search for the row in the clustered index.*** If the primary key is long, the secondary indexes use more space, so it is advantageous to have a short primary key***.

    对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。

这是一种简单的生成方式,简单,高效,但在一般业务系统中我还没见过有这种生成方式。

3. 数据库自增ID

我们都知道为数据库主键设置自增序号,以一定的趋势自增,以保证主键ID的唯一性。

这个方案很简单,但最主要的问题在于依赖数据库本身,这就无形增加了对数据库的访问压力和依赖,一旦对单库进行分库分表或者数据迁移就尴尬了。

所以,这也不是合适的ID生成方法。

4. 批量生成ID

一次按需批量生成多个ID,每次生成都需要访问数据库,将数据库修改为最大的ID值,并在内存中记录当前值及最大值。这样就避免了每次生成ID都要访问数据库并带来压力。

这种方案服务就是单点了,如果服务重启势必会造成ID丢失不连续的情况,而且这种方式也不利于水平扩展。

数据库自增ID 和 批量生成ID 详见:Leaf分布式ID-号段模式源码分析

5. 中间件

Redis的所有命令操作都是单线程的,本身提供像incr这样的自增命令,所以能保证生成的ID肯定是唯一有序的。

这种方式不依赖关系数据库,而且速度快。但系统要引入Redis这一中间件,增加维护成本,而且编码和配置工作量比较大。即使已经有了Redis组件,但生成ID的高频率访问对单线程的Redis性能势必也会造成影响。

还可以利用像Zookeeper中的znode数据版本来生成序列号,及MongoDB的ObjectId等,这种利用中间件的做法不是很推荐。

6. snowflake算法

image

如上图的所示,Twitter的snowflake算法下面几部分组成:

  • 41位的时间序列,精确到毫秒,可以使用69年
  • 10位的机器标识,最多支持部署1024个节点
  • 12位的序列号,支持每个节点每毫秒产生4096个ID序号,最高位是符号位始终为0。

这种方案性能好,在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。

而且这个项目在2010就停止维护了,但这个设计思路还是应用于其他各个ID生成器及变种。

7. Leaf

Leaf是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。

具体可以参考官网说明:

tech.meituan.com/MT_Leaf.htm…

8. Tinyid

tinyid 是滴滴开源的分布式id 生成服务,分布式id生成系统,简单易用、高性能、高可用的id生成系统。

官网:github.com/didi/tinyid

9. UidGenerator

UidGenerator是百度开源的分布式ID生成器,基于于snowflake算法的实现,看起来感觉还行。不过,国内开源的项目维护性真是担忧。

大家可以参考具体使用:

github.com/baidu/uid-g…

参考文章

分布式ID生成器的解决方案总结
Leaf——美团点评分布式ID生成系统