抢红包的业务分析
可以明显的看到打开了红包不一定可以抢到。这样做的好处是:
- 符合现实生活逻辑,有仪式感
- 防止误领,发现不对劲可以马上退出
- 流程拆的长一些,平摊高并发下的压力
预拆包:我在发红包的时候,就已经把所有的东西都计算好了放在redis里面了。
实时计算:在抢红包的时候进行现场计算。
我选择的时把红包提前拆好,虽然提前拆好需要占用Redis的一些存储,但是这样可以让抢红包的速度到达最快。
发红包
发红包的时候需要一些算法,这里采用的是二倍均值法
二倍均值法可以在正态分布的前提下做到抢红包相对的公平。计算方法如下:
每次抢到的金额 = 随机区间(0, M/N*2)。
发红包的时候需要指定红包的金额和个数。然后通过二倍均值法计算后放入Redis。这里我们采用的数据结构是List类型,因为List抢走一个红包和红包库存呢减一是一个原子性操作。而且List类型的数据结构就决定了获取库存的时间复杂度是O(1),是可行的。到时候按照顺序弹出即可。
拆红包
用户点击红包的时候查看库存,如果库存为0的话直接显示红包已经抢完了,并且把详细信息显示。如果还有库存的话,就可以进入抢红包的环节了。
抢红包
用户点击抢红包,此时再次判断库存是否充足,如果充足的话判断用户是否抢过红包,如果两个条件都满足的话,就抢红包成功,从Redis里面弹出一个红包给用户,更新对应的Redis(红包总金额要扣除了),然后使用消息队列异步的调用服务,将用户的金额进行相应的加上。
与此同时,需要记录哪些用户已经抢过红包了。
退款
使用延迟队列,24小时后进行消息的消费,如果发现数据库里面还有相应的缓存,那么就需要把剩下的金额退还给用户。具体过程是查看Redis的某个key是否还存在。如果还存在的话那么就把List里面的红包全部弹出,然后把金额归还给用户。然后把红包key删除掉。
高并发场景下可能会遇到的问题
超卖问题
因为查看是否还有库存和扣减库(抢红包)存两个操作不是原子性的,因此我们可以使用Redis+Lua实现原子性操作。
Lua实现抢红包的流程:
- 查询用户是否抢过红包
- 查询是否还有红包
- 有的话就扣减红包Redis弹出一个
- 没有的话就返回
使用 EVAL 命令每次请求都需要传输 Lua 脚本 ,若 Lua 脚本过长,不仅会消耗网络带宽,而且也会对 Redis 的性能造成一定的影响。
思路是先将 Lua 脚本先缓存起来 , 返回给客户端 Lua 脚本的 sha1 摘要。 客户端存储脚本的 sha1 摘要 ,每次请求执行 EVALSHA 命令即可。