订单状态流程
订单状态流转图:
订单状态:
服务单状态:
分库分表
分库方案
设计三个数据库,根据用户id哈希,分库表达式为:db_用户id % 3
在项目后期虽然存在数据迁移的问题,但总体来说很长一段时间不需要考虑
分表方案
根据订单范围分表,0—1500万落到table_0,1500万—3000万落到table_1,依次类推。根据范围分表不存在数据库迁移问题,方便系统扩容。
分表表达式:orders_${(int)Math.floor(id % 10000000000 / 15000000)}
整体方案
分库方案:
设计三个数据库, 根据服务人员id哈希,分库表达式:jzo2o-orders-${serve_provider_id % 3}
分表方案:
根据订单范围分表,0—1500万落到table_0,1500万—3000万落到table_1,依次类推。
orders_serve_${(int)Math.floor(id % 10000000000 / 15000000)}
创建订单
订单号生成规则
本项目订单号生成规则如下:
19位:2位年+2位月+2位日+13位序号
例如:2311011000000000001
实现方案:
1、前6位通过当前时间获取。
2、后13位通过Redis的INCR 命令实现。
价格计算规则
分两种情况:
未使用优惠券:
实际支付金额=订单总金额
订单总金额=服务单价 * 数量
使用优惠券:
实际支付金额=订单总金额-优惠金额。
订单总金额=服务单价 * 数量
优惠金额:调用优惠券服务接口进行计算,分两种情况:满减和折扣。
优惠券核销(分布式事务)🌟
下单与优惠券核销组成分布式事务,进行分布式事务控制,根据需求分布式事务控制满足AP,即强调的是可用性,允许出现短暂不一致,最终达到数据一致性。
Seata控制分布式事务:
Seata事务管理中有三个重要的角色:
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交互注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
- 事务管理器(TM)开启全局事务
- 事务管理器(TM)请求RM执行分支事务
下单RM:先向事务协调器(TC)注册下单分支事务,并执行sql(记录undo log),并向TC报告事务状态
undo log:用于回滚事务使用,下单sql是插入记录,根据undo log会生成反向sql删除记录,通过执行反向sql实现回滚事务。
优惠券RM:先向事务协调器(TC)注册优惠券核销分支事务,并执行sql(记录undo log),并向TC报告事务状态 - 事务管理器(TM)请求TC提交全局事务
- TC检查分支事务状态,如果发现都成功则请求每个RM提交事务(删除undo log),如果发现其中有失败记录则请求每个RM回滚事务,RM回滚事务的方法是通过undo log执行提交事务的反向sql。
防止订单重复提交
-
前端防重复提交
禁用提交按钮: 在用户点击提交按钮后,立即将按钮禁用,防止用户多次点击。
显示加载中状态: 提交按钮点击后,显示加载中状态,防止用户再次点击。 -
后端防重复提交
使用分布式锁:以用户id+服务id作为分布式锁锁定下单接口10秒,10秒内该用户不能再次下同一个服务的订单。
订单支付
查询订单
单条订单查询优化
根据订单Id查询缓存信息,先从缓存查询如果缓存没有则查询快照表的数据然后保存到缓存中。缓存设置了过期时间是30分钟。
当订单状态变更,此时订单最新状态的快照有变更,会删除快照缓存,当再次查询快照时从数据库查询最新的快照信息进行缓存。
用户端订单列表优化🌟
使用滚动查询,一次查询指定数量的记录,不用进行count查询,省去count查询的全标扫描消耗。
首先在订单表使用排序字段sort_by作为滚动ID,滚动ID是一种递增的序列号,是按开始服务时间+订单生成时间计算生成。
然后根据查询条件查询符合条件的订单ID,这里使用覆盖索引优化的方法。最后对订单信息进行缓存,使用redis的hash结构存储订单信息。
保证缓存一致性:
当用户创建新订单删除该用户在redis的hash结构中的数据。
当用户的 订单状态发生变更则删除redis中hash结构中的订单数据库。
sort_by bigint null comment '排序字段,serve_start_time秒级时间戳+订单id后六位',
运营端订单列表优化
运营端订单列表由于访问量不大这里无需使用缓存,实现方案是首先通过覆盖索引查询符合条件的订单ID,再根据订单ID查询聚集索引拿到数据。
我们根据查询条件创建的联合字段索引是非聚集索引,先从非聚集索引中查询到符合条件的ID,再根据订单ID从聚集索引中查找数据。
取消订单
- 根据上边的需求,下边分析订单取消功能。
取消待支付的订单:
最终的结果是更改订单状态为已取消,因为没有支付所以不涉及退款。
消派单中的订单:
用户和运营人员都可以取消订单,订单状态改为已关闭。
到达服务时间还没有派单成功系统自动取消。
取消订单后自动退款。
删除抢单池记录。
- 用户权限
普通用户:可取消待支付、派单中、待服务的订单。
运营人员:可取消除待支付状态下的所有订单。
策略模式🌟
针对不同场景下取消订单的逻辑定义一个策略类,不同场景只需要修改或者增加策略实现类即可。
/**
* 订单取消策略接口
*
* @author itcast
* @cre