在公司去Oracle的要求下,需要对原有系统进行一些改造优化,由此引出了对分布式全局ID的一些思考。在平常的开发工作中可能不会对id做考量,一般都是使用数据库自带的自增主键或是sequence函数来生成id,但是随着业务的发展,系统会越来越大,会使用一些新的技术,例如分表分库,大数据,ES全文检索等。这时数据主键就越发变得重要,在分布式系统中,对于核心业务表的主键id设计需要考虑以下几点
- 全局唯一:这里说的全局,可以是整个分布式系统,也可以是整个公司中,或是全球唯一。防止出现撞库
- 趋势递增:多数关系型数据库索引基于B+树,尽可能的有序,可以更多的保证顺序读写,避免随机读写
- 信息安全:在某些场景下,主键id的设计可能会加入业务字段,因此要保障信息安全
业界如支付宝,淘宝,美团,叮咚等,均采用 时间戳+userid后几位+随机数 或 唯一数+userid后几位。其原因在于随着业务发展,采用了分库方式解决数据库IO,线程数压力。结合业务场景大致均分为查询某人订单,通过某算法使对某个用户的订单创建和查询均操作到固定某个库(即因子分库法)。下面总结了几种常见分布式id生成方式
UUID
构成:当前日期和时间+时钟序列+全局唯一机器识别码(网卡)
优点:
- 实现简单,本地生成,基本不会有性能问题
- 全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对
缺点
- 没有排序,无法保证趋势递增。
- UUID往往是使用字符串存储,长度为32位的字符串,过长,从性能考虑不适合作主键
数据库序列号
构成:Oracle使用sequence,mysql使用自增字段
优点:
- 简单,代码方便,性能可以接受。
- 数字连续自增
缺点
- 不适合分库系统,分库后ID会重复
- 不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。
- 在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。
Snowflake雪花算法
构成:整体64位,使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0
优点
- 简单高效,生成速度快
- 时间戳在高位,自增序列在低位,整个ID是趋势递增的,按照时间有序递增。
- 灵活度高,可以根据业务需求,调整bit位的划分,满足不同的需求。
缺点
- 依赖机器的时钟,如果服务器时钟回拨,会导致重复ID生成。某台回拨N分钟,则该实例将N分钟内无法取号
- 在分布式环境上,每个服务器的时钟不可能完全同步,有时会出现不是全局递增的情况。
- 如采用中心化取号服务,对外提供服务,业务系统每次生成业务编号增加一次远程调用
Redis取号
构成:利用redis的自增函数,单线程特性,保障取号不会重复
优点
- 趋势递增
- 不依赖于数据库,灵活方便,且性能优于数据库。
缺点
- 如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
- Redis是单线程的,若造成阻塞,则会引发高并发问题,需要处理好集群与主从关系
取号服务
提供取号基础服务,底层为数据库sequence/自增长,该服务提供对数据库的操作。由client接入取号SDK,在SDK内缓存从取号基础服务获取的号段,当号段使用完,再次请求取号基础服务获取号段,同美团Leaf取号方案
优点
- 性能好
- Client端SDK获取号段,对业务系统侵入小
缺点
- 依赖数据库,号段由数据库生成并在client端缓存,当数据库crash,短时间内不影响业务
- 号段用完需要调用取号服务一次,增加调用链路长度
- 当业务系统重启,未使用完号段丢失,造成跳号
各公司取号规则
淘宝订单号:唯一ID(13位数字)+userID后6位,共19位
支付宝订单号:YYYYMMDD+userID+唯一ID(具体构成不明),共21位数字
美团酒店订单号:snowflake19位数字,美团内部不同业务板块取号规则不同,如美团外卖订单号采用17位数字,美团电影,买菜订单11位数字
叮咚买菜订单:YYYYMMDD+userID(9位)+4位随机数,共19位数字
壹钱包订单号:1位大写字母+14位数字(类似产品编号)+YYYYMMDD+8位数字
平安银行基金:32位UUID