目标:设计一个能支撑 10W QPS 的优惠券系统,并覆盖券的完整生命周期。

需求背景

App Store 抽成高,独立网页充值后,需要优惠券引导用户走网页充值。多个业务方需要发券、查券、核销,并且 QPS 高。

需求拆解

  • 活动管理
  • 券模板管理
  • 券记录管理
  • C 端领券与核销

管理端负责配置与统计,C 端负责领取与使用。

中间件选型

  • MySQL:活动、模板、券记录存储
  • Redis:模板缓存与库存扣减
  • MQ:延迟处理券过期状态
  • gRPC:内部服务调用

API 设计

管理端:

  • 活动管理
  • 券模板管理
  • 券记录统计

C 端:

  • GetCampaignMeta
  • GetCouponMeta
  • ListCouponRecord
  • ClaimCoupon
  • LockCoupon
  • ConsumeCoupon
  • UnlockCoupon

数据库设计

活动表

CREATE TABLE `campaign_meta_tab` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL COMMENT '活动名称',
  `description` varchar(255) NOT NULL COMMENT '活动描述',
  `campaign_type` tinyint(4) NOT NULL COMMENT '活动类型',
  `start_time` datetime NOT NULL COMMENT '活动开始时间',
  `end_time` datetime NOT NULL COMMENT '活动结束时间',
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '活动状态',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `ext_data` JSON COMMENT '扩展字段',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

券模板表

CREATE TABLE `coupon_meta_tab` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL COMMENT '券模板名称',
  `logo` varchar(255) NOT NULL COMMENT '券模板logo',
  `description` varchar(255) NOT NULL COMMENT '券模板描述',
  `coupon_type` varchar(255) NOT NULL COMMENT '券模板分类',
  `campaign_id` int(11) NOT NULL COMMENT '活动id',
  `status` tinyint(4) NOT NULL COMMENT '券模板状态',
  `product_line` varchar(255) NOT NULL COMMENT '产品线',
  `coupon_count` int(11) NOT NULL COMMENT '券数量',
  `coupon_code` char(6) NOT NULL COMMENT '券码',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `ext_data` JSON COMMENT '扩展字段',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

券记录表

CREATE TABLE `coupon_record_tab_[000-999]` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `coupon_template_id` int(11) NOT NULL COMMENT '券模板id',
  `coupon_code` varchar(255) NOT NULL COMMENT '券码',
  `status` tinyint(4) NOT NULL COMMENT '券状态',
  `user_id` bigint(20) NOT NULL COMMENT '用户id',
  `used_time` datetime DEFAULT NULL COMMENT '使用时间',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `expirey_time` datetime NOT NULL COMMENT '过期时间',
  `ref_order_no` varchar(255) DEFAULT NULL COMMENT '外部关联订单号',
  `ext_data` JSON COMMENT '扩展字段',
  PRIMARY KEY (`id`),
  INDEX `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

核心流程

发券

  • 参数校验
  • 幂等校验
  • 扣减库存
  • 写券记录

锁券

  • 校验是否过期
  • 状态从 available 改为 locked

核销

  • 支付成功后从 locked 改为 consumed

解锁

  • 订单取消时从 locked 改回 available

高并发问题与解决

存储瓶颈

现实指标:

  • MySQL 单机写入约 1w TPS
  • Redis 单分片写入约 2w TPS

解决:

  • MySQL 读写分离
  • MySQL 水平分库分表
  • Redis 分片扩容

热点库存

单模板库存集中在一个 key 上会形成热点。

解决:库存拆分为子库存,扣减时轮询子 key。

券模板获取失败

Redis 超时会导致发券失败。

解决:

  • 内部重试
  • 二级缓存(本地缓存)

二级缓存可显著提高成功率,但要注意一致性与缓存刷新策略。

服务治理

超时设置

接口执行在 100ms 以内,超时设置可放大到 500ms 以防止上游拖死。

监控与报警

  • 核心接口成功率
  • 系统 CPU 与内存
  • Redis/MQ 延迟

限流

多业务方调用时要做合理限流,避免被单一上游拖垮。

资源隔离

跨可用区部署,避免单区故障导致服务不可用。

压测建议

  • 先找到单实例瓶颈
  • 再找到 MySQL 与 Redis 的单机瓶颈
  • 基于瓶颈推算整体资源

压测资源建议是线上需求的 1.5 倍,保证突发流量。

总结

优惠券系统的关键点不在 CRUD,而在高并发与一致性。核心策略是:

  • 水平扩展存储
  • 拆分热点库存
  • 二级缓存兜底
  • 完整的服务治理