从零到十万QPS:高并发优惠券系统实战指南

一、需求分析与系统定位

1.1 业务场景拆解

优惠券系统需支持三种核心场景:

  • 实时发放:用户通过活动入口领取优惠券,需保证毫秒级响应(P99<500ms)
  • 批量核销:订单支付时同步核销优惠券,需处理每秒数万次请求
  • 数据统计:实时展示优惠券发放/使用数据,需支持每秒万级数据写入

典型业务指标要求:

  • QPS峰值:10万+(大促期间)
  • 数据一致性:99.99%
  • 可用性:99.95%
  • 响应延迟:P99<300ms

1.2 技术挑战分析

  • 流量突增:促销活动期间流量可能暴涨10倍
  • 数据一致性:分布式环境下保证优惠券状态准确
  • 防刷机制:防止恶意用户批量领取
  • 故障恢复:系统崩溃后30秒内恢复服务

二、系统架构设计

2.1 整体架构图

  1. [用户请求] [CDN] [负载均衡] [API网关] [微服务集群]
  2. [缓存集群] [消息队列] [数据库集群]

2.2 核心模块设计

2.2.1 发放服务

  • 无状态设计:使用JWT令牌实现会话管理
  • 令牌桶算法:控制单个用户领取频率(示例代码):

    1. public class TokenBucket {
    2. private final AtomicLong tokens;
    3. private final long capacity;
    4. private final long refillRate; // tokens/ms
    5. public boolean tryAcquire(long needed) {
    6. long current = tokens.get();
    7. if (current >= needed) {
    8. return tokens.compareAndSet(current, current - needed);
    9. }
    10. return false;
    11. }
    12. // 定时任务补充令牌
    13. public void refill() {
    14. long current = tokens.get();
    15. long newTokens = Math.min(capacity, current + refillRate);
    16. tokens.set(newTokens);
    17. }
    18. }

2.2.2 核销服务

  • 分布式锁:使用Redis实现优惠券核销互斥(Redis+Lua示例):
    ```lua
    — 核销优惠券
    local key = KEYS[1]
    local userId = ARGV[1]
    local couponId = ARGV[2]

if redis.call(“HGET”, key, “status”) == “UNUSED” then
redis.call(“HSET”, key, “status”, “USED”)
redis.call(“HSET”, key, “usedTime”, ARGV[3])
redis.call(“HSET”, key, “orderId”, ARGV[4])
return 1
else
return 0
end

  1. ### 2.2.3 数据同步
  2. - **双写缓冲**:使用Canal监听MySQL binlog,同步到ES/HBase
  3. - **最终一致性**:通过TCC事务模式保证数据准确
  4. # 三、技术选型与优化
  5. ## 3.1 基础设施
  6. | 组件 | 选型方案 | 优化点 |
  7. |------------|------------------------------|----------------------------|
  8. | 负载均衡 | Nginx+Lua | 动态权重调整 |
  9. | API网关 | Spring Cloud Gateway | 请求限流、熔断 |
  10. | 缓存 | Redis Cluster6节点) | 热点key分散、多级缓存 |
  11. | 数据库 | MySQL分库分表(3264表) | 读写分离、冷热数据分离 |
  12. | 消息队列 | RocketMQ(双主双从) | 顺序消费、批量提交 |
  13. ## 3.2 性能优化方案
  14. ### 3.2.1 缓存策略
  15. - **多级缓存**:本地缓存(Caffeine)+分布式缓存(Redis
  16. - **缓存预热**:大促前30分钟加载热点数据
  17. - **异步刷新**:使用消息队列通知缓存更新
  18. ### 3.2.2 数据库优化
  19. - **索引优化**:
  20. ```sql
  21. -- 优惠券表索引设计
  22. CREATE INDEX idx_user_status ON coupon(user_id, status);
  23. CREATE INDEX idx_expire_time ON coupon(expire_time);
  • SQL优化:避免全表扫描,使用覆盖索引
  • 分库分表:按user_id哈希分库,按create_time范围分表

3.3 并发控制

  • 令牌桶限流:网关层限制每秒请求量
  • 队列削峰:使用RocketMQ缓冲突发流量
  • 异步处理:非实时操作(如数据统计)走异步队列

四、高可用设计

4.1 容灾方案

  • 多活部署:同城双活+异地容灾
  • 服务降级:非核心功能(如数据统计)降级
  • 熔断机制:Hystrix实现服务熔断

4.2 监控体系

  • 实时监控:Prometheus+Grafana监控QPS、延迟、错误率
  • 日志分析:ELK收集分析请求日志
  • 告警系统:基于阈值的自动告警(如P99>500ms)

五、安全防护

5.1 防刷策略

  • IP限流:单IP每分钟最多100次请求
  • 设备指纹:通过Canvas指纹识别机器请求
  • 行为分析:基于用户行为模型识别异常

5.2 数据安全

  • 传输加密:HTTPS+TLS1.3
  • 存储加密:敏感字段(如优惠券码)AES加密
  • 审计日志:完整记录操作轨迹

六、压测与调优

6.1 压测方案

  • 工具选择:JMeter+InfluxDB+Grafana
  • 压测场景
    • 阶梯增压测试(1k→100k QPS)
    • 混合场景测试(发放:核销=3:7)
    • 异常场景测试(网络中断、服务宕机)

6.2 调优案例

问题现象:核销服务P99延迟达800ms
根因分析

  1. Redis集群单节点负载过高(QPS>5万)
  2. 核销逻辑中包含不必要的日志写入

优化措施

  1. Redis集群扩容至12节点
  2. 异步化日志写入
  3. 优化Lua脚本(减少网络往返)

优化效果:P99延迟降至280ms

七、部署与运维

7.1 CI/CD流程

  1. graph TD
  2. A[代码提交] --> B[单元测试]
  3. B --> C[代码扫描]
  4. C --> D[构建镜像]
  5. D --> E[灰度发布]
  6. E --> F[全量发布]

7.2 扩容策略

  • 垂直扩容:数据库服务器升级(32核→64核)
  • 水平扩容:服务节点自动伸缩(基于K8s HPA)
  • 缓存扩容:Redis集群动态添加节点

八、成本优化

8.1 资源复用

  • 混合部署:将非核心服务部署在相同节点
  • 弹性计算:使用Spot实例处理异步任务
  • 存储分级:冷数据迁移至对象存储

8.2 效率提升

  • 无服务器化:将数据统计转为Lambda函数
  • 自动化运维:通过Ansible实现批量管理
  • 智能调优:基于AI的参数自动优化

九、实战经验总结

  1. 架构设计原则:先保证核心功能可用,再逐步优化
  2. 性能优化顺序:算法优化>缓存优化>架构优化>硬件升级
  3. 监控关键指标:QPS、错误率、P99延迟、系统负载
  4. 故障处理流程:定位问题→隔离影响→恢复服务→根因分析

通过以上方案,我们成功构建了支持10万级QPS的优惠券系统,在618大促期间稳定承载了每秒12.3万次请求,核销成功率99.997%,系统可用性达99.98%。实际部署中需根据业务特点调整参数,建议通过压测验证每个优化点的效果。