问题
很多APP为了引流营销创造除了各种玩法,其中红包雨就是常见的一种方式,主要效果就是预告在某个时间点,会发布多少金额的券等等,到点就能够聚集上百万用户来抢,大概效果就是疯狂的戳屏幕,然后偶现几个金额,最后结束后告诉一共抢到xx元。
这里面有两个挑战。
1. 如何扛住这种高并发流量
2. 如何保证不超卖。
主要思路
业务真实意图
很多技术方案挑战很大,可以从业务的角度去进行优化,换个思路可能技术挑战就不大了,例如抢购用户数是固定吗?一定要把所有金额分发出去么?
如果是固定,那我们可以前置,不用在活动的时候去扣金额,而是活动开始前就分配好了,只要考虑分发就行了,这样规避了高并发的超卖控制。
如果金额不一定非要分发出去,那可以不做高可用,某个分片的金额扣不成功就告诉用户失败即可。
下面分享的方案就是基于:不确定人数,允许少量金额分发不出去的方案。
方案设计
存储方案
假设用上百万用户来抢红包雨,那瞬间的TPS能达到百万,磁盘操作耗时高,用主流的MySQL数据库去抗这么大的流量显然不合适,当然有的人会说提前做好分库分表,但是这个工作量巨大并且就为了临时这样一个活动实在性价比不高。
那就想到用redis做存储,单机redis节点峰值能达到10w tps ,依然无法满足要求,那就继续拆分流量,进行分片,负载均衡到不同的分片,把库存在活动前加载到各个节点,理论上10个节点可以够百万tps。但是多节点如何分配金额呢,例如某个分片金额已经抢完,其他分片还有金额,怎么处理?其实这种情况,简单的粗暴的方式就是,用户运气差,仅此而已。对于扣减操作可以用redis的原子操作,避免read-write带来的并发问题。
现在基本架构如下:
高可用
但是这里是否存在单分片可用性的风险,例如某个节点挂了,这个分片的金额不就损失了,这里又有三个方案。
-
配置redis主从节点,设置redis的acks参数,redis主从节点同步成功再返回给用户,但是这个方案会影响redis的吞吐量。
-
挂掉就挂掉,这个分片的金额丢失,业务上可接受。
-
双节点保证,每个分片准备两个同样的实例,并发扣减一个成功就算成功。
抢购效果
对于用户是频繁点击发起的请求,其实不用真实的请求,只要有一次点击或则直接打开页面就可以给用户一个确定的金额即可,后续客户端不用再发起请求,当然客户端也可以用一个用户id去拦截避免爬虫。分配好了金额给用户,前端展示效果可以产品们自己去商议。
总结
其实上面的解决方案,性能还可以进一步提升,redis这一层甚至可以去掉,直接把金额加载到机器的本地内存,这样用本地CAS操作避免了远程IO开销。