目录
一、ID结构
淘宝订单ID(例如:266807390250123456)包含三个核心部分:
- **时间序列部分 (26680739025):**标识订单生成的时间序列。
- **订单类型标识 (0):**用于区分不同类型的订单。
- **用户基因段 (123456):**嵌入的用户标识信息(用户ID的后6位)。
二、业务需求
淘宝的核心特性就是在订单号的后6位嵌入了用户基因,那么为什么需要这么做呢?
这是为了双维度查询难题:
传统方案的痛点:
- **userId分片:**虽然用户查询自己的订单效率极高,但噩梦在于按 orderId 查询,因为 orderId 本身不含路由信息,必须扫描所有分片,在数千分片下性能灾难。
- **orderId分片:**按orderId查询高效,但用户查自己订单又变成全分片扫描。
或许可以完全把当前订单表复制一遍改为冗余订单id分片表,但这样一来成本翻倍不说,每次下单需强事务保证写入两个库,高并发下成为瓶颈,还增加了维护的复杂度。
所以淘宝就通过将用户id的后6位作为基因段嵌入订单id里,以这个基因段为分片键,通过查询用户全部订单时就取userId后6为转化为基因进行路由;查询单个订单就订单号后六位基因段进行路由即可。
这种方法巧妙地将用户标识融入订单ID本身,使得无论是基于userId的批量查询,还是基于orderId的单点查询,都能高效地定位到目标分片,避免了全表扫描。同时,无需维护额外的冗余。
其实淘宝订单号整个可以视作snowflake算法生成ID的一个变种,引入用户基因将机器ID位的作用大幅度弱化,因为同一毫秒内用户生成多个订单的可能性几乎为0,就算有也可以通过序列号位进行区分。省去了传统的麻烦的机器ID分配流程。
所以淘宝订单号就是用用户基因取代了机器ID位,然后将序列号位前移,再引入了订单型标识结合而成的。
三、具体实现
核心在于从用户基因提取用户id以及将用户id转化为用户基因的两个方法。
这里我们不对这两个方法做详解,仅讲解基本流程:
public class TaobaoOrderIdGenerator { // 基因位数 private static final int GENE_BITS = 6; // 最大重试次数 private static final int MAX_RETRIES = 5;
// 布隆过滤器 private final RBloomFilter<String> geneBloomFilter;
public String generate(long userId) { int retries = 0; String orderId = null; long userGene = 0; // 使用随机盐值增强安全性 long salt = ThreadLocalRandom.current().nextLong();
while (orderId == null && retries < MAX_RETRIES) { // 提取用户基因 userGene = extractGene(userId, salt);
// 布隆过滤器快速冲突预检 if (isGeneConflict(userGene)) { // 改变盐值扰动哈希结果,尝试生成新基因 salt = mutateSalt(salt); retries++; // 重试 continue; }
// 生成唯一ID long uniquePart = generateSnowflakeId(userGene);
// 组合最终订单号 orderId = formatOrderId(uniquePart, userGene); }
if (orderId == null) { throw new IllegalStateException("订单号生成失败,重试次数耗尽"); }
// 将成功生成的基因添加到布隆过滤器 geneBloomFilter.add(String.valueOf(userGene)); // 存储到映射表中,redis/DB storeGeneUserIdMapping(userGene, userId);
return orderId; }}由于不能直接将用户id暴露给外界,所以可以采用哈希 + 盐值来生成基因段,但是考虑到哈希碰撞的可能性,我们可以对使用的哈希函数进行优化,将碰撞的可能性降低,同时引入布隆过滤器进行筛选拦截。
关于snowflake算法的生成可以阅读下面这篇博客: 从业务场景到知名企业开源框架全面解析分布式ID生成方案-CSDN博客https://blog.csdn.net/2401_88959292/article/details/150607790?spm=1001.2014.3001.5501
因为用户基因段是userId的单向哈希加盐,无法通过计算反向得到userId。当需要根据orderId查询订单详情时,系统需要知道这个订单属于哪个userId。
所以对于用户基因提取用户id的方法我们一般会使用映射表,在生成订单号成功后将用户基因以及其对应的用户id存储到或者DB中,提取用户id时就直接拿用户基因查询即可。
码文不易、留个赞再走吧
原文链接: 破局双维度查询难题:淘宝订单ID的用户基因设计奥秘 作者: Yilena
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时










