38

秒杀场景实践之抢红包(一) —— 常用解决方案

 4 years ago
source link: https://www.tuicool.com/articles/FZbeE3b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

7NN7Vj3.jpg!web

九月 1, 2019 朴瑞卿

目录

  • 秒杀场景实践之抢红包(一) —— 常用解决方案

秒杀场景实践之抢红包(一) —— 常用解决方案

秒杀场景实践之抢红包常用解决方案

文章地址: https://blog.piaoruiqing.com/blog/2019/09/01/秒杀场景实践之抢红包一/

前言

秒杀场景在生活中几乎随处可见, 不论是商品抢购、春运抢票还是一个随处可见的红包, 都会涉及到秒杀的场景. 在面试中, 秒杀业务的设计也成为热门题目为面试官和应聘者津津乐道.

接下来, 本文将针对秒杀场景中的抢红包实现方案进行分享, 包括红包业务常见的实现方案, 瓶颈及优化.

分析

场景

红包的应用场景有很多, 如随机红包、定额红包等, 甚至还有结合其他促销业务的红包变种如抢购物津贴等. 但从技术的角度来看, 不论玩法有多少变化, 其核心都是相似的:

  • 稳定: 扛得住突发的大流量, 确保红包都能成功派发.
  • 准确: 数据一定要正确, 不能出现超额派发的情况.

业务

抢红包可能会由于业务需求不同而产生很多变种, 设计上要足够抽象, 不能为了抢现金红包和抢购物津贴红包写多份相似的代码. 抢到红包的后置操作可以作为消息, 由不同的业务模块自行处理.

技术

抢红包核心业务不复杂, 其关键点在于应对高并发、资源争用等.

  • 高并发: 异步、横向扩展负载均衡、限流等.

  • 读多写少: 缓存.

  • 资源争用: 原子操作, 缓存或数据库等层面可进行控制. 如使用Lua脚本进行减库存操作.

方案一 —— 预分配

适用场景

红包 数量相对合理 , 很 少产生库存剩余 的情况、用户量级不大的情况.

  • 优势: 实现简单、配合缓存很容易应对高并发
  • 不足: 频繁发放较多数量大的红包会导致一次性写入大量分配记录, 如果领取的人不多, 会产生很多无效数据.

简要描述

预分配是在发放红包时, 根据红包总额和数量、按照既定算法进行分配, 提前创建好全部的红包分配记录. 领取时只是将红包分配记录进行更新.

比较适合系统发放的红包(面向某一标签的全部用户群体, 发出的红包基本会被领取完), 不适合用户群组红包(无法控制领取红包人数, 当红包个数远大于群组人数的情况下, 无效数据比较多, 比如在一个10人群组发放一个数量为1000的红包).

实现细节

graph LR
A(创建红包, 个数为N) --> B(预分配N条红包领取记录)
B --> C(开始抢红包)
C --> D(先到先得)

流程

  • 在红包开抢前, 预先分配好红包领取记录, 领取记录的用户ID为负值.

  • 开抢后, 开放唯一领取红包的入口

  • 领取操作核心就是更新红包分配记录:

    -- 此处划重点 ( ̄▽ ̄)"
    UPDATE IGNORE record SET user_id = {userId}, gmt_receive = UNIX_TIMESTAMP() WHERE red_envelop_id = 1 AND user_id < 0 LIMIT 1;
    -- red_envelop_id + user_id 有唯一约束

红包发放记录

ID 总金额 数量 ... 1 100 3 ...

红包分配记录

unique: 红包ID + 领取用户ID

ID 红包ID 金额 领取用户ID 领取时间 ... 1 1 10 -1 0 ... 2 1 60 -2 0 ... 3 1 30 -3 0 ...

备注

  • UPDATE IGNORE ... LIMIT 1 : 解决了资源争用问题, 确保并发请求下红包的领取的数据正确性.
  • red_envelop_id + user_id : 创建索引并唯一约束, 确保对于同一个红包同一用户只能领取一次.
  • 预分配的 user_id 为负值: 因为 red_envelop_id + user_id 有唯一约束.
  • 对于一般流量的小型活动, 这种方式实现简单、成本低, 不引入缓存的情况下只用一个MySQL基本也能扛得住.

[版权声明]

本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接: https://blog.piaoruiqing.com .

如有授权方面的协商或合作, 请联系邮箱: [email protected] .

方案二 —— 实时分配

适用场景

领取人数无法估计、频发退款, 如群组红包(经常发生剩余退款)

实现细节

graph LR
A(创建红包, 个数为N) --> B(首次写入缓存)
B --> C(抢红包)
C --> D(读取或加载缓存)
D --> E(分配红包金额)
E --> F(写入缓存--原子操作)
F --> G(落库)

流程

  • 开抢前将红包信息加载到缓存, 首次加载时间可长一些
  • 抢红包: 从缓存读取(没有则加载), 分配红包后原子更新缓存(若已发放完毕则直接返回失败)
  • 缓存更新后写入数据库(校验数据正确性)

红包发放记录

ID 总金额 数量 剩余金额 剩余数量 ... 1 100 3 100 3 ...

红包分配记录

unique: 红包ID + 领取用户ID

ID 红包ID 金额 领取用户ID 领取时间 ... ... ... ... ... ...

备注

  • 首次缓存加载时间要稍长一点: 红包刚开始发放时可能会有较大的突发流量, 此时去DB加载缓存不合适.
  • 缓存可以不用和数据库保证强一致: 数据的正确性由数据库进行维护, 如: 缓存扣除了红包额度, 但更新数据库时发生了异常, 此时缓存不需要回滚, 待缓存失效后重新加载即可.(所以缓存时间可以是几秒钟, 不用太长)
  • 更新缓存可以考虑使用Lua脚本以保证原子性.
  • 实时分配红包不会产生无效的记录, 适合大多数场景, 但实现比预分配复杂的多.

细节及优化

  • 客户端点击频率控制能在一定程度上减少流量.

  • 红包领光后在缓存一层拦截掉全部请求, 直接返回失败.

  • 网关层进行限流.

结语

秒杀场景其特点是高并发、读多写少、资源争用, 每一个点都需要根据其业务场景选择适合的解决方案, 如使用缓存解决频繁读取的问题、使用队列解决数据库性能瓶颈等.

对于抢红包业务来说, 预分配和实时分配都是行之有效的方案, 各有优劣, 具体选择哪种, 还是要看业务需求.

© 2019,朴瑞卿.

[版权声明]

本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接: https://blog.piaoruiqing.com . 如有授权方面的协商或合作, 请联系邮箱: [email protected] .

欢迎关注公众号: (代码如诗) iYFFBbB.jpg!web

3+


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK