📖 开场:买买买的背后
想象你在双11抢购 🛍️:
10:00:00 秒杀开始:
你:点击"立即购买" 🖱️
↓
订单服务:创建订单 📝
↓
支付服务:扣款 💰
↓
库存服务:扣库存 📦
↓
物流服务:发货 🚚
↓
你:收到商品 ✅
看似简单,实则复杂:
- 100万人同时下单
- 库存不能超卖
- 支付失败要回滚
- 订单状态要正确
- 数据要一致
- 性能要高
这就是订单服务:电商系统的核心!
🤔 订单服务的核心挑战
挑战1:高并发 🔥
双11秒杀:
10:00:00 开始
↓
100万用户同时点击
↓
QPS:100万
↓
订单服务能承受吗?💀
解决方案:
- 限流
- 削峰填谷(消息队列)
- 分库分表
- 缓存
挑战2:分布式事务 🔄
订单创建流程:
1. 创建订单
2. 扣减库存
3. 扣减余额
4. 发送短信
问题:
- 库存扣减成功
- 余额扣减失败 💀
- 订单已创建,但没支付
- 数据不一致 ❌
解决方案:
- TCC
- Saga
- 本地消息表
挑战3:订单状态管理 🎯
订单状态:
待支付 → 已支付 → 待发货 → 已发货 → 已签收 → 已完成
问题:
- 状态流转复杂
- 不能随意跳转
- 需要状态机
例子:
已发货 → 待支付 ❌(不合法)
待支付 → 已支付 ✅(合法)
挑战4:超时关单 ⏰
问题:
用户下单后不支付
↓
订单占用库存
↓
其他用户无法购买 💀
解决方案:
30分钟未支付 → 自动关闭订单
↓
释放库存 ✅
挑战5:幂等性 🔐
问题:
用户点击"支付"
↓
网络抖动,重试
↓
支付请求发送2次
↓
扣款2次 💀
解决方案:
- 订单号唯一
- 幂等性校验
- 防止重复支付 ✅
🎯 核心设计
设计1:订单状态机 🎯
状态定义
public enum OrderStatus {
WAIT_PAY(1, "待支付"),
PAID(2, "已支付"),
WAIT_SEND(3, "待发货"),
SENT(4, "已发货"),
RECEIVED(5, "已签收"),
COMPLETED(6, "已完成"),
CANCELLED(7, "已取消"),
REFUNDING(8, "退款中"),
REFUNDED(9, "已退款");
private int code;
private String desc;
// ...
}
状态流转规则
状态机:
待支付
↓
(支付)
↓
已支付
↓
(商家发货)
↓
已发货
↓
(用户签收)
↓
已签收
↓
(自动确认)
↓
已完成
任何状态都可以 → 已取消(用户取消或超时)
已支付后可以 → 退款中 → 已退款
状态机实现
@Component
public class OrderStateMachine {
// ⭐ 状态流转规则
private static final Map<OrderStatus, Set<OrderStatus>> STATE_MACHINE = new HashMap<>();
static {
// 待支付 → 已支付、已取消
STATE_MACHINE.put(OrderStatus.WAIT_PAY,
EnumSet.of(OrderStatus.PAID, OrderStatus.CANCELLED));
// 已支付 → 待发货、已取消、退款中
STATE_MACHINE.put(OrderStatus.PAID,
EnumSet.of(OrderStatus.WAIT_SEND, OrderStatus.CANCELLED, OrderStatus.REFUNDING));
// 待发货 → 已发货、已取消、退款中
STATE_MACHINE.put(OrderStatus.WAIT_SEND,
EnumSet.of(OrderStatus.SENT, OrderStatus.CANCELLED, OrderStatus.REFUNDING));
// 已发货 → 已签收、退款中
STATE_MACHINE.put(OrderStatus.SENT,
EnumSet.of(OrderStatus.RECEIVED, OrderStatus.REFUNDING));
// 已签收 → 已完成、退款中
STATE_MACHINE.put(OrderStatus.RECEIVED,
EnumSet.of(OrderStatus.COMPLETED, OrderStatus.REFUNDING));
// 退款中 → 已退款
STATE_MACHINE.put(OrderStatus.REFUNDING,
EnumSet.of(OrderStatus.REFUNDED));
}
/**
* ⭐ 校验状态流转是否合法
*/
public boolean canTransition(OrderStatus from, OrderStatus to) {
Set<OrderStatus> allowedStates = STATE_MACHINE.get(from);
return allowedStates != null && allowedStates.contains(to);
}
/**
* ⭐ 状态流转
*/
public void transition(Order order, OrderStatus toStatus, String reason) {
OrderStatus fromStatus = order.getStatus();
// 校验状态流转是否合法
if (!canTransition(fromStatus, toStatus)) {
throw new IllegalStateException(
String.format("订单状态不能从 %s 流转到 %s", fromStatus.getDesc(), toStatus.getDesc())
);
}
// 更新订单状态
order.setStatus(toStatus);
// 记录状态变更日志
OrderStatusLog log = new OrderStatusLog();
log.setOrderId(order.getId());
log.setFromStatus(fromStatus);
log.setToStatus(toStatus);
log.setReason(reason);
log.setCreateTime(new Date());
orderStatusLogMapper.insert(log);
// 更新订单
orderMapper.updateById(order);
}
}
使用示例
@Service
public class OrderService {
@Autowired
private OrderStateMachine stateMachine;
/**
* ⭐ 支付订单
*/
public void pay(Long orderId, String paymentId) {
// 查询订单
Order order = orderMapper.selectById(orderId);
if (order == null) {
throw new OrderNotFoundException("订单不存在");
}
// ⭐ 状态流转:待支付 → 已支付
stateMachine.transition(order, OrderStatus.PAID, "用户支付成功,支付单号:" + paymentId);
}
/**
* ⭐ 发货
*/
public void send(Long orderId, String expressNo) {
Order order = orderMapper.selectById(orderId);
// ⭐ 状态流转:待发货 → 已发货
stateMachine.transition(order, OrderStatus.SENT, "商家发货,快递单号:" + expressNo);
}
}
设计2:分库分表 📊
为什么要分库分表?
订单表数据量:
- 用户:1亿
- 每人每年:10个订单
- 每年:10亿订单
- 10年:100亿订单 💀
单表问题:
- 查询慢(全表扫描)
- 写入慢(索引维护)
- 表锁竞争
解决方案:
分库分表 ✅
分库分表策略
水平分表:
- 按用户ID分表
- 1024个表
例子:
用户ID = 123456789
↓
hash = 123456789 % 1024 = 789
↓
存储到:order_789 表
优点:
- 数据均匀分布 ✅
- 支持千亿级数据 ✅
ShardingSphere实现
引入依赖:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.0</version>
</dependency>
配置:
spring:
shardingsphere:
datasource:
names: ds0,ds1 # 2个数据源
# 数据源1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/order_db_0
username: root
password: root
# 数据源2
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/order_db_1
username: root
password: root
rules:
sharding:
tables:
# ⭐ 订单表分片规则
t_order:
actual-data-nodes: ds$->{0..1}.t_order_$->{0..1023} # 2个库,每个库1024张表
# 分库策略(按用户ID)
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db-inline
# 分表策略(按用户ID)
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: table-inline
# 分片算法
sharding-algorithms:
# 分库算法
db-inline:
type: INLINE
props:
algorithm-expression: ds$->{user_id % 2} # 2个库
# 分表算法
table-inline:
type: INLINE
props:
algorithm-expression: t_order_$->{user_id % 1024} # 1024张表
props:
sql-show: true # 打印SQL
使用:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* ⭐ 创建订单(自动路由到正确的库表)
*/
public Order createOrder(Long userId, List<OrderItem> items) {
Order order = new Order();
order.setUserId(userId); // ⭐ 根据userId自动路由
order.setStatus(OrderStatus.WAIT_PAY);
order.setCreateTime(new Date());
orderMapper.insert(order);
return order;
}
/**
* ⭐ 查询用户订单(自动路由)
*/
public List<Order> getUserOrders(Long userId) {
// ⭐ 根据userId自动路由到正确的库表
return orderMapper.selectByUserId(userId);
}
}
分布式ID生成
问题:
分库分表后,订单ID如何生成?
↓
不能用自增ID(会重复)💀
解决方案:
雪花算法(Snowflake)⭐
雪花算法:
订单ID(64位):
┌─────────┬───────┬───────┬────────────┐
│时间戳 │机器ID │序列号 │
│41位 │10位 │12位 │
└─────────┴───────┴───────┴────────────┘
时间戳:41位,可用69年
机器ID:10位,支持1024台机器
序列号:12位,每毫秒可生成4096个ID
优点:
- 全局唯一 ✅
- 趋势递增(有利于数据库索引)✅
- 高性能(本地生成)✅
代码实现:
@Component
public class SnowflakeIdGenerator {
// ⭐ 起始时间戳(2020-01-01)
private final long startTimestamp = 1577808000000L;
// 机器ID(10位,0-1023)
private final long workerId;
// 序列号(12位,0-4095)
private long sequence = 0L;
// 上次生成ID的时间戳
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId) {
if (workerId < 0 || workerId > 1023) {
throw new IllegalArgumentException("workerId must be between 0 and 1023");
}
this.workerId = workerId;
}
/**
* ⭐ 生成订单ID
*/
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回拨,拒绝生成ID");
}
if (timestamp == lastTimestamp) {
// 同一毫秒内,序列号+1
sequence = (sequence + 1) & 4095;
if (sequence == 0) {
// 序列号用完,等待下一毫秒
timestamp = waitNextMillis(lastTimestamp);
}
} else {
// 新的一毫秒,序列号重置为0
sequence = 0L;
}
lastTimestamp = timestamp;
// ⭐ 组装ID
long id = ((timestamp - startTimestamp) << 22) // 时间戳左移22位
| (workerId << 12) // 机器ID左移12位
| sequence; // 序列号
return id;
}
/**
* 等待下一毫秒
*/
private long waitNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
设计3:超时关单 ⏰
方案1:定时任务(不推荐)❌
定时任务:
每分钟扫描一次订单表
↓
查询30分钟前的待支付订单
↓
关闭订单
缺点:
- 延迟高(最多1分钟)❌
- 数据库压力大(全表扫描)❌
- 不支持分库分表 ❌
方案2:延迟消息(推荐)⭐⭐⭐
创建订单时:
1. 创建订单(待支付)
2. 发送延迟消息(30分钟后)
↓
30分钟后:
消息队列 → 消费者
↓
消费者:检查订单状态
↓
如果还是待支付 → 关闭订单 ✅
优点:
- 精准(刚好30分钟)✅
- 高性能(无需扫描数据库)✅
- 支持分库分表 ✅
RocketMQ实现:
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* ⭐ 创建订单
*/
public Order createOrder(Long userId, List<OrderItem> items) {
// 1. 创建订单
Order order = new Order();
order.setId(idGenerator.nextId());
order.setUserId(userId);
order.setStatus(OrderStatus.WAIT_PAY);
order.setCreateTime(new Date());
orderMapper.insert(order);
// ⭐ 2. 发送延迟消息(30分钟后关单)
rocketMQTemplate.syncSend(
"order-timeout-topic",
MessageBuilder.withPayload(order.getId()).build(),
3000, // 超时时间
18 // 延迟级别:18 = 30分钟
);
return order;
}
}
/**
* ⭐ 超时关单消费者
*/
@Component
@RocketMQMessageListener(
topic = "order-timeout-topic",
consumerGroup = "order-timeout-consumer"
)
public class OrderTimeoutConsumer implements RocketMQListener<Long> {
@Autowired
private OrderService orderService;
@Override
public void onMessage(Long orderId) {
// 查询订单
Order order = orderService.getById(orderId);
if (order == null) {
return;
}
// ⭐ 如果还是待支付,关闭订单
if (order.getStatus() == OrderStatus.WAIT_PAY) {
orderService.closeOrder(orderId, "超时未支付,自动关闭");
}
}
}
RocketMQ延迟级别:
延迟级别:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
级别18 = 30分钟
方案3:Redis有序集合(轻量级)⭐⭐
创建订单时:
1. 创建订单(待支付)
2. 添加到Redis ZSet
score = 当前时间 + 30分钟
定时任务:
每10秒执行一次
↓
查询Redis ZSet(score < 当前时间)
↓
关闭这些订单 ✅
优点:
- 简单(无需消息队列)✅
- 高性能(Redis内存操作)✅
缺点:
- 可靠性稍低(Redis宕机会丢失)❌
代码实现:
@Service
public class OrderService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String ORDER_TIMEOUT_KEY = "order:timeout";
/**
* ⭐ 创建订单
*/
public Order createOrder(Long userId, List<OrderItem> items) {
// 1. 创建订单
Order order = new Order();
order.setId(idGenerator.nextId());
order.setUserId(userId);
order.setStatus(OrderStatus.WAIT_PAY);
order.setCreateTime(new Date());
orderMapper.insert(order);
// ⭐ 2. 添加到Redis ZSet(30分钟后)
long expireTime = System.currentTimeMillis() + 30 * 60 * 1000;
redisTemplate.opsForZSet().add(ORDER_TIMEOUT_KEY,
String.valueOf(order.getId()),
expireTime);
return order;
}
/**
* ⭐ 支付成功,从Redis中移除
*/
public void pay(Long orderId) {
// 更新订单状态
// ...
// 从Redis中移除
redisTemplate.opsForZSet().remove(ORDER_TIMEOUT_KEY, String.valueOf(orderId));
}
}
/**
* ⭐ 超时关单定时任务
*/
@Component
public class OrderTimeoutJob {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private OrderService orderService;
private static final String ORDER_TIMEOUT_KEY = "order:timeout";
/**
* 每10秒执行一次
*/
@Scheduled(fixedRate = 10000)
public void closeTimeoutOrders() {
long now = System.currentTimeMillis();
// ⭐ 查询超时的订单(score < now)
Set<String> orderIds = redisTemplate.opsForZSet()
.rangeByScore(ORDER_TIMEOUT_KEY, 0, now);
if (orderIds == null || orderIds.isEmpty()) {
return;
}
// 关闭订单
for (String orderIdStr : orderIds) {
Long orderId = Long.valueOf(orderIdStr);
try {
orderService.closeOrder(orderId, "超时未支付,自动关闭");
// 从Redis中移除
redisTemplate.opsForZSet().remove(ORDER_TIMEOUT_KEY, orderIdStr);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
设计4:幂等性保证 🔐
问题
用户点击"支付":
↓
网络抖动
↓
前端重试
↓
支付请求发送2次
↓
扣款2次 💀
解决方案:订单号 + 状态机
@Service
public class OrderService {
@Autowired
private OrderStateMachine stateMachine;
/**
* ⭐ 支付订单(幂等)
*/
@Transactional
public void pay(Long orderId, String paymentId) {
// 查询订单
Order order = orderMapper.selectById(orderId);
if (order == null) {
throw new OrderNotFoundException("订单不存在");
}
// ⭐ 幂等性校验:如果订单已支付,直接返回
if (order.getStatus() != OrderStatus.WAIT_PAY) {
log.info("订单已支付,幂等返回。orderId={}", orderId);
return; // 幂等返回 ✅
}
// 调用支付服务扣款
paymentService.deduct(paymentId, order.getAmount());
// 更新订单状态:待支付 → 已支付
stateMachine.transition(order, OrderStatus.PAID, "用户支付成功");
}
}
关键点:
- 订单状态校验
- 数据库乐观锁(version字段)
- 支付单号唯一性
设计5:分布式事务 🔄
场景
创建订单流程:
1. 创建订单
2. 扣减库存
3. 扣减余额
问题:
- 订单创建成功
- 库存扣减成功
- 余额扣减失败 💀
↓
数据不一致!
方案:本地消息表 ⭐⭐⭐
流程:
1. 开启事务
2. 创建订单
3. 写入本地消息表(扣减库存消息)
4. 提交事务
↓
定时任务:
扫描消息表
↓
发送MQ消息 → 库存服务
↓
库存服务:扣减库存,返回ACK
↓
删除消息表记录 ✅
优点:
- 最终一致性 ✅
- 高性能 ✅
代码实现:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private LocalMessageMapper localMessageMapper;
/**
* ⭐ 创建订单(本地消息表)
*/
@Transactional
public Order createOrder(Long userId, List<OrderItem> items) {
// 1. 创建订单
Order order = new Order();
order.setId(idGenerator.nextId());
order.setUserId(userId);
order.setStatus(OrderStatus.WAIT_PAY);
orderMapper.insert(order);
// ⭐ 2. 写入本地消息表(扣减库存消息)
LocalMessage message = new LocalMessage();
message.setId(UUID.randomUUID().toString());
message.setTopic("stock-deduct-topic");
message.setContent(JSON.toJSONString(items));
message.setStatus(MessageStatus.PENDING);
message.setCreateTime(new Date());
localMessageMapper.insert(message);
// 3. 提交事务(订单和消息原子性写入)
return order;
}
}
/**
* ⭐ 消息发送定时任务
*/
@Component
public class LocalMessageSender {
@Autowired
private LocalMessageMapper localMessageMapper;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 每5秒执行一次
*/
@Scheduled(fixedRate = 5000)
public void sendMessages() {
// 查询待发送的消息
List<LocalMessage> messages = localMessageMapper.selectPending();
for (LocalMessage message : messages) {
try {
// ⭐ 发送MQ消息
rocketMQTemplate.syncSend(message.getTopic(), message.getContent());
// 更新消息状态
message.setStatus(MessageStatus.SUCCESS);
localMessageMapper.updateById(message);
} catch (Exception e) {
// 发送失败,下次重试
e.printStackTrace();
}
}
}
}
📊 数据库设计
订单表
-- ⭐ 订单主表
CREATE TABLE t_order (
id BIGINT PRIMARY KEY COMMENT '订单ID(雪花算法)',
user_id BIGINT NOT NULL COMMENT '用户ID',
status TINYINT NOT NULL COMMENT '订单状态',
total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
pay_amount DECIMAL(10,2) COMMENT '实付金额',
payment_id VARCHAR(100) COMMENT '支付单号',
payment_time DATETIME COMMENT '支付时间',
express_no VARCHAR(100) COMMENT '快递单号',
express_time DATETIME COMMENT '发货时间',
receive_time DATETIME COMMENT '签收时间',
complete_time DATETIME COMMENT '完成时间',
cancel_time DATETIME COMMENT '取消时间',
cancel_reason VARCHAR(500) COMMENT '取消原因',
version INT NOT NULL DEFAULT 0 COMMENT '版本号(乐观锁)',
create_time DATETIME NOT NULL COMMENT '创建时间',
update_time DATETIME COMMENT '更新时间',
INDEX idx_user_id (user_id),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) COMMENT '订单表';
-- ⭐ 订单明细表
CREATE TABLE t_order_item (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL COMMENT '订单ID',
product_id BIGINT NOT NULL COMMENT '商品ID',
product_name VARCHAR(200) NOT NULL COMMENT '商品名称',
product_image VARCHAR(500) COMMENT '商品图片',
quantity INT NOT NULL COMMENT '购买数量',
price DECIMAL(10,2) NOT NULL COMMENT '商品单价',
total_amount DECIMAL(10,2) NOT NULL COMMENT '小计金额',
INDEX idx_order_id (order_id)
) COMMENT '订单明细表';
-- ⭐ 订单状态日志表
CREATE TABLE t_order_status_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL COMMENT '订单ID',
from_status TINYINT COMMENT '原状态',
to_status TINYINT NOT NULL COMMENT '新状态',
reason VARCHAR(500) COMMENT '变更原因',
create_time DATETIME NOT NULL COMMENT '创建时间',
INDEX idx_order_id (order_id)
) COMMENT '订单状态日志表';
🎓 面试题速答
Q1: 订单状态如何管理?
A: 状态机:
// 状态流转规则
private static final Map<OrderStatus, Set<OrderStatus>> STATE_MACHINE;
// 校验状态流转
public boolean canTransition(OrderStatus from, OrderStatus to) {
Set<OrderStatus> allowedStates = STATE_MACHINE.get(from);
return allowedStates != null && allowedStates.contains(to);
}
优点:
- 防止非法状态流转
- 清晰的状态管理
Q2: 订单表如何分库分表?
A: 按用户ID分片:
# 分片规则
database-strategy:
sharding-column: user_id
algorithm-expression: ds$->{user_id % 2} # 2个库
table-strategy:
sharding-column: user_id
algorithm-expression: t_order_$->{user_id % 1024} # 1024张表
订单ID生成:雪花算法(Snowflake)
Q3: 超时关单如何实现?
A: **延迟消息(RocketMQ)**⭐:
// 创建订单时,发送延迟消息
rocketMQTemplate.syncSend(
"order-timeout-topic",
orderId,
3000,
18 // 延迟级别:18 = 30分钟
);
// 30分钟后,消费者收到消息
@Override
public void onMessage(Long orderId) {
if (order.getStatus() == OrderStatus.WAIT_PAY) {
orderService.closeOrder(orderId);
}
}
Q4: 如何保证幂等性?
A: 订单状态校验:
public void pay(Long orderId) {
Order order = orderMapper.selectById(orderId);
// ⭐ 幂等性校验
if (order.getStatus() != OrderStatus.WAIT_PAY) {
return; // 已支付,直接返回 ✅
}
// 支付逻辑
// ...
}
Q5: 分布式事务如何处理?
A: 本地消息表⭐:
@Transactional
public Order createOrder() {
// 1. 创建订单
orderMapper.insert(order);
// 2. 写入本地消息表(扣减库存消息)
localMessageMapper.insert(message);
// 3. 提交事务(原子性)
}
// 定时任务扫描消息表,发送MQ消息
@Scheduled(fixedRate = 5000)
public void sendMessages() {
List<LocalMessage> messages = localMessageMapper.selectPending();
for (LocalMessage message : messages) {
rocketMQTemplate.send(message);
}
}
最终一致性 ✅
🎬 总结
订单服务核心设计
┌────────────────────────────────────┐
│ 1. 订单状态机 │
│ - 状态流转规则 │
│ - 防止非法流转 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 2. 分库分表 │
│ - 按用户ID分片 │
│ - 雪花算法生成订单ID │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 3. 超时关单 │
│ - 延迟消息(RocketMQ)⭐ │
│ - Redis ZSet │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 4. 幂等性保证 │
│ - 订单状态校验 │
│ - 乐观锁 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 5. 分布式事务 │
│ - 本地消息表 │
│ - 最终一致性 │
└────────────────────────────────────┘
🎉 恭喜你!
你已经完全掌握了电商订单服务的设计!🎊
核心要点:
- 订单状态机:管理状态流转,防止非法操作
- 分库分表:按用户ID分片,支持海量数据
- 超时关单:延迟消息,精准关单
- 幂等性保证:状态校验,防止重复支付
- 分布式事务:本地消息表,最终一致性
下次面试,这样回答:
"订单服务使用状态机管理订单状态。定义了待支付、已支付、待发货等状态,并通过Map定义状态流转规则。每次状态变更前校验是否允许流转,防止非法操作,如已发货不能流转回待支付。
订单表按用户ID进行分库分表。使用ShardingSphere配置分片规则,2个库每个库1024张表,根据用户ID取模路由到对应的库表。订单ID使用雪花算法生成,保证全局唯一且趋势递增。
超时关单使用RocketMQ延迟消息实现。创建订单时发送延迟消息,延迟级别设置为18(30分钟)。30分钟后消费者收到消息,检查订单状态,如果还是待支付则关闭订单并释放库存。这种方式精准且高性能,无需扫描数据库。
幂等性通过订单状态校验实现。支付接口先查询订单状态,如果不是待支付则直接返回,避免重复支付。配合数据库乐观锁(version字段)保证并发安全。
分布式事务使用本地消息表实现最终一致性。创建订单时在同一事务中写入订单记录和本地消息记录。定时任务扫描消息表,将消息发送到MQ,库存服务消费消息后扣减库存。这种方式性能高且可靠,适合高并发场景。"
面试官:👍 "很好!你对订单服务的设计理解很深刻!"
本文完 🎬
上一篇: 207-设计一个分布式配置中心.md
下一篇: 209-设计一个IM即时通讯系统.md
作者注:写完这篇,我都想去做电商架构师了!🛒
如果这篇文章对你有帮助,请给我一个Star⭐!