1. 外卖系统架构设计核心要点
外卖系统作为典型的O2O电商平台,其架构设计需要同时满足高并发、实时性和业务复杂性的要求。从技术角度看,一个完整的外卖系统主要由以下几个核心模块构成:
- 用户端(小程序/APP):负责用户交互和订单创建
- 商家端(PC/APP):处理接单和菜品管理
- 骑手端(APP):实现接单和配送跟踪
- 后台管理系统:处理运营数据和系统配置
- 核心业务系统:订单、配送、支付等核心服务
1.1 订单系统技术架构
订单系统作为外卖平台的核心中枢,其设计需要考虑以下几个关键因素:
分布式事务处理:订单创建涉及库存扣减、优惠券核销、支付预创建等多个操作,必须保证这些操作的原子性。实践中我们通常采用TCC(Try-Confirm-Cancel)模式:
java复制// TCC模式示例代码
public class OrderService {
@Transactional
public void createOrder(OrderDTO orderDTO) {
// Try阶段
couponService.tryLockCoupon(orderDTO.getCouponId());
inventoryService.tryReduceInventory(orderDTO.getItems());
paymentService.tryCreatePayment(orderDTO);
// Confirm阶段
orderMapper.create(orderDTO);
couponService.confirmLockCoupon(orderDTO.getCouponId());
inventoryService.confirmReduceInventory(orderDTO.getItems());
paymentService.confirmCreatePayment(orderDTO);
}
}
分库分表策略:订单数据通常采用用户ID哈希分片,同时考虑按时间维度进行冷热数据分离。对于MySQL 5.7中的parentid字段设计,这是处理订单拆单场景的常见方案:
sql复制CREATE TABLE `order` (
`id` bigint(20) NOT NULL,
`parentid` bigint(20) DEFAULT NULL COMMENT '父订单ID,拆单场景使用',
`user_id` bigint(20) NOT NULL,
`status` tinyint(4) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_parentid` (`parentid`),
KEY `idx_user_status` (`user_id`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.2 配送系统关键技术
配送系统的核心在于实时性和路线优化,主要包含以下技术组件:
实时位置追踪:
- 骑手端定期上报GPS坐标(通常15-30秒一次)
- 使用Redis GEO存储最新位置数据
- WebSocket实现实时位置推送到用户端
智能派单算法:
python复制def dispatch_order(order, riders):
"""
基于多种因素的派单算法
:param order: 订单信息(包含商家位置、配送地址等)
:param riders: 可用骑手列表
:return: 最优骑手
"""
candidates = []
for rider in riders:
# 计算骑手与商家的距离
distance_to_merchant = calculate_distance(rider.location, order.merchant_location)
# 计算预计配送时间
eta = estimate_delivery_time(distance_to_merchant)
# 综合评分(距离、评分、负载等)
score = 0.6*(1/distance_to_merchant) + 0.3*rider.rating + 0.1*(1/rider.load)
candidates.append((rider, score))
# 返回评分最高的骑手
return max(candidates, key=lambda x: x[1])[0]
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 订单状态机设计与实现
2.1 订单生命周期管理
外卖订单的典型状态流转如下:
code复制待支付 → 已支付待接单 → 商家已接单 → 制作中 → 待取餐 → 配送中 → 已送达 → 已完成
↘ 取消(用户) ↘ 拒绝(商家) ↘ 取消(系统)
在Uniapp等跨平台框架中实现订单状态切换栏时,需要注意:
javascript复制// Uniapp订单状态组件示例
<template>
<view class="order-status-bar">
<view
v-for="(status, index) in statusList"
:key="index"
:class="['status-item', {active: currentStatus >= status.value}]"
@click="switchStatus(status.value)"
>
{{status.label}}
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentStatus: 2, // 当前状态值
statusList: [
{label: '待支付', value: 1},
{label: '待接单', value: 2},
// ...其他状态
]
}
}
}
</script>
2.2 订单取消与异常处理
订单取消是外卖系统中最复杂的业务场景之一,涉及分布式事务回滚:
-
用户取消:
- 支付前取消:直接更新订单状态
- 支付后取消:触发退款流程,同时释放库存和优惠券
-
超时未接单自动取消:
java复制// 使用延迟队列处理超时订单
public void processTimeoutOrder(Order order) {
// 1. 检查订单状态是否仍为"待接单"
if (!"PENDING".equals(order.getStatus())) {
return;
}
// 2. 执行取消操作
order.setStatus("TIMEOUT_CANCELED");
orderRepository.save(order);
// 3. 触发退款
paymentService.refund(order.getPaymentId());
// 4. 释放库存
inventoryService.release(order.getItems());
}
3. 库存与订单的分布式一致性
3.1 库存扣减方案对比
| 方案 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 预扣库存 | 下单时先锁定库存,支付成功时确认扣减 | 避免超卖 | 可能造成库存冻结 | 高并发抢购 |
| 支付扣库存 | 支付成功时才扣减库存 | 库存真实 | 可能支付后才发现没货 | 库存充足场景 |
| 异步扣库存 | 下单成功即返回,后台异步处理库存 | 响应快 | 可能短暂超卖 | 对实时性要求不高的业务 |
3.2 SAP与用友系统的订单集成
对于使用SAP或U8等ERP系统的商家,需要特别注意:
SAP生产订单处理:
abap复制* SAP中修改生产订单结算规则示例
DATA: lt_settlement_rule TYPE TABLE OF bapi_order_settlement_rule,
ls_settlement_rule LIKE LINE OF lt_settlement_rule.
* 删除原有结算规则
CALL FUNCTION 'BAPI_PRODORD_SETTLE_RULE_DELETE'
EXPORTING
orderid = lv_orderid.
* 添加新结算规则
ls_settlement_rule-settle_rule = 'FERT'.
APPEND ls_settlement_rule TO lt_settlement_rule.
CALL FUNCTION 'BAPI_PRODORD_SETTLE_RULE_ADD'
EXPORTING
orderid = lv_orderid
TABLES
settlement_rules = lt_settlement_rule.
用友U8销售订单集成:
- 通过中间表或API接口同步订单数据
- 特别注意自定义项、自由项等扩展字段的映射
- 使用LRP计划时确保订单明细信息完整传递
4. 配送系统高级功能实现
4.1 实时轨迹追踪技术栈
现代外卖配送系统通常采用以下技术组合:
-
位置数据采集:
- 移动端:Android/iOS原生定位SDK
- 优化策略:智能采样(静止时降低频率)、WiFi辅助定位
-
数据传输:
- 协议:MQTT/WebSocket长连接
- 数据格式:Protocol Buffers二进制协议
-
数据存储:
- 实时位置:Redis GEO
- 历史轨迹:MongoDB分片集群
-
路径展示:
- 地图SDK:高德/Google Maps API
- 轨迹平滑:贝塞尔曲线算法
4.2 智能调度系统架构
mermaid复制graph TD
A[订单池] --> B(调度引擎)
C[骑手池] --> B
B --> D[规则引擎]
D --> E[距离计算]
D --> F[骑手评分]
D --> G[负载均衡]
E --> H[派单决策]
F --> H
G --> H
H --> I[派单结果]
重要提示:实际开发中应避免"最优解陷阱",调度算法需要在响应时间(<500ms)和派单质量之间取得平衡。我们实践中发现,简单的加权评分算法配合人工干预规则,往往比复杂的机器学习模型更稳定可靠。
5. 系统性能优化实战经验
5.1 MySQL订单表优化案例
针对订单表的常见性能问题及解决方案:
问题场景:
- parentid字段大多为null,少数情况才有值
- 按用户ID查询历史订单缓慢
优化方案:
- 索引优化:
sql复制ALTER TABLE `order`
ADD INDEX `idx_user_parent` (`user_id`, `parentid`) USING BTREE;
- 查询优化:
sql复制-- 避免全表扫描parentid为null的记录
SELECT * FROM `order`
WHERE user_id = 123
AND (parentid IS NULL OR parentid = 0)
ORDER BY create_time DESC LIMIT 10;
- 归档策略:
- 热数据:最近3个月订单,SSD存储
- 温数据:3-12个月订单,普通磁盘
- 冷数据:1年以上订单,归档到对象存储
5.2 分布式锁在订单系统的应用
高并发下防止重复下单的几种方案对比:
java复制// 基于Redis的分布式锁实现
public boolean createOrder(OrderDTO orderDTO) {
String lockKey = "order:lock:" + orderDTO.getUserId();
String requestId = UUID.randomUUID().toString();
try {
// 尝试获取锁
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("操作太频繁,请稍后再试");
}
// 执行业务逻辑
return doCreateOrder(orderDTO);
} finally {
// 释放锁
if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
6. 特殊业务场景处理
6.1 订单拆单处理
当用户从不同商家下单或部分商品缺货时,需要拆单处理:
- 拆单逻辑:
python复制def split_order(original_order):
split_orders = []
# 按商家拆单
for merchant_id, items in groupby(original_order.items, key=lambda x: x.merchant_id):
new_order = Order.copy_attrs(original_order)
new_order.parentid = original_order.id
new_order.items = list(items)
split_orders.append(new_order)
# 保存子订单
for order in split_orders:
order_repo.save(order)
# 更新父订单状态
original_order.status = 'SPLIT'
order_repo.save(original_order)
return split_orders
- 支付处理:
- 父订单记录总金额
- 子订单分别发起支付或合并支付
- 退款时需按子订单比例计算
6.2 跨国外卖系统考量
对于跨国外卖平台,还需要额外考虑:
- 多时区处理:
- 所有时间戳统一存储为UTC
- 前端根据用户时区动态显示
- 多币种支持:
java复制// 金额处理示例
public class Money {
private BigDecimal amount;
private Currency currency;
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(this.amount.add(other.amount), this.currency);
}
}
- 本地化配送规则:
- 不同国家的地址解析系统
- 宗教/文化敏感的配送时间安排
- 现金支付的找零习惯处理
我在实际开发外卖系统时发现,测试环境的异常流测试往往被忽视。建议特别关注以下场景的测试用例设计:
- 骑手接单后又取消的场景
- 商家接单超时与用户取消的竞态条件
- 支付成功但通知丢失的异常恢复
- 高峰期批量取消订单的补偿机制
这些边界情况往往决定了系统的最终稳定性。
