目标:设计一个能支撑 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,而在高并发与一致性。核心策略是:
- 水平扩展存储
- 拆分热点库存
- 二级缓存兜底
- 完整的服务治理