文章目录

  • 一、订单号特性
  • 二、业界方案
  • 方案1:数据库自增ID(不推荐)
  • 方案2:UUID(不推荐)
  • 方案3:分布式唯一ID
  • 三、因子分表法
  • 3.1 方案设计
  • 3.2 因子分表法 VS 分布式唯一ID


一、订单号特性

  1. 唯一性必要):每个订单号全局唯一代表一个订单;
  2. 安全性必要):订单号不能透露订单量、运营规模等业务信息(数据安全性);
  3. 高性能:订单号的创建成本越低越好;
  4. 扩展性:能够较好的支撑后续业务发展变大带来的分库分表、订单长度扩展等场景;

二、业界方案

方案1:数据库自增ID(不推荐)

思路: 基于数据库主键ID自增的特性生成订单号,不推荐此方案的原因有如下几点:

  1. 不安全:订单号能够反映出系统的订单量,存在业务数据外露的风险;
  2. 性能问题:需要数据库插入一条记录获取主键ID,性能受限于数据库,且存在单点;
  3. 扩展性:不利于后续的分库分表(见方案3);

方案2:UUID(不推荐)

思路: JDK自带UUID工具类,生成UUID足够快速、方便,业界不使用UUID作为分布式唯一ID,或者订单号的原因在于:

  1. 性能问题:在订单表中,订单号为主键或者唯一索引,UUID是无序的(36位字符串),如果作为数据库主键,在InnoDB引擎下,UUID的无序性会引起数据位置频繁变动,严重影响数据库性能;(UUID生成是很快的,作为订单号影响的是订单表的写性能)
  2. 扩展性:不利于后续的分库分表(见方案3);

方案3:分布式唯一ID

 分布式唯一ID生成器,主要有类snowflake方案和分库分表形式的数据库自增ID方案。目前分布式唯一ID方案已经很成熟,能做到高性能、高可用、递增等。在订单场景下,主要问题在于不利于分库分表,如下图。将分布式唯一ID作为订单号不利于分库分表原因如下:

  1. 无法满足多维护查询,当查询条件中没有订单id时,会全表扫描,性能低下;(全表扫描
  2. 为了避免全表扫描,可以通过DB或者缓存存储订单号与用户ID、商户ID的映射关系,每次在用户ID和商户ID维度查询时,先通过映射关系查询到对应的订单ID,然后路由到对应的分表;(多一次查询

电商订单号生成 java 订单号代码设计_数据库

三、因子分表法

3.1 方案设计

思路: 牺牲通用性,根据订单场景将影响分库分表的业务因子融入到订单号中,比如用户id,根据订单号分库分表路由时,通过位移对业务因子取模路由即可,实现订单号和业务因子路由的一致性,如下图所示(需要哪些因子,占用几位看实际情况)。这样做的好处是:

  1. 趋势递增:时间戳在高位,利用时间的递增性,保证订单号趋势递增;
  2. 高性能:没有第三方依赖,且基本零消耗;
  3. 分库分表:分库分表因子融入到了订单号中,避免了全表扫描或者多一次查询;
  4. 唯一性:同一用户在同一店铺的同一时间内下多个单才有可能重复,对于正常用户行为是不可能出现的;

  以用户的订单库为例进行说明,订单号由三部分组成:时间戳+分库分表因子+随机数。用户因子为用户ID哈希后的某10位,这样无论是按照订单ID还是用户ID查询,最终都会用户因子分库分表,从而路由的分表也会一致,如下所示:

电商订单号生成 java 订单号代码设计_分表_02

3.2 因子分表法 VS 分布式唯一ID

  1. 分布式唯一ID是一种通用的基础服务,为各业务系统提供唯一ID,不包含任何业务属性;(通用方案
  2. 订单号是一种特殊的分布式唯一ID,为了便于业务处理,通常会结合一些业务信息来生成;(结合业务场景的个性化方案)

参考:

  1. Leaf——美团点评分布式ID生成系统
  2. 老司机设计了一个能扛住双11并发的订单号生成方案,同事说“666”
  3. 单KEY业务,数据库水平切分架构实践 | 架构师之路