文章目录
- 一、订单号特性
- 二、业界方案
- 方案1:数据库自增ID(不推荐)
- 方案2:UUID(不推荐)
- 方案3:分布式唯一ID
- 三、因子分表法
- 3.1 方案设计
- 3.2 因子分表法 VS 分布式唯一ID
一、订单号特性
- 唯一性(必要):每个订单号全局唯一代表一个订单;
- 安全性(必要):订单号不能透露订单量、运营规模等业务信息(数据安全性);
- 高性能:订单号的创建成本越低越好;
- 扩展性:能够较好的支撑后续业务发展变大带来的分库分表、订单长度扩展等场景;
二、业界方案
方案1:数据库自增ID(不推荐)
思路: 基于数据库主键ID自增的特性生成订单号,不推荐此方案的原因有如下几点:
- 不安全:订单号能够反映出系统的订单量,存在业务数据外露的风险;
- 性能问题:需要数据库插入一条记录获取主键ID,性能受限于数据库,且存在单点;
- 扩展性:不利于后续的分库分表(见方案3);
方案2:UUID(不推荐)
思路: JDK自带UUID工具类,生成UUID足够快速、方便,业界不使用UUID作为分布式唯一ID,或者订单号的原因在于:
- 性能问题:在订单表中,订单号为主键或者唯一索引,UUID是无序的(36位字符串),如果作为数据库主键,在InnoDB引擎下,UUID的无序性会引起数据位置频繁变动,严重影响数据库性能;(UUID生成是很快的,作为订单号影响的是订单表的写性能)
- 扩展性:不利于后续的分库分表(见方案3);
方案3:分布式唯一ID
分布式唯一ID生成器,主要有类snowflake方案和分库分表形式的数据库自增ID方案。目前分布式唯一ID方案已经很成熟,能做到高性能、高可用、递增等。在订单场景下,主要问题在于不利于分库分表,如下图。将分布式唯一ID作为订单号不利于分库分表原因如下:
- 无法满足多维护查询,当查询条件中没有订单id时,会全表扫描,性能低下;(全表扫描)
- 为了避免全表扫描,可以通过DB或者缓存存储订单号与用户ID、商户ID的映射关系,每次在用户ID和商户ID维度查询时,先通过映射关系查询到对应的订单ID,然后路由到对应的分表;(多一次查询)
三、因子分表法
3.1 方案设计
思路: 牺牲通用性,根据订单场景将影响分库分表的业务因子融入到订单号中,比如用户id,根据订单号分库分表路由时,通过位移对业务因子取模路由即可,实现订单号和业务因子路由的一致性,如下图所示(需要哪些因子,占用几位看实际情况)。这样做的好处是:
- 趋势递增:时间戳在高位,利用时间的递增性,保证订单号趋势递增;
- 高性能:没有第三方依赖,且基本零消耗;
- 分库分表:分库分表因子融入到了订单号中,避免了全表扫描或者多一次查询;
- 唯一性:同一用户在同一店铺的同一时间内下多个单才有可能重复,对于正常用户行为是不可能出现的;
以用户的订单库为例进行说明,订单号由三部分组成:时间戳+分库分表因子+随机数。用户因子为用户ID哈希后的某10位,这样无论是按照订单ID还是用户ID查询,最终都会用户因子分库分表,从而路由的分表也会一致,如下所示:
3.2 因子分表法 VS 分布式唯一ID
- 分布式唯一ID是一种通用的基础服务,为各业务系统提供唯一ID,不包含任何业务属性;(通用方案)
- 订单号是一种特殊的分布式唯一ID,为了便于业务处理,通常会结合一些业务信息来生成;(结合业务场景的个性化方案)
参考: