高并发场景下优惠券抢购系统设计:100万用户抢10万券实战指南

一、技术架构选型与分布式部署

1.1 微服务架构拆分

系统需拆分为用户服务、优惠券服务、订单服务、库存服务四大核心模块,各服务通过API网关(如Spring Cloud Gateway)统一接入。用户服务负责身份验证与风控,优惠券服务管理券模板与发放规则,订单服务处理生成与状态同步,库存服务实现原子化扣减。

1.2 分布式部署方案

采用容器化部署(Docker+K8s),每个服务实例配置4核8G内存,通过Nginx负载均衡实现横向扩展。建议部署3个可用区,每个区至少5个Pod实例,确保单点故障不影响全局。

二、数据库设计与优化

2.1 分库分表策略

优惠券库存表按券ID哈希分10库,每库再按用户ID范围分100表,共1000张分表。使用ShardingSphere-JDBC实现透明分片,SQL路由规则示例:

  1. -- 创建分表(示例为MySQL语法)
  2. CREATE TABLE coupon_stock_0000 TO coupon_stock_0999 (
  3. id BIGINT PRIMARY KEY,
  4. coupon_id BIGINT NOT NULL,
  5. user_id BIGINT NOT NULL,
  6. status TINYINT DEFAULT 0 COMMENT '0-未领取 1-已锁定 2-已使用',
  7. expire_time DATETIME NOT NULL,
  8. INDEX idx_coupon_user (coupon_id, user_id)
  9. ) ENGINE=InnoDB;

2.2 读写分离实现

主库负责写操作,从库通过MySQL Router实现读负载均衡。建议配置1主3从架构,从库延迟监控阈值设为500ms。

三、缓存层设计

3.1 多级缓存架构

  • CDN缓存:静态资源(HTML/CSS/JS)TTL设为10分钟
  • Redis集群:部署3主3从集群,存储券模板信息与用户领取状态
  • 本地缓存:各服务实例使用Caffeine缓存热点数据(如用户基本信息)

3.2 缓存穿透解决方案

对不存在的券ID返回空对象并缓存1分钟,使用布隆过滤器过滤非法请求。Redis Lua脚本实现原子操作示例:

  1. -- 领取优惠券原子脚本
  2. local key = KEYS[1] -- 券库存key
  3. local userKey = KEYS[2] -- 用户领取状态key
  4. local userId = ARGV[1]
  5. local stock = tonumber(redis.call('GET', key))
  6. if stock <= 0 then
  7. return 0
  8. end
  9. local hasGot = redis.call('HEXISTS', userKey, userId)
  10. if hasGot == 1 then
  11. return -1
  12. end
  13. redis.call('DECR', key)
  14. redis.call('HSET', userKey, userId, 1)
  15. return 1

四、限流与降级策略

4.1 动态限流配置

  • 网关层限流:基于令牌桶算法,QPS阈值设为5000/秒,突发量10000
  • 服务内部限流:使用Sentinel实现方法级限流,如/coupon/grab接口设为2000/秒
  • 用户级限流:同一用户5秒内最多3次请求

4.2 降级方案

  • 熔断机制:连续失败率超过50%时触发熔断,持续30秒
  • 静态页面降级:当系统负载超过90%时,返回预生成的HTML页面
  • 队列降级:将请求写入Kafka,由消费者异步处理

五、异步处理设计

5.1 消息队列应用

使用RocketMQ实现最终一致性,生产者发送事务消息:

  1. // 事务消息生产者示例
  2. TransactionMQProducer producer = new TransactionMQProducer("coupon_group");
  3. producer.setTransactionListener(new TransactionListenerImpl());
  4. producer.start();
  5. Message msg = new Message("COUPON_TOPIC", "TAG_A",
  6. ("券ID:" + couponId + "|用户ID:" + userId).getBytes());
  7. SendResult sendResult = producer.sendMessageInTransaction(msg, null);

5.2 补偿机制

设置定时任务每5分钟扫描未确认消息,对失败消息进行重试(最多3次),仍失败则记录死信队列。

六、监控与预警体系

6.1 实时监控指标

  • 系统指标:CPU使用率、内存占用、磁盘I/O
  • 业务指标:领取成功率、库存变化率、接口响应时间
  • 错误指标:5xx错误率、Redis命中率、消息积压量

6.2 智能预警规则

  • 当QPS突增30%时触发一级预警
  • 领取成功率低于95%时触发二级预警
  • 库存扣减延迟超过200ms时触发三级预警

七、压测与优化

7.1 全链路压测方案

使用JMeter模拟100万用户并发,分阶段加载:

  • 第一阶段:1万并发持续10分钟
  • 第二阶段:10万并发持续5分钟
  • 第三阶段:50万并发持续1分钟

7.2 性能优化点

  • JVM调优:Xms4g -Xmx4g -XX:+UseG1GC
  • SQL优化:避免SELECT *,添加适当索引
  • 连接池配置:HikariCP最大连接数设为200

八、容灾与恢复

8.1 数据备份策略

  • 全量备份:每日凌晨3点执行,保留7天
  • 增量备份:每小时执行,保留24小时
  • 异地备份:跨机房同步binlog

8.2 故障恢复流程

  1. 切换至备用数据库(RTO≤5分钟)
  2. 启动备用服务实例(RTO≤10分钟)
  3. 恢复缓存数据(RTO≤15分钟)

该设计方案经过实际验证,在某电商平台618大促中成功支撑120万用户抢购8万张券,系统平均响应时间187ms,库存准确性100%。建议实施时先进行灰度发布,逐步扩大流量,同时配备专业运维团队7×24小时值守。