拓冰建站拓冰建站
首页 / 资讯中心 / 正文

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

系统设计 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 幂等执行流程Mermaid5.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操作客户端privateRedisTemplateString,ObjectredisTemplate;// 注入数据库DAOprivateCouponInfoDaocouponInfoDao;/** * 优惠券基础信息缓存预热 * param batchId 券批次ID */publicvoidpreheatCouponCache(LongbatchId){// 1. 从数据库批量查询该批次所有券静态信息ListCouponInfocouponListcouponInfoDao.listByBatchId(batchId);// 2. 遍历数据批量写入Redisfor(CouponInfoinfo:couponList){StringcacheKeycoupon: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 整体架构消息体三大核心字段全局唯一幂等 IDMID幂等判断的唯一依据用户 IDUserID接收优惠券的目标用户券批次 IDBatchID待发放优惠券批次系统独立创建幂等处理记录表专门存储已完成消费的幂等 ID。5.3 幂等执行流程Mermaid已存在不存在消息队列推送发券消息解析消息幂等ID用户ID券批次ID查询幂等记录表判断ID是否已处理判定为重复消息直接丢弃执行正常发券逻辑将当前幂等ID写入记录表流程结束图表说明上图为消息幂等处理全流程。消息消费前优先查询幂等记录表若 ID 已存在说明消息已处理直接拦截若 ID 不存在则执行发券逻辑并记录幂等 ID保证后续重复消息不再生效。5.4 幂等 ID 生成方案幂等 ID 硬性要求全局唯一行业主流两种实现方案雪花算法SnowFlake开源分布式 ID 生成算法基于时间戳、机器码、序列号组合生成全局唯一 ID适配分布式集群高并发场景性能优异是互联网公司首选方案。数据库自增 ID单独创建一张自增 ID 表利用数据库主键自增特性生成唯一编号。实现简单、上手门槛低适合中小体量业务、单体应用。5.5 幂等判断核心伪代码/** * 发券消息消费 幂等判断逻辑 */publicclassCouponMessageConsumer{privateRedisTemplateString,ObjectredisTemplate;privateCouponServicecouponService;// 消息消费入口publicvoidconsumeCouponMsg(CouponMessagemessage){Stringmidmessage.getMid();// 幂等IDLonguserIdmessage.getUserId();// 用户IDLongbatchIdmessage.getBatchId();// 券批次ID// 1. 幂等判断查询Redis判断该ID是否已处理StringidempotentKeyidempotent:mid:mid;BooleanisExistsredisTemplate.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 插件化改造、定制化中间件等。在生产环境选型时需遵循以下原则优先选用通用、成熟、经过大规模线上验证的技术方案小众定制化方案、非通用插件虽可临时解决问题但生态薄弱、排查困难、兼容性差线上风险较高综合考量稳定性、运维成本、学习成本择优落地。八、防重复领券️ Redis 集合去重筑牢业务防线限制用户重复领券、超额领券是优惠券系统必备的基础能力。依托Redis Set 集合的天然去重特性可高效实现该需求。8.1 实现原理Redis Set 集合特性元素唯一重复元素无法写入。我们将「券批次 ID 用户 ID」作为集合元素记录已领券用户。用户发起领券请求时先校验集合中是否存在当前用户标识以此判断是否允许领券。8.2 执行流程用户发起领券请求查询 Redis Set判断用户 ID 是否已存在已存在 → 拦截请求提示 “已领取不可重复领取”不存在 → 执行发券逻辑同时将用户 ID 写入 Set 集合8.3 防重领券伪代码/** * Redis Set 实现防重复领券 */publicclassCouponReceiveController{privateRedisTemplateString,ObjectredisTemplate;privateCouponServicecouponService;publicStringreceiveCoupon(LonguserId,LongbatchId){// 拼接集合Key一个券批次对应一个Set集合StringsetKeycoupon:receive:batch:batchId;// 1. 判断用户是否已领券LongcountredisTemplate.opsForSet().size(setKey);BooleanmemberExistredisTemplate.opsForSet().isMember(setKey,userId);if(Boolean.TRUE.equals(memberExist)){return您已领取该优惠券请勿重复操作;}// 2. 执行领券逻辑booleanresultcouponService.receive(userId,batchId);if(result){// 3. 领取成功将用户ID加入Set集合redisTemplate.opsForSet().add(setKey,userId);return领券成功;}return领券失败券已抢完;}}代码说明利用 Redis Set 实现用户领券去重借助isMember判断用户领取状态领取成功后调用add写入用户 ID天然规避重复领取问题读写性能可支撑大促高并发流量。九、总结与感悟 小业务藏大架构一枚小小的优惠券看似是简单的营销功能实则串联起分库分表、热点流量治理、缓存架构、消息队列、幂等性、分布式锁、并发防重等全套分布式高并发技术体系。从数据记录的细微规则到海量流量的全局治理从单接口并发防护到全链路架构优化每一处设计都围绕性能、安全、稳定三大核心展开。深耕业务场景拆解技术难点方能在高并发架构之路上稳步前行。希望本文的流程拆解、代码示例、图文分析能为各位开发者带来参考与启发✨。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门