题目
设计高并发支付系统的扣款与退款流程
信息
- 类型:问答
- 难度:⭐⭐
考点
分布式事务, 幂等性设计, 消息队列应用, 数据库分片, 系统容错
快速回答
核心设计要点:
- 采用TCC分布式事务模型处理跨服务操作
- 通过唯一ID+状态机实现幂等控制
- 消息队列解耦核心流程并保证最终一致性
- 用户账户数据按UID分片存储
- 设计熔断降级和异步对账机制
1. 系统架构设计
核心组件:
- 支付网关:处理外部请求
- 账户服务:管理用户余额
- 交易服务:处理订单逻辑
- 消息队列(Kafka/RocketMQ)
- 分库分表MySQL集群
2. 关键设计原理
2.1 分布式事务(TCC模式)
扣款流程:
- Try阶段:冻结账户余额
UPDATE accounts SET frozen_balance = frozen_balance + 100
WHERE user_id = 123 AND balance - frozen_balance >= 100 - Confirm阶段:实际扣减余额
UPDATE accounts SET balance = balance - 100, frozen_balance = frozen_balance - 100
WHERE user_id = 123 - Cancel阶段:解冻余额(超时或失败时触发)
2.2 幂等性设计
实现方案:
- 全局唯一ID(雪花算法)作为交易流水号
- 数据库去重表:
CREATE TABLE idempotent_keys (
idempotent_key VARCHAR(64) PRIMARY KEY,
created_at TIMESTAMP
) - 状态机校验:
// 退款请求处理伪代码
function refund(idempotentKey, orderId) {
if (db.exists(idempotentKey)) return DUPLICATE;
Order order = orderService.get(orderId);
if (order.status != 'SUCCESS') throw InvalidStatusException();
startTransaction();
insertIdempotentKey(idempotentKey); // 插入去重表
updateOrderStatus(orderId, 'REFUNDING');
commitTransaction();
}
2.3 消息队列应用
退款流程异步化:
- 交易服务生成退款记录(状态为处理中)
- 发送延迟消息到MQ:
Message msg = new MessageBuilder()
.setTopic("REFUND_PROCESS")
.setDelayLevel(3) // 30分钟延迟
.setBody(refundRequest)
.build(); - 账户服务消费消息执行退款
- 未收到ACK则重试(最多3次)
2.4 数据库分片策略
用户账户分片:
- 分片键:user_id
- 分片算法:user_id % 1024(一致性哈希)
- 冷热分离:3个月前的交易转入历史库
3. 容错与最佳实践
- 熔断降级:Hystrix/Sentinel保护核心服务,失败率>10%时熔断
- 对账机制:每日定时任务比对支付系统和银行流水
- 重试策略:指数退避重试(1s, 2s, 4s...)
- 限流:Redis令牌桶控制每秒请求量
4. 常见错误
- ❌ 未做幂等控制导致重复退款
- ❌ 同步调用第三方支付导致线程阻塞
- ❌ 分片键选择不当引发数据热点
- ❌ 忽略事务隔离级别造成超扣(使用SELECT FOR UPDATE)
5. 扩展知识
- 资金安全:采用双记账体系(借贷平衡)
- 数据一致性:Saga模式替代TCC的适用场景
- 性能优化:本地缓存账户基础信息
- 监控:Prometheus监控TP99延迟,ELK收集异常日志