背景
在分布式系统中,部分业务场景以及技术场景下,往往需要对大量的数据或者消息或者请求做唯一标识。如订单系统订单号,处理请求时防止重复请求幂等过滤标识,业务分表之后,依旧通过唯一的业务id标识等等
满足业务场景,ID有何要求:
1、全局唯一性,唯一ID需满足唯一不重复
2、趋势递增:如将生成的ID用作数据库主键时,由于MySql的innodb存储引擎使用聚集索引,所以当数据表主键为趋势递增数据时,插入数据时,调整较小。提高写入性能。
3、单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
4、信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。
在满足上述条件的情况下,要设计一个公共的生成唯一ID的服务。还需要做到什么?
假如多个业务都依赖该ID服务,则需要保证该服务的高可用性以及SLA
常见实现方案
1、UUID
UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,UUID是基于当前时间、计数器(counter)和硬件标识(通常为无线网卡的MAC地址)等数据计算生成的。可以在分布式环境下,高效生产唯一的ID。
优点:性能高,本地生成,不依赖网络
缺点:所占空间大,且无规则。不适用于作MySql主键(聚集索引,插入时调整较大)
2、snowflake (雪花算法)
snowflake 算法,通过划分 64bit 空间来生产ID。将64bit划分成多段,分开来标示时间戳,机器信息等。
如:
这种方式的优缺点是:
优点:
-
毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
-
不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
-
可以根据自身业务特性分配bit位,非常灵活。
缺点:
-
强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
3、Mysql自增
不推荐,一般高并发场景下,系统瓶颈都在DB,业务通过DB获取唯一ID,效率不高。
4、利用DB + 服务内部自增
第三种方案是每次获取ID,都需要查询且更新一次DB,对DB压力比较大。要满足唯一且趋势递增需求,可在一定区间内,只更新一次DB,而在区间内,通过jvm或者redis等满足自增。
数据表设计:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,无意义 |
| biz_type | int | 业务类型 |
| step | int | 每次更新数据库的步长 |
| max_id | bigint | 当前biz_type所分配的最大ID |
| update_time | datetime | 更新时间 |
使用流程:
(1)根据biz_type更新max_id:
update #table set max_id = max_id + step where biz_type = #biz_type; select max_id from #table where biz_type = #biz_type
每次更新max_id 增加步长,假如,step为1000,则大致会在生成1000个ID之后,再写一次DB。所以数据库压力不大。可根据实际生产场景,更改step大小。
(2)获取max_id之后,服务内部,如果是单机部署,可以通过AtomicLong 或者 锁的方式自增。但现在业务一般都是多机部署,则可添加机器号,或者最优的方式是通过redis。redis提供 incr。类似分布式环境下的AtomicLong.incrAndGet
jedisClient.incr("key");
(3)当步骤2中采用的是jvm内部自增时,服务重启或者宕机恢复后。则业务需要重新更新并获取max_id。
优点:
-
ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。
-
容灾性高:业务服务内部有号段缓存,即使DB宕机,短时间内正常对外提供服务。
-
可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。
缺点:
-
ID号码不够随机,能够泄露发号数量的信息,不太安全。
-
TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tp999数据会出现偶尔的尖刺。
-
DB宕机会造成整个系统不可用
该方案能满足大多数场景需求。对于内部服务分表后获取ID,标示唯一请求等,均满足需求。
对于缺点1,不利于 用作生成订单号 场景;
对于缺点2,偶尔抖动,不适用于高并发高性能要求场景;
对于缺点3,假如是生成ID的DB与业务DB是同一个,则影响不大,因为业务系统也挂了。假如是单独的DB,则需要保证DB的高可用
参考:https://tech.meituan.com/2017/04/21/mt-leaf.html