5

红包系统的设计

 1 year ago
source link: https://jiajunhuang.com/articles/2023_01_31-red_envelope.md.html
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.
neoserver,ios ssh client

红包系统的设计

秒杀系统中,很常见的一种就是抢红包,还有其他的例如:双11抢购、直播间秒杀下单、一元夺宝等等。其中抢红包的特点在于无法超售, 对于下单,如果比库存超售少许,其实是可以接受的,但是对于红包,一旦用户抢到的钱比发出去的钱更多,那就是大问题了。当然, 抢购也有特定的业务复杂度,例如下单之后,用户可能没有付款,超时之后需要重新把库存加回去等。我们这里主要讨论红包系统。

对于红包系统,我们需要保证两点:

  1. 扛得住并发,用户抢红包的时候带来的流量峰值是非常高的;
  2. 不能超售,数据一定要准确

那么我们怎么处理这两个问题呢?

处理峰值(削峰)

对于单个红包来说,红包的可抢人数是有限制的,例如200个,500个,再往上就比较少了,而参与的用户,大部分只要看到红包就会 参与点击,并且大都是多次点击,直到抢完。因此,这种行为带来的一个特点,就是红包发出以后,会迎来一波比红包个数大很多的 流量,但是超过红包个数的流量基本是无用的流量。因此,对于红包系统,我们需要在请求到达真正处理的后端服务之前,加上如下 防火墙,对流量进行过滤:

  • 用户组织层限流:限制群组大小,这样就限制了一个红包最大的参与人数,例如一个群组最大2万人。但是有的场景下无法限制,例如 直播间。这一层面的限制取决于产品形态

  • 用户端限流:浏览器、App、小程序等用户参与游戏的客户端,我们可以进行限速操作,例如1s点击一次,点击后1s内按钮置灰不可 再次点击

  • 网关层限流:CDN,Nginx,限速,从这里开始,就涉及到服务端的处理了,这里就涉及到多种策略,例如针对IP限速,针对用户限速等

  • 服务层限流:这里需要代码进行处理,例如设置当前参与人数超过红包总数3倍,直接拒绝当前请求,一般会用Redis来存储这个信息

  • 应用层限流:当红包抢完之后,可以在Redis中记录一个标记,应用检测到这个信息,就不需要进行后面的操作了,可以直接拒绝

数据准确(加锁)

如何保证红包的数据准确性呢?如果是使用数据库来存储的话,就需要使用事务,加上行锁,也就是 SELECT ... FOR UPDATE 语句, 如果是使用Redis来存储的话,就需要使用CAS操作,由于Redis中的事务并不保证ACID,但是Lua脚本是原子执行的,因此需要使用Lua 脚本来进行操作。

对于红包的分配,有两种方案:

  • 预分配。发送红包的时候,就提前分配好每一个人能抢到多少,抢红包的时候,取一个未抢的金额出来,分配给对应用户,并且更新状态, 此处需要保证一个用户在一个红包下,只能抢一次。

  • 实时分配。发送红包的时候不分配,在抢红包的时候进行分配,根据红包的剩余金额和剩余个数,计算一个金额给当前用户,此处也许要 在数据库层面保证一个用户在一个红包下,只能抢一次。

红包分配算法可以参考微信红包的分配算法:随机,额度在0.01和剩余平均值*2之间。

数据落地方案

  • 数据库事务 + SELECT ... FOR UPDATE,通过事务保证ACID,通过行锁保证不会产生并发更新错误。
  • 数据库集群(分库sharding),当一个数据库吃不住的时候,可以使用集群来扛并发。
  • Redis Lua 脚本,当集群扛不住的时候,就可以考虑使用Redis来进行操作了。但是最终数据还是需要落库到数据库。

因此我实现时,采用的是第一种方案,也就是直接数据库+行锁来实现。使用数据库实现时,需要注意的是避免死锁场景,我就踩到 死锁的坑导致数据库连接池爆了,排查了好一会儿。这种方案对于数据库本身有一定要求,尤其是磁盘IOPS,需要好好的调校数据库以 得到一个比较好的并发性能。

对于红包本身,我认为缓存的必要性不大,因为每一次成功抢红包,都会导致缓存失效,还是需要数据库参与。与之相对的,反而是红包 的基本信息更加管用,例如:

  • 当前红包是否已经抢完?可以用于提前拒绝请求
  • 当前用户是否抢过?可以用于提前拒绝请求
  • 当前用户是否最近1s抢过?可以用于限速

把这些信息写到缓存里,更有助于提高整个系统的并发能力。

这篇文章中,总结了一个红包系统需要用到的知识,以及如何取处理流量和如何设计系统。


StackGuard的作用 Go DiskQueue源码阅读 NSQ源码分析 NSQ简明教程 结合Redis与MySQL实现又快又好的数据方案 程序员的MySQL手册(五):索引优化 程序员的MySQL手册(四):索引设计 程序员的MySQL手册(三):数据库设计 Linux窗口管理器下的截图 Go设计模式:facade模式和观察者模式 程序员的MySQL手册(二): 监控与benchmark Go设计模式: 责任链模式 我们真的需要这么复杂的技术栈吗? Go设计模式:装饰器模式 程序员的MySQL手册(一): 安装,基本配置



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK