目录
前言
之前有一篇文章已经简单描述了红包项目还有一些技术点和方案
18年面头条也面过抢红包的大体流程
本文将对抢红包项目做简单的分析
红包项目和秒杀、抽奖项目比较类似,都存在高并发流量
系统能不能支撑万级QPS
要考虑到系统的可用性和稳定性,也要考虑到用户的体验
读本文前可先简单浏览下上面2篇博客哈
读完本文后再读下文末贴的一些链接=-=
红包种类
金额红包分为随机红包、等额红包
等额红包没什么好说的,金额大小的红包
为什么我们发微信红包,需要设置领取人数呢,会根据我们设置的领取人数也可以说是红包个数来计算可能抢到的金额
还有支付包口令红包,答对一个口令才能抢红包;
还有QQ语音红包等等
这种应该是抢之前做一层校验,完成了某个事情,会做判断放开
还有一种抖音直播现在已经有的礼物红包,就是发的红包是礼物,抢到的是礼物
抢到的礼物到背包又可以刷给主播
这里我们可以想想礼物红包在设计和流程上有哪些可能和金额红包不一样呢
红包的种类很多,玩法也很多
随便一个地方精心设计,都可以有很多变动
红包基础属性
发红包用户id
总金额
总个数
剩余金额、剩余个数 (有个方案我们可以不需要这2个字段)
红包项目挑战
抢红包的高QPS
尽量让红包抢完
红包金额尽可能随机
避免资损
可用性和稳定性要求比较高
核心模块
发红包
抢红包
拆红包
其他功能:
是否支持在原有红包追加红包
手气最佳
发红包、抢红包历史记录
发红包、抢红包消息广播提醒 (xxx领取了yyy的红包)
发红包
红包表是需要分库分表的
发红包好说就是初始化红包数据落库
在往下方案的设计会根据业务场景就不太一样了
是要求红包立即发出去,立即抢;
还是红包发出去,倒计时抢;
还是红包发出去还在后台排队,到这个排队的红包展示了再抢呢
先说普通的微信红包,抢出去立马要抢,红包数据落库后,需要同步到redis
redis存下红包剩余数量、剩余金额
每次抢到一个就减
并落抢红包记录
拆红包 + 抢红包
把一个红包拆成数个不同金额的小红包,小包的金额总合 = 原红包
微信红包有个逻辑我猜测,就是先点红包,出来一个弹窗,这一步是计算你能不能抢红包或者抢到红包金额,
下一步点弹窗上的抢,才是真正的抢 (仅猜测…)
在没有拆包预处理时:
抢红包时触发拆包,计算本次抢红包金额,减redis中红包金额,减redis红包个数
【拆包策略预处理】
我们根据红包个数预先把红包拆好,拆成多个子包,把所有的子包放在redis set里
抢红包就是从set里随机pop一个就行,再同步落db,说明抢红包成功,
pop失败就抢红包失败
【抢红包】
首先需要分布式锁,禁止一个用户抢2次红包
还有一点要说的就是正式抢红包前可以由客户端请求token,token包含一些信息,比如你是否会发起请求,是否有机会抢到红包
比如canRequest = false , 用户直接不会请求到后端,客户端简单mock返回;canGrab = false ,请求后端但不会抢到红包
我们要认识到的是如果抢红包的人很多,那么很多流量都是无效流量,能过滤一部分是一部分,
不要让这么多无效流量打到我们的服务,我们的缓存,甚至db,甚至说我们压根不让他请求到后端
真正抢红包时我们把请求随机打散到0~3s, 所以前端会展示loading界面,等待抢红包结果
抢到红包,就落抢红包记录
如果按照我们预先拆包的策略,我们还需要分布式锁锁住子包,防止在redis故障主从切换时一个子包被2个用户抢
拆红包算法
在网上找到一个据说是数年前微信的拆红包算法
class RedPackage {
int id;
int userId;
int remainSize;
long remainMoney;
}
/**
* 抢红包最重要的莫过于拆包
* 拆的金额要保证尽可能随机,还要保证不能出现资损
*
* 先抢后抢拿到红包的大小的期望是大致相等的,所以还是先下手抢吧
* 后抢的人方差大(依赖前面人抢的多少),波动较大,有较大几率拿到手气最佳
*
* 前面的人抢的越少,后面的抢的越多 (remainMoney大,产生大的随机值可能就大)
*
**/
public static double getRandomMoney(RedPackage redPackage) {
// remainSize 剩余的红包数量
// remainMoney 剩余的钱
if (redPackage.remainSize == 1) {
redPackage.remainSize--;
return (double) Math.round(redPackage.remainMoney * 100) / 100;
}
Random r = new Random();
double min = 0.01;
double max = redPackage.remainMoney / redPackage.remainSize * 2;
double money = r.nextDouble() * max; //13.252960381435447
money = money <= min ? 0.01: money;
money = Math.floor(money * 100) / 100; //13.25
redPackage.remainSize--;
redPackage.remainMoney -= money; // 100 - 13.25 =86.75
return money;
}
算法主要是:
抢红包随机金额 = [0.01 , 剩余金额 / 剩余个数 * 2)
至于为啥是 剩余金额 / 剩余个数 * 2 应该是让抢红包的随机金额范围更大
简单测试一下:
public static void main(String[] args) {
RedPackage redPackage = new RedPackage();
redPackage.remainMoney = 100;
redPackage.remainSize = 10;
while (redPackage.remainSize > 0) {
double randomMoney = getRandomMoney(redPackage);
System.out.println(randomMoney);
}
}
输出:
19.02
7.91
6.4
2.53
4.76
18.23
0.24
7.1
9.62
19.0
分布还算均匀,随机性也不错
抢红包策略
1,完全公平公正
2,加有一定的定制化 筛选高优用户或者新手光环用户,让他抢到红包概念变大或者让他抢到大红包 【这个处理相对复杂】
非核心流程异步化
需要异步化:
抢到红包广播消息
抢到红包落手气最佳zset
抢到金额兑换(异步转账)
手气最佳
维持一个zset榜单,score就是抢到的金额
这里需要注意的地方就是,如果抢红包的人特别多,可能会出现大key
所有我们新增元素后需要压缩下榜单大小
参考下文
参考资料
https://www.zhihu.com/question/22625187
https://www.zybuluo.com/yulin718/note/93148
https://my.oschina.net/u/3770578/blog/1815309?_from=gitee_search
https://blog.52itstyle.vip/archives/5103/
https://blog.csdn.net/u013985664/article/details/97274587
说点什么
您将是第一位评论人!