微服务架构下为保证服务间数据强一致性,不得不依赖分布式唯一id来串联整个流程
生成ID的方法有很多,适应不同的场景、需求以及性能要求。我们常常为这个问题而纠结最终方案。下面就介绍一些常见的ID生成策略!!
常见算法
1. uuid
420995ed-d684-4c7b-a59f-c0ca708907a1 2e156d90-dc81-48cb-9313-9e6a9adb5218 9ce1fd17-603d-4984-bc69-c795852b0546
优点
性能非常高,本地生成,没有网络消耗
缺点
- 不易存储,16字节128位,通常以36位长度的字符串表示,
- 信息不安全,给予mac地址生成UUID 的算法可能会造成mac地址泄漏,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置
- 用作mysql索引时,无序性会导致索引位置频繁变动,严重影响性能
2. snowflake
以划分命名空间来生成id 的一种算法,这种方案把64-bit分别分成多端,用来标识 机器、时间等
41-bit的时间可以表示(1L<<41)/(1000L360024*365)=69年的时间,10-bit机器可以分别表示1024台机器,12个字增序列 可以表示2^12个id,理论上 qps 可达 109.6w/s ,这种分配方式可以保证任一台机器在任何毫秒内生成的id 都是不同的。
优点:
- 毫秒数在高位,自增序列在低位,整个id 都是趋势递增的
- 不依赖任何第三方系统,以服务的方式部署,稳定性高,性能也不错。
- 可以根据自身特性 分配 bit 位,比较灵活。
缺点:
- 强烈依赖机器时钟,如果机器时钟回拨,会导致发号重复。
3. 数据库生成
利用自增id生成
优点:
非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。单调自增
缺点:
- 强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的提高可用性,但主从切换时的不一致可能会导致重复信号。
- id 发号性能瓶颈限制在单台mysql的读写性能。对于mysql性能问题,可以部署多台机器,每台机器设置不同的初始值,以及步长。假设有两台机器,设置步长为2 ,server1的初始值为1.(1,3,5,7,9)。server2的初始值为2.(2,4,6,8,10)。这样貌似能满足性能要求,但是将来在做扩容将恐怖如斯。
leaf 方案
本文的终极方案
Leaf是美团推出的一个分布式ID生成服务,这个名字是来自德国哲学家、数学家莱布尼茨的一句话:>There are no two identical leaves in the world > “世界上没有两片相同的树叶”。
Leaf在设计之初就秉承着几点要求:
- 全局唯一,绝对不会出现重复的ID,且ID整体趋势递增。
- 高可用,服务完全基于分布式架构,即使MySQL宕机,也能容忍一段时间的数据库不可用。
- 高并发低延时,在CentOS 4C8G的虚拟机上,远程调用QPS可达5W+,TP99在1ms内。
- 接入简单,直接通过公司RPC服务或者HTTP调用即可接入。
Leaf-segment号段模式
CREATE TABLE `leaf_id` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID', `max_id` bigint NOT NULL DEFAULT '0' COMMENT '当前最大的ID',`step` int NOT NULL DEFAULT '1000' COMMENT '步长', `biz_tag` varchar(100) NOT NULL COMMENT '业务信息', `description` varchar(100) DEFAULT NULL COMMENT '描述', `version` bigint NOT NULL DEFAULT '0' COMMENT '版本号', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `biz_tag` (`biz_tag`), KEY `leaf_id_biz_tag_IDX` (`biz_tag`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='leaf分布式id生成器';
基于号段模式采用了预分发的方式生成ID,在服务内存中维护一个固定长度的 id-list,
id由内存分发,所以可以做到很高效。至于数据持久化问题,leaf_server每次去DB拿固定长度的id-list,然后把最大的ID持久化下来(max_id),并非每个id都做持久化,仅仅持久化一批id中最大的那一个。server中 id号段分配至阈值时,通过这一段脚本
begin UPDATE table SET max_id=max_id+step WHERE biz_tag=xxxSELECT tag, max_id, step FROM table WHERE biz_tag=xxx commit
去申请新的号段区间维护在内存中
大家读到这里可能会想
咦?我这是分布式微服务架构啊,leaf_server 不是存在单点问题吗?如果leaf_server 挂了,整个服务都不可用了呀!!!但其实 leaf_server 也可水平扩展部署多台在不同的机房, 每一个server维护不同的号段 以支持服务的高可用
- Leaf Server 1:从DB加载号段[1,1000]。
- Leaf Server 2:从DB加载号段[1001,2000]。
- Leaf Server 3:从DB加载号段[2001,3000]。
mysql 也可以做主从集群,由于号段维护在server 内存中,可撑过mysql 主从切换时的db不可用
总结
本文主要介绍了分布式id 的生成方案演进过程。
最后引出美团的leaf方案,基于Segment缓存模式的设计非常巧妙,保证了服务的性能以及高可用。
基于mysql的行锁,保证多leaf服务并发修改同一条数据场景下不会分配到同一号段,设计非常巧妙。但也有一定的缺点,就是 某台 leaf_server 宕机时,可能会造成号段不连续,某段id丢失,但整体还是趋势递增。
本文介绍并非全面,只是基于leaf_id 的核心思想展开叙述!!!