微信红包的设计考虑

最近在公司设计一个直播相关的项目,会用到红包功能。

网上很多对微信红包架构的猜测,也有很多博客在说redis解决方案,其实大多不是很靠谱。

我的设计思路

红包一般涉及用户资产,所以本质是一个金融业务,交易场景,强调高度一致性与可靠性。

既然是交易,那么就要往交易的模型上去靠。

红包内有一定数量的money,1个红包有N份,每一份的金额又不同,这样看起来红包的角色就是库存,随着抢红包不断的减少。

抢红包的角色就是一笔订单,它会去扣减红包的库存。

用户资产是另外一套系统,它提供了用户资产增/减操作能力。

那么整个流程是:创建抢红包订单 -> 扣减红包库存 -> 给用户增加资产。

交易模型最大的好处就是,订单的状态机可以保障最终一致性,异常情况可以通过订单状态进行补偿。

接下来一个问题是,红包的库存和商品库存有一个本质的区别:

  • 商品库存只要有足够的份数,你就可以完成扣减。
  • 红包扣库即便剩余了100元,但因为红包剩余了2份,那么这次抢红包也不能拿走全部100元,否则剩下的那份就没有钱可抢了。

那么红包的库存怎么做呢?

首先,无论是商品还是红包,库存和库存流水是要做行锁+事务的,这样才能保证库存的互斥操作以及幂等性(按订单ID幂等),这个是交易系统无法避免的。

商品库存仅仅是一个数字,而红包的库存涉及份数和金钱两个维度,设计一个动态拆包的算法还是太过于复杂,很难想的出来。

但是如果提前把红包拆分好还是很简单的,所以我们在生成红包时就可以把红包拆成N份,每一份的金额都计算好。假设红包一共10元5份,那么拆分结果可能是:2,1,2,4,1,我们要做的就是把这个字符串作为库存保存到红包的一个字段中去。

接下来扣减库存时,首先开启事务,行锁该红包记录,取第一个包:2元,将剩余红包更新回去,然后写入库存流水,最后提交事务,行锁被释放。

(其实更建议把预先生成的分配列表按次序保存到独立的表中,红包库存表仅通过剩余份数维护库存,第N份的金额去分配表中可以得知金额)

扩展性

抢红包订单本身可以按uid sharding,和任何交易系统都是一样的,用以缓解大量的抢请求造成的写压力。

红包库存本身也是一个订单,因为发红包的人需要先付款给微信,这个红包库存才有效。

因为红包库存主要处理行锁事务库存扣减,所以横向扩展更为重要,一般可以考虑直接按红包ID散列,这样最没有规律性。

至于发红包的用户想查看自己的发红包清单,可以通过异步的同步机制,将红包库存(订单)推送到另外一套按UID维度散列的mysql中保存。

秒杀场景

微信红包一般谈不上什么秒杀,因为1个红包几十份,发到一个微信群里最多几百人。

但是我这边是一个直播场景,更像熊猫TV里的佛跳墙触发的竹子奖励,大量的人会瞬间点击抢包按钮。

这个问题要这么来看待:

  1. 抢红包订单和红包库存是有横向扩展能力的,但是最终一个红包的库存只能由一个mysql来处理。
  2. 红包库存抢空后,后续的抢红包请求都只有一次读操作,因为它们发现库存为0,不需要创建抢包订单。
  3. 所以只要能保障最开始点击抢红包的一小波用户能够进入到交易环节,将库存快速且平稳的消耗为0即可。
  4. 可以通过redis计数来控制每秒进入交易环节的用户数,比如每秒只能进入100人,其他的人均阻挡,正常情况下这100人会顺利的完成交易得到红包,库存减少为0后可以设置redis标志位,避免下一秒进入的另外100位用户继续进入交易环节做无用功。

扩展阅读

参考《微信红包》。

 

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~