不管是普通场景的下单,还是秒杀场景的下单,对订单中心来说,都是下单,关键是要能支撑秒杀瞬间大量的下单请求。本文探讨一下通用的订单中心架构,主要从服务划分、下单请求处理流程、核心表分库等方面来介绍,不区分普通下单还是秒杀下单,系统架构设计做好了,有秒杀活动时,无非进行一些扩容、限流、降级等手段即可应对。
我不希望整个订单中心就是一个巨大的单体服务,也不希望是太细的微服务,我希望订单中心是合理的“中服务”的组合。
可针对不同场景对服务进行扩容,比如订单搜索请求量比较大,就适当增加订单搜索服务的实例数量;消费速度慢,可针对订单消费者服务进行优化,调整服务实例数量。
上面划分的每一个服务都是独立部署运行的服务。
服务 |
说明 |
order-core(订单核心服务) |
负责订单的业务处理,直接和 DB 交互 |
order-search(订单搜索服务) |
负责订单索引的维护和搜索,直接和 ES 交互 |
order-job(订单调度服务) |
订单超时取消等调度任务 |
order-consumer(订单消费者服务) |
消费订单相关消息,如下单消息、订单索引更新消息 |
order-manage(订单管理后台系统) |
订单管理后台,数据来源于 ES 和从库 |
提交订单服务调用关系:
后台服务调用关系:
订单搜索或查看订单详情服务调用关系:
订单调度服务调用关系:
下单请求通过 MQ 异步化处理,下单处理结果存入 redis,前端轮询下单结果。
步骤 1:提交订单说明
步骤 2:消费下单请求说明
步骤 3:根据订单号查询轮询下单结果说明
1. 以订单主表和订单明细表为例进行分库设计,假如按 32 个库进行分库。
2. 订单主表和订单明细表通过订单号进行关联。
3. 分库要求:
4. 按照以上分库要求,做出以下分库设计
5. 分库设计图:
库存设置到 redis 中,已 skuId 为 key,变化的数量为值,如:
Redis 如何与数据库中的库存保持一致:
/**
* 预占库存 伪代码
* @param orderNo 订单号
* @param skuId sku 标识
* @param quantity 预占数量
*/
boolean preOccupy(String orderNo, String skuId, int quantity) {
boolean isPreOccupySuccess = false;
int value = redis.decrby(skuId, quantity);
if (value >= 0) {
// 库存充足
// 生成库存预占流水记录
//(关键字段:orderNo,skuId,quantity,state(0-预占中;1-已扣减;2-已释放),timeout(超时时间)
isPreOccupySuccess = true;
} else {
// 库存不足,返还刚才预占的库存
redis.incrby(skuId, qunatity);
}
return isPreOccupySuccess;
}
数据库的库存数量禁止覆盖更新!
扣减库存伪 SQL:update stock set stock_num = stock_num - 变化的值 where sku_id = 10086
关于释放库存
对一些释放异常的情况,可由库存中心调度服务,找出库存预占流水状态为预占中且预占超时的记录,根据订单号向订单中心确认该订单号的库存是已扣减还是已释放,再进行相应业务处理。
除了以上大的方面设计,分布式事务、幂等、补偿、压测……这些点是大家在设计系统时都需要考虑的,不在本文讨论范围。