1. 项目背景与核心挑战
支付系统作为电商平台的核心模块,对稳定性、性能和一致性的要求极高。去年我在参与某跨境电商平台支付系统重构时,就遇到了一个典型的高并发支付场景:在促销活动期间,系统需要处理每秒超过5000笔的支付请求,同时保证每笔交易的幂等性和数据一致性。
这个场景下我们需要解决三个核心问题:
- 高并发下的Redis缓存穿透/雪崩
- 分布式环境下的接口幂等性保障
- 第三方支付渠道不稳定时的熔断降级
2. 技术栈选型解析
2.1 Spring Boot作为基础框架
选择Spring Boot 2.7.x版本主要基于:
- 自动配置简化了Redis、Resilience4j等组件的集成
- 内嵌Tomcat支持高并发请求处理
- Actuator提供完善的监控端点
关键配置示例:
server: tomcat: max-threads: 800 accept-count: 10002.2 Redis缓存方案设计
采用Redis 6.2集群实现:
- 支付订单缓存
- 分布式锁
- 接口幂等令牌存储
防雪崩策略:
// 缓存空对象解决穿透问题 redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES); // 互斥锁防止缓存击穿 Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);2.3 Resilience4j熔断配置
针对第三方支付接口的配置参数:
CircuitBreakerConfig.custom() .failureRateThreshold(50) // 失败率阈值 .waitDurationInOpenState(Duration.ofMillis(1000)) // 熔断时间 .ringBufferSizeInHalfOpenState(10) // 半开状态探测请求数 .ringBufferSizeInClosedState(100) // 关闭状态样本数 .build();3. 支付核心流程实现
3.1 幂等性设计
采用"token+redis"方案:
- 前端预获取幂等token(有效期5分钟)
- 支付请求必须携带token
- 服务端Redis原子性校验
public boolean checkIdempotent(String token) { String redisKey = "pay:idempotent:" + token; Long result = redisTemplate.opsForValue().increment(redisKey); if(result == 1) { redisTemplate.expire(redisKey, 5, TimeUnit.MINUTES); return true; } return false; }3.2 分布式锁实现
Redisson实现的库存扣减:
RLock lock = redissonClient.getLock("stock:" + productId); try { if(lock.tryLock(1, 10, TimeUnit.SECONDS)) { // 扣减库存逻辑 } } finally { lock.unlock(); }3.3 熔断降级策略
支付失败时的降级方案:
- 本地队列重试(最多3次)
- 记录到MySQL待补偿表
- 返回友好提示页面
4. 性能优化实战
4.1 Redis管道批处理
批量查询优化示例:
List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> { for(String orderNo : orderNos) { connection.get(("order:" + orderNo).getBytes()); } return null; });4.2 热点数据分离
将支付结果与支付日志分离存储:
- Redis:存储支付状态(设置TTL)
- MySQL:存储完整支付流水
- Elasticsearch:存储查询日志
5. 踩坑实录与解决方案
5.1 Redis连接池耗尽
现象:高峰期出现"max number of clients reached"错误
解决方案:
- 调整连接池参数
spring: redis: lettuce: pool: max-active: 200 max-idle: 50 min-idle: 20- 增加Redis集群节点
- 引入连接池监控
5.2 分布式锁误用
错误案例:锁过期时间小于业务执行时间导致并发问题
正确做法:
// 使用看门狗自动续期 lock.lock(30, TimeUnit.SECONDS); // 业务代码... lock.unlock();5.3 熔断配置不当
错误配置:半开状态探测请求数设置过小
优化方案:
.ringBufferSizeInHalfOpenState(20) // 根据实际QPS调整 .recordExceptions(TimeoutException.class) // 只针对超时熔断6. 监控与告警体系
6.1 Prometheus监控指标
关键监控项:
- 支付接口成功率
- Redis命中率
- 熔断器状态变化
- 线程池活跃度
6.2 日志排查技巧
使用MDC实现请求链路追踪:
MDC.put("traceId", UUID.randomUUID().toString()); try { // 业务逻辑 } finally { MDC.clear(); }日志配置示例:
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>7. 面试要点解析
7.1 高频面试问题
Redis持久化策略如何选择?
- RDB适合备份恢复
- AOF保证数据安全
- 生产环境建议混合使用
如何设计分布式ID生成器?
- 雪花算法(Snowflake)
- Redis原子自增
- 数据库号段模式
熔断与降级的区别?
- 熔断:自动故障保护
- 降级:手动功能简化
7.2 实战经验分享
在压力测试中发现:
- Redis集群在节点故障转移时会有200ms左右的抖动
- Resilience4j的熔断恢复时间需要根据业务特点调整
- Spring Boot的Tomcat参数需要根据服务器配置优化