高并发问题,是每个程序员、架构师都不可避免的问题,而这类问题无非是并发量大小以及需要多么可靠的区别。在解决这类问题的时候,也遵循着「没有银弹」的法则,一个场景一个设计,不同容忍度不同可靠性。

春节期间的抢红包,这个场景是一个非常鲜活现实的场景、就在我们身边,正好旁听了某司的红包架构设计,浅谈自己的看法权当抛砖引玉。

业务需求

春节红包是一个容忍性非常高的业务场景,他可以允许用户抢不到红包、业务代码没有响应,但不能容忍数据上的不一致以及财务上的损失

所以在这里,「保证数据最终一致性」是我们的 top priority 。

理解了这一点,我们就开始自己的春节红包的架构设计。

红包金额生成

这个场景与微信群抢红包不太一致,这相当于是几亿人抢一个群红包,所以不能够动态生成金额数量。

因为超高并发场景下动态生成金额是有风险的,而且是风险极高的。若是超发红包,以 3kw QPS 的情况,这个金额是不堪想象的。

因此,结论一:

我们使用空间换时间,预先生成红包的金额与数量,不动态生成。

其好处,不仅可以提前对红包的金额的分布有所了解,也可以根据运营特殊化定制特殊红包。

红包数据存储

基于结论一,我们肯定需要有个地方存储,而且这个存储方案的选择会影响到后面的技术实现。

第一个想法,使用数据库去存储,但结论是不可能。

每个请求都要求实时的数据,每秒 3kw 请求用什么数据库都要崩,所以不可能使用数据库去抗。

第二个想法,使用 Redis 去存储,但结论是不放心。

Redis 的持久化 ADF 和 RDB 都不一定可靠,虽然说久经战场,但不能把自己的性命赌在了别人的可靠上。

第三个想法,客户端、服务端(业务层面)做存储(容灾),结论是可行的。

假设每一轮红包有 5 亿用户去争抢,那么相当于我们要做一个「10秒5亿」的发号器。

每一个号是预先生成的,只要保证同一个号一个用户,那么这就是可以接受的。

于是这个号就会存储在客户端和服务端,做双端冗余(容灾)。当然,这里的每个号必然是无规律,并且有着特殊签名校验的。

所以,结论二:

不完全依赖数据库、Redis,业务实现超高并发的发号器。

高并发的发号

我们知道,当并发量大的时候,如果代码处理的不好,那么就有很大的几率出现「一号多人」的情况。

有几率发生的事情,总是会发生。

所以我们就要想一个办法,让这件事情不可能发生。于是我们就想到了 Golang 的 Channel 的机制,原生就可以帮助我们实现 exactly once 的效果。

这个号一旦从 channel 取出,那么就不会再被第二个人获取。

所以,结论三:

业务上基于 Golang 的 Channel 机制,保证每个号的使用是唯一的。

最终设计

我们会对每一轮的红包数量进行估计,在每台实例上预先生成那么多红包(Token)。

在这里,每一个 Token 就是一个红包,Token 保存着红包的唯一 ID 以及红包的基础信息(以分为单位的金额、展示的广告、校验码)。

然后这些信息通过压缩、AES(对称)加密后,变成一个 60 位的 Token。

1
<Red_Packet_ID>|<Money_Amount>#<Ad_ID>#<Nounce>#<Checksum>

为什么要将红包的信息存放在 token 里面呢?

这就和我们的结论二有关,客户端和服务端进行双端冗余,而且解密这个 Token 不强依赖于数据库或者缓存,直接在 API 层就可以将红包进行拆包。

然后使用 Golang 的 Channel 机制,确保红包只会被使用一次,不会出现「一个红包多个人」的情况。

上面的设计就保证了「发红包」的最终一致性,而且我们在「发」的同时,将获取红包的设备 ID 异步写到「发红包」实例的日志中。

这样就保证我们可以知道红包发给了哪个设备,若是有用户反馈,我们就可以根据这个记录反查到红包路径。

因为 Token 中存有着红包的信息,所以「拆红包」的时候只要 API 层将根据 AES 的密钥进行解密即可,不依赖于任何存储。

于是,我们只需要将拆包的用户 ID 异步写到实例的日志中,并将这个用户 ID 和红包 Token 塞入队列中,异步入账及核对。

至此我们就扛过了 5s 每秒 3kw+ 请求的春节红包。

总结

最后总结一下让人眼前一亮的地方:

  • Token 即红包,预先生成、双端冗余、业务容灾
  • Golang Channel 保证每个红包只消费一次,保证不会重复
  • 拆包不强依赖其他组件,解密 Token 即结果
  • 异步入账,用户抢、拆体验流畅,钱包延迟展示

至于安全问题,每个服务器一个 AES 密钥、Nounce 字符串、MD5 校验,以及发、拆日志记录,无论是核账还是薅羊毛都没有太大的问题。

更多的实现细节就不做讨论,权当抛砖引玉、学习别人的架构设计,不断地提升自己。