系统设计 022:券系高并发架构深度剖析
2026/8/7 3:01:24 网站建设 项目流程

系统设计 022:券系高并发架构深度剖析

  • Bilibili 同步视频
  • 一、券记录生成规则📜 两类模式,各循章法
    • 1.1 领取触发型记录
    • 1.2 发放触发型记录
    • 流程示意图(Mermaid)
  • 二、券名与券批次🏷️ 内外分野,泾渭分明
    • 2.1 对外券名
    • 2.2 内部批次名
    • 对比表格
  • 三、热点 Key 问题🔥 流量倾斜之困,分流破局之法
    • 3.1 问题成因
    • 3.2 主流解决方案:热点数据分片打散
    • 热点 Key 分流原理图(Mermaid)
  • 四、缓存预热🌡️ 读多写少场景,前置优化降库压
    • 4.1 核心概念
    • 4.2 典型业务场景:电商秒杀 / 大促领券
    • 4.3 缓存预热伪代码(Java 风格)
    • 4.4 写缓存优化:Redis Lua 脚本
  • 五、消息队列与幂等设计🔒 杜绝消息重复,防止优惠券超发
    • 5.1 幂等核心定义
    • 5.2 幂等 ID 整体架构
    • 5.3 幂等执行流程(Mermaid)
    • 5.4 幂等 ID 生成方案
    • 5.5 幂等判断核心伪代码
  • 六、消息队列与缓存预热⚖️ 功能分野,各司其职
  • 七、Redis 分布式锁 & RedLock🗝️ 突破单机性能瓶颈
    • 7.1 优化方案:RedLock(红锁)
    • 7.2 技术方案选型原则
  • 八、防重复领券🛡️ Redis 集合去重,筑牢业务防线
    • 8.1 实现原理
    • 8.2 执行流程
    • 8.3 防重领券伪代码
  • 九、总结与感悟📖 小业务藏大架构

📝 导读:每逢电商大促,优惠券发放与领取链路便会直面海量流量冲击。从数据落库规则、券标识划分,到热点流量倾斜、缓存优化、消息队列幂等防重、分布式锁、防重复领券等一系列经典问题,皆是分布式高并发场景下的核心考点。本文以业务落地为根基,辅以图文、伪代码、流程拆解,以雅致行文结合技术实操,全方位拆解优惠券系统架构设计与问题解决方案。


Bilibili 同步视频

系统设计 022:券系高并发架构深度剖析


一、券记录生成规则📜 两类模式,各循章法

优惠券业务中,数据记录表的生成时机,历来分为主动发放被动领取两大范式,二者场景相异、逻辑有别,一静一动,各司其职。

1.1 领取触发型记录

🎯 适用场景:用户自主点击领券、活动页手动领取优惠券
逻辑要义:无领取动作,则无数据落地。唯有用户发起领取请求的刹那,系统方才创建对应券记录,数据与用户行为强绑定。

1.2 发放触发型记录

🎯 适用场景:商家后台批量发券、定向用户推送券、会员权益自动发券
逻辑要义:商家执行发券动作,数据即刻生成。无需用户进行任何操作,发券指令下达完成,记录表同步落地,属于平台主动推送模式。

流程示意图(Mermaid)

用户手动领取

商家批量发放

券业务发起

模式判断

触发领券动作

新建券领取记录

执行批量发券逻辑

批量生成券发放记录

图表说明:上图清晰区分两种券记录生成链路。左侧分支为用户主动领券流程,以用户操作为驱动创建记录;右侧分支为平台主动发券流程,由后台运营操作直接批量生成数据,二者构成优惠券数据存储的两大基础模式。


二、券名与券批次🏷️ 内外分野,泾渭分明

券名与券批次是券体系中两大基础标识,一面向用户,一服务内部,用途、受众、设计目标截然不同,亦是日常开发中极易混淆的知识点。

2.1 对外券名

面向前端展示、终端用户可见,是优惠券对外的 “名片”。例如劳斯莱斯5元代金券,作用为视觉展示、用户识别、活动宣发,侧重体验与传播。

2.2 内部批次名

仅用于研发、运维、运营内部协作,终端用户完全无感知
同一类优惠券,可在双十一、618、年货节等多轮大促重复上线,仅凭对外券名无法区分发放场次。而批次名便是为区分发券批次、隔离活动数据、追溯发放链路而生,依托批次标识,运维可快速定位每一轮发券任务、统计核销数据、排查线上问题。

对比表格

标识类型面向对象核心作用用户可见性应用场景
对外券名终端用户展示、识别、营销宣传✅ 可见前端页面、个人卡包、订单结算页
券批次名内部人员区分批次、数据隔离、问题排查❌ 不可见后台发券、数据统计、日志追溯

表格说明:本表从使用对象、核心价值、可见范围、落地场景四个维度,对比券名与券批次的差异,帮助开发者快速厘清二者设计初衷与使用边界。


三、热点 Key 问题🔥 流量倾斜之困,分流破局之法

在分库分表的分布式架构下,大促领券活动极易催生热点 Key(热点数据)问题,这是分布式存储的经典顽疾,轻则服务响应迟缓,重则单库宕机、链路雪崩。

3.1 问题成因

系统为承载海量数据,会将数据拆分至多个数据库节点(如 DB1、DB2、DB3),常态下数据均匀分布、流量均衡。
当爆款优惠券活动上线,整批券数据统一存储在单个数据库节点中,海量领券请求会全部涌向该节点,形成流量单点倾斜。单一数据库承载远超设计阈值的请求压力,CPU、连接数、IO 持续打满,最终引发服务不可用。

3.2 主流解决方案:热点数据分片打散

核心思路:预判热点,拆分总量,多库分流
假设某券批次总发放量为 100000 张,预判为热点数据后,将总量拆分为 10 个子分片,把分片数据均匀分发至 10 个数据库节点。原本集中于单库的流量,被平摊至多个节点,从根源消解单点压力。

热点 Key 分流原理图(Mermaid)

普通数据

热点Key数据

海量领券请求

热点数据预判

路由至原单库DB

拆分券总量为多分片

分片1 → DB1

分片2 → DB2

分片3 → DB3

分片N → DBN

图表说明:该图展示热点 Key 分流完整逻辑。系统首先对券数据做热点预判,普通数据维持原有路由规则;判定为热点数据后,对券数量进行分片拆分,将不同分片路由至不同数据库,实现流量打散、负载均衡。


四、缓存预热🌡️ 读多写少场景,前置优化降库压

高并发互联网业务普遍具备读多写少的特征,数据库磁盘 IO 性能有限,无法直面每秒十万、百万级的查询请求,缓存便成为数据库的第一道屏障,而缓存预热则是活动前置优化的核心手段。

4.1 核心概念

缓存预热:在活动正式开启前,主动将静态不变、高频查询的数据批量加载至内存缓存(Redis)中。用户请求优先读取缓存数据,绕过数据库,大幅降低数据库查询压力。

4.2 典型业务场景:电商秒杀 / 大促领券

商品详情、活动规则、券基础信息等内容,在活动周期内基本不会变动。若每一次用户访问都直连数据库查询,海量请求会持续冲击库表,资源损耗严重。借助缓存预热,可提前完成数据加载。

4.3 缓存预热伪代码(Java 风格)

/** * 缓存预热工具类 - 活动前批量加载静态数据至Redis * 适用:券基础信息、秒杀商品信息、活动文案等静态数据 */publicclassCachePreheatUtil{// 注入Redis操作客户端privateRedisTemplate<String,Object>redisTemplate;// 注入数据库DAOprivateCouponInfoDaocouponInfoDao;/** * 优惠券基础信息缓存预热 * @param batchId 券批次ID */publicvoidpreheatCouponCache(LongbatchId){// 1. 从数据库批量查询该批次所有券静态信息List<CouponInfo>couponList=couponInfoDao.listByBatchId(batchId);// 2. 遍历数据,批量写入Redisfor(CouponInfoinfo:couponList){StringcacheKey="coupon:info:"+info.getCouponId();// 设置缓存,有效期与活动周期一致redisTemplate.opsForValue().set(cacheKey,info,7,TimeUnit.DAYS);}log.info("券批次{} 缓存预热完成,共加载{}条数据",batchId,couponList.size());}}

代码说明:以上为缓存预热简易实现代码。程序主动从数据库批量查询券信息,拼接统一缓存 Key 后写入 Redis,在活动开始前完成数据预加载。活动期间用户查询直接读取 Redis,无需访问数据库。

4.4 写缓存优化:Redis Lua 脚本

缓存不仅承担读请求,也需处理数据更新、数量扣减等写操作。Redis 单条命令可保证原子性,但多条命令组合会存在并发安全问题。
Redis Lua 脚本可将多条指令封装为一个原子脚本执行,完美实现券数量扣减、状态修改等复杂操作,规避并发竞争问题。


五、消息队列与幂等设计🔒 杜绝消息重复,防止优惠券超发

高并发发券场景中,普遍采用消息队列实现异步解耦、流量削峰。但消息队列的底层特性决定了:仅能保证消息至少投递一次,无法严格保证只投递一次。消息重复消费,会直接导致同一用户重复领券、券数量超发,而幂等设计是解决该问题的核心方案。

5.1 幂等核心定义

幂等:同一个请求,无论重复执行多少次,最终业务结果完全一致
落地到发券业务:同一条发券消息,即便被队列重复消费十次、百次,用户也仅能收到一张优惠券,不会出现重复发放。

5.2 幂等 ID 整体架构

消息体三大核心字段:

  1. 全局唯一幂等 ID(MID):幂等判断的唯一依据

  2. 用户 ID(UserID):接收优惠券的目标用户

  3. 券批次 ID(BatchID):待发放优惠券批次

系统独立创建幂等处理记录表,专门存储已完成消费的幂等 ID。

5.3 幂等执行流程(Mermaid)

已存在

不存在

消息队列推送发券消息

解析消息:幂等ID+用户ID+券批次ID

查询幂等记录表,判断ID是否已处理

判定为重复消息,直接丢弃

执行正常发券逻辑

将当前幂等ID写入记录表

流程结束

图表说明:上图为消息幂等处理全流程。消息消费前优先查询幂等记录表,若 ID 已存在,说明消息已处理,直接拦截;若 ID 不存在,则执行发券逻辑,并记录幂等 ID,保证后续重复消息不再生效。

5.4 幂等 ID 生成方案

幂等 ID 硬性要求:全局唯一,行业主流两种实现方案:

  1. 雪花算法(SnowFlake)
    开源分布式 ID 生成算法,基于时间戳、机器码、序列号组合生成全局唯一 ID,适配分布式集群,高并发场景性能优异,是互联网公司首选方案。

  2. 数据库自增 ID
    单独创建一张自增 ID 表,利用数据库主键自增特性生成唯一编号。实现简单、上手门槛低,适合中小体量业务、单体应用。

5.5 幂等判断核心伪代码

/** * 发券消息消费 + 幂等判断逻辑 */publicclassCouponMessageConsumer{privateRedisTemplate<String,Object>redisTemplate;privateCouponServicecouponService;// 消息消费入口publicvoidconsumeCouponMsg(CouponMessagemessage){Stringmid=message.getMid();// 幂等IDLonguserId=message.getUserId();// 用户IDLongbatchId=message.getBatchId();// 券批次ID// 1. 幂等判断:查询Redis判断该ID是否已处理StringidempotentKey="idempotent:mid:"+mid;BooleanisExists=redisTemplate.hasKey(idempotentKey);if(Boolean.TRUE.equals(isExists)){// 重复消息,直接返回,不执行业务log.warn("幂等ID{} 已处理,忽略重复消息",mid);return;}// 2. 执行发券业务couponService.sendCoupon(userId,batchId);// 3. 标记该幂等ID已处理,设置过期时间与业务周期一致redisTemplate.opsForValue().set(idempotentKey,"1",15,TimeUnit.DAYS);}}

代码说明:该代码模拟消息消费与幂等拦截逻辑。以 Redis 存储已处理幂等 ID,利用hasKey做快速判重,拦截重复请求,从代码层面实现发券接口幂等性。


六、消息队列与缓存预热⚖️ 功能分野,各司其职

二者同为高并发架构两大基石,但定位、职责、应用场景天差地别,切不可混淆:

  • 📨消息队列:承接全量业务请求,实现异步处理、流量削峰、服务解耦。核心作用是处理业务请求、削平流量波峰,直面写请求与复杂业务逻辑。

  • 🗄️缓存预热:仅负责静态数据预加载,服务于页面查询、数据展示,不参与业务请求处理。核心作用是加速读请求、降低数据库压力。

一者处置动态请求,一者优化静态查询,相辅相成,共建高并发架构。


七、Redis 分布式锁 & RedLock🗝️ 突破单机性能瓶颈

领券、扣库存等并发争抢场景,常使用 Redis 分布式锁保证数据安全。单机 Redis 锁存在天然短板:所有争抢请求集中于单节点,极易形成请求排队,拉高响应耗时,出现性能瓶颈。

7.1 优化方案:RedLock(红锁)

RedLock 摒弃单节点加锁模式,在多台独立 Redis 节点上同时对同一个 Key 加锁。只有多数节点加锁成功,才判定为锁获取成功。
优势:突破单机硬件与连接数限制,横向扩展集群承载能力,大幅提升锁服务并发上限,适配超大流量领券场景。

7.2 技术方案选型原则

业界存在多种解决方案,如 MySQL 8.0 插件化改造、定制化中间件等。在生产环境选型时,需遵循以下原则:

  1. 优先选用通用、成熟、经过大规模线上验证的技术方案;

  2. 小众定制化方案、非通用插件虽可临时解决问题,但生态薄弱、排查困难、兼容性差,线上风险较高;

  3. 综合考量稳定性、运维成本、学习成本,择优落地。


八、防重复领券🛡️ Redis 集合去重,筑牢业务防线

限制用户重复领券、超额领券,是优惠券系统必备的基础能力。依托Redis Set 集合的天然去重特性,可高效实现该需求。

8.1 实现原理

Redis Set 集合特性:元素唯一,重复元素无法写入
我们将「券批次 ID + 用户 ID」作为集合元素,记录已领券用户。用户发起领券请求时,先校验集合中是否存在当前用户标识,以此判断是否允许领券。

8.2 执行流程

  1. 用户发起领券请求;

  2. 查询 Redis Set,判断用户 ID 是否已存在;

  3. 已存在 → 拦截请求,提示 “已领取,不可重复领取”;

  4. 不存在 → 执行发券逻辑,同时将用户 ID 写入 Set 集合;

8.3 防重领券伪代码

/** * Redis Set 实现防重复领券 */publicclassCouponReceiveController{privateRedisTemplate<String,Object>redisTemplate;privateCouponServicecouponService;publicStringreceiveCoupon(LonguserId,LongbatchId){// 拼接集合Key:一个券批次对应一个Set集合StringsetKey="coupon:receive:batch:"+batchId;// 1. 判断用户是否已领券Longcount=redisTemplate.opsForSet().size(setKey);BooleanmemberExist=redisTemplate.opsForSet().isMember(setKey,userId);if(Boolean.TRUE.equals(memberExist)){return"您已领取该优惠券,请勿重复操作";}// 2. 执行领券逻辑booleanresult=couponService.receive(userId,batchId);if(result){// 3. 领取成功,将用户ID加入Set集合redisTemplate.opsForSet().add(setKey,userId);return"领券成功";}return"领券失败,券已抢完";}}

代码说明:利用 Redis Set 实现用户领券去重,借助isMember判断用户领取状态,领取成功后调用add写入用户 ID,天然规避重复领取问题,读写性能可支撑大促高并发流量。


九、总结与感悟📖 小业务藏大架构

一枚小小的优惠券,看似是简单的营销功能,实则串联起分库分表、热点流量治理、缓存架构、消息队列、幂等性、分布式锁、并发防重等全套分布式高并发技术体系。

从数据记录的细微规则,到海量流量的全局治理;从单接口并发防护,到全链路架构优化,每一处设计都围绕性能、安全、稳定三大核心展开。

深耕业务场景,拆解技术难点,方能在高并发架构之路上稳步前行。希望本文的流程拆解、代码示例、图文分析,能为各位开发者带来参考与启发✨。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询