1280 字
6 分钟
微信红包的架构设计 & Demo

微信红包的架构设计 & Demo#

1. 微信的金额什么时候算?#

答:微信金额是拆的时候实时算出来,不是预先分配的,采用的是纯内存计算,不需要预算空间存储。采取实时计算金额的考虑:预算需要占存储,实时效率很高,预算才效率低。

2. 实时性:为什么明明抢到红包,点开后发现没有?#

答:2014 年的红包一点开就知道金额,分两次操作,先抢到金额,然后再转账。 2015 年的红包的拆和抢是分离的,需要点两次,因此会出现抢到红包了,但点开后告知红包已经被领完的状况。进入到第一个页面不代表抢到,只表示当时红包还有。

3. 分配:红包里的金额怎么算?为什么出现各个红包金额相差很大?#

答:随机,额度在 0.01 和剩余平均值 *2 之间。 例如:发 100 块钱,总共 10 个红包,那么平均值是 10 块钱一个,那么发出来的红包的额度在 0.01元~20元 之间波动。 当前面3个红包总共被领了 40 块钱时,剩下 60 块钱,总共 7 个红包,那么这 7 个红包的额度在:0.01~(60/7*2)=17.14 之间。 注意:这里的算法是每被抢一个后,剩下的会再次执行上面的这样的算法。

这样算下去,会超过最开始的全部金额,因此到了最后面如果不够这么算,那么会采取如下算法:保证剩余用户能拿到最低 1 分钱即可。

如果前面的人手气不好,那么后面的余额越多,红包额度也就越多,因此实际概率一样的。

Demo

package main
import (
"fmt"
"math/rand"
"time"
)
// 最少1分钱
const MinAmount = int64(1)
func main() {
// 总金额100元(转为最小分单位),发10个红包
count, amount := int64(10), int64(10000)
var res []float64
remain := amount
for i := int64(0); i < count; i++ {
x := CalculateEnvelop(count-i, remain)
remain -= x
res = append(res, float64(x)/100)
}
fmt.Println(res)
}
// CalculateEnvelop 计算随机红包序列
// count - 红包数量
// amount - 总金额(单位:分)
func CalculateEnvelop(count, amount int64) int64 {
if count == 1 {
return amount
}
// 2倍均值范围,避免分散过大
maxAvailable := ((amount - count*MinAmount) / count) * 2
//fmt.Println(maxAvailable)
rand.Seed(time.Now().UnixNano())
x := rand.Int63n(maxAvailable) + MinAmount
return x
}

运行结果:
[18.63 4.3 4.37 0.89 19.92 11.11 7.33 7.66 20.06 5.73]
[15.58 5.94 11.12 14.19 12.39 6.02 10.71 14.95 1.96 7.14]
[7.98 10.51 13.59 2.47 6.97 6.78 24.2 2.22 2.27 23.01]
[6.27 6.71 15.48 3.24 21.55 9.69 11.77 5.79 3.05 16.45]
[5.98 13.95 9.98 17.06 3.8 0.33 2.06 19.64 26.4 0.8]


方案优化?(之后将带来多种优化方案)
比较先抢、后抢的公平性;
性能测试比较;
先计算红包序列,还是抢的时候再计算?

4. 红包的设计#

答:微信从财付通拉取金额数据过来,生成个数/红包类型/金额放到 redis 集群里,app 端将红包 ID 的请求放入请求队列中,如果发现超过红包的个数,直接返回。根据红包的裸祭处理成功得到令牌请求,则由财付通进行一致性调用,通过像比特币一样,两边保存交易记录,交易后交给第三方服务审计,如果交易过程中出现不一致就强制回归。

5. 并发性处理:红包如何计算被抢完?#

答:cache 会抵抗无效请求,将无效的请求过滤掉,实际进入到后台的量不大。cache 记录红包个数,原子操作进行个数递减,到 0 表示被抢光。财付通按照 20万 笔每秒入账准备,但实际还不到 8万 每秒。

6. 通如何保持 8w 每秒的写入?#

答:多主 sharding,水平扩展机器。

7. 数据容量多少?#

答:一个红包只占一条记录,有效期只有几天,因此不需要太多空间。

8. 轮询红包分配,压力大不?#

答:抢到红包的人数和红包都在一条 cache 记录上,没有太大的查询压力。

9. 一个红包一个队列?#

答:没有队列,一个红包一条数据,数据上有一个计数器字段。

10. 有没有从数据上证明每个红包的概率是不是均等?#

答:不是绝对均等,就是一个简单的拍脑袋算法。

11. 拍脑袋算法,会不会出现两个最佳?#

答:会出现金额一样的,但是手气最佳只有一个,先抢到的那个最佳。

12. 每领一个红包就更新数据么?#

答:每抢到一个红包,就 cas(compare and swap) 更新剩余金额和红包个数。

13. 红包如何入库入账?#

数据库会累加已经领取的个数与金额,插入一条领取记录。入账则是后台异步操作(调用财付通实现)。

14. 入帐出错怎么办?比如红包个数没了,但余额还有?#

答:最后会有一个 take all 操作。另外还有一个对账来保障。


@来源:QCon某高可用架构群整理

微信红包的架构设计 & Demo
https://blog.zhequtao.com/posts/golang/go-project/go电商设计手册/material/astraw/market/red-envelop/
作者
阿俊
发布于
2020-05-10
许可协议
CC BY-NC-SA 4.0

相关阅读