抢红包总结

目录

业务场景分析

架构分析

业务上需要注意的地方

具体架构设计

拆红包入账异步化

发拆落地,其他操作双层cache

高并发

柔性降级方案

红包算法

总结


参考   https://www.jianshu.com/p/63f238b04c59?from=singlemessage

https://www.cnblogs.com/8hao/archive/2016/04/12/5383143.html

业务场景分析

根据微信红包的操作,可以把红包的业务场景分为发放红包,抢红包和打开红包三个步骤。
PS:我觉得微信红包的架构讨论之所以非常火爆,一个重要原因就是微信红包的业务场景大家都很熟悉

发放红包

发放者设置红包金额,红包数量等属性

从发放者账户扣除红包金额

生成红包,并且发送抢红包的链接

抢红包

用户点击红包链接

打开红包

显示用户抢到的红包金额

剩余红包数量-1,红包剩余金额改变

抢到红包的用户账户金额增加

 

注意,以上均为原子操作(发红包,抢红包,打开红包,都是原子操作)

根据业务场景,可以知道红包的主要属性有:

红包金额:始终不变

红包总数量:始终不变

红包剩余金额:每次有人领取红包,该属性变化

红包剩余数量:每次有人领取红包,该属性-1

抢到红包的用户以及抢到的金额:用于显示手气排行,并且防止重复抢红包

过期时间:如果到过期红包仍未抢完,返回发放者账户

架构分析

单纯实现微信红包的功能并不复杂,其难点在于如何处理高访问和并发,当发出一个红包之后,只有少数人能够抢到,而大部分的请求都属于无效请求,如果让这部分请求落到数据库或者在服务端经过复杂处理,那么以微信的用户体量,后果是灾难性的。

从这个角度来说,微信红包和电商的秒杀业务有几分相似

处理类似业务最常见的手段就是请求过滤和添加缓存(cache),当然,至于服务器集群,负载均衡,读写分离这些基础架构,由于已经成了大型网站的标配,默认已经存在。

其中,请求过滤是只允许少数符合条件的请求走到最后,把大量不符合条件的请求挡在外面,这个非常关键。

而采用缓存技术,原因有两个,一是缓存的访问速度快,使用缓存可以有效提高吞吐速度,二是红包发放相对来说是一个临时性的东西,故而可以放在缓存里面。

业务上需要注意的地方

防止用户重复抢红包

应对redis宕机的风险,目前一般是采用sentinel机制

具体架构设计

缓存我们默认使用redis,数据库默认使用mysql

新建红包的时候,生成唯一红包id,设置红包属性,在mysql中添加记录,表结构大致如下

|红包id|发放者uid|红包总金额|红包总数量|红包剩余金额|红包剩余数量|抢到红包的用户列表json|过期时间|创建时间|

 

同时,在redis里添加:

抢红包请求队列(队列)

打开红包请求队列(队列)

红包信息,包含剩余红包数量和剩余金额(键值对)

已经抢到红包的用户信息(有序集合)

 

当抢红包的时候,做以下判断:

剩余红包数量是否大于0

该用户是否在已经抢到红包的用户集合里面
通过判断的请求添加进请求队列,否则返回已经抢完的标示,这样下一步用户打开红包的时候,甚至可以不发送请求直接显示红包已经抢完,过滤无效请求

于此同时,另外有进程从请求队列里取出抢红包请求,生成token标示,返回给抢红包者

当用户打开红包的时候,传入该标示,放进打开红包请求队列,后端消费者进程从队列中取出打开红包请求,判断红包数量是否大于0,以及用户传来的打开红包token与红包id是否合法,参数检验之后,生成红包金额,调用余额接口处理,并且更新redis中红包剩余数量,红包剩余金额

最后异步方式更新数据库

拆红包入账异步化

信息流与资金流分离。拆红包时,DB中记下拆红包凭证,然后异步队列请求入账。入账失败通过补偿队列补偿,最终通过红包凭证与用户账户入账流水对账,保证最终一致性。

这个架构设计,理论基础是快慢分离。红包的入账是一个分布事务,属于慢接口。而拆红包凭证落地则速度快。实际应用场景中,用户抢完红包,只关心详情列表中谁是“最佳手气”,很少关心抢到的零是否已经到账。因为只需要展示用户的拆红包凭证即可。

发拆落地,其他操作双层cache

1、Cache住所有查询,两层cache

除了使用ckv做全量缓存,还在数据访问层dao中增加本机内存cache做二级缓存,cache住所有读请求。

查询失败或者查询不存在时,降级内存cache;内存cache查询失败或记录不存在时降级DB。

DB本身不做读写分离。

2、DB写同步cache,容忍少量不一致

DB写操作完成后,dao中同步内存cache,业务服务层同步ckv,失败由异步队列补偿,定时的ckv与DB备机对账,保证最终数据一致。

高并发

微信红包的并发挑战,主要在于微信大群,多人同时抢同一个红包。这种情况,存在竞争MySQL行锁。为了控制这种并发,团队做了以下一些事情:

1、请求按红包订单路由,逻辑块垂直sticky,事务隔离

按红包订单划分逻辑单元,单元内业务闭环。服务rpc调用时,使用红包订单号的hash值为key寻找下一跳地址。对同一个红包的所有拆请求、查询请求,都路由到同一台逻辑机器、同一台DB中处理。

2、Dao搭建本机Memcache内存cache,控制同一红包并发个数

在DB的接入机dao中,搭建本机内存cache。以红包订单号为key,对同一个红包的拆请求做原子计数,控制同一时刻能进DB中拆红包的并发请求数。

这个策略的实施,依赖于请求路由按红包订单hash值走,确保同一红包的所有请求路由到同一逻辑层机器。

3、多层级并发量控制

1) 发红包控制

发红包是业务流程的入口,控制了这里的并发量,代表着控制了红包业务整体的并发量。在发红包的业务链路里,做了多层的流量控制,确保产生的有效红包量级在可控范围。

2) 抢红包控制

微信红包领取时分为两个步骤,抢和拆。抢红包这个动作本身就有控制拆并发的作用。因为抢红包时,只需要查cache中的数据,不需要请求DB。对于红包已经领完、用户已经领过、红包已经过期等流量可以直接拦截。而对于有资格进入拆红包的请求量,也做流量控制。通过这些处理,最后可进入拆环节的流量大大减少,并且都是有效请求。

3) 拆时内存cache控制

针对同一个红包并发拆的控制,上文已经介绍。

4、DB简化和拆分

DB的并发能力,有很多影响因素。红包系统结合红包使用情境,进行了一些优化。比较有借鉴意义的,主要有以下两点:

1) 订单表只存关键字段,其他字段只在cache中存储,可柔性。

红包详情的展示中,除了订单关键信息(用户、单号、金额、时间、状态)外,还有用户头像、昵称、祝福语等字段。这些字段对交易来说不是关键信息,却占据大量的存储空间。

将这些非关键信息拆出来,只存在cache,用户查询展示,而订单中不落地。这样可以维持订单的轻量高效,同时cache不命中时,又可从实时接口中查询补偿,达到优化订单DB容量的效果。

2) DB双重纬度分库表,冷热分离

使用订单hash、订单日期,两个纬度分库表,也即db_xxx.t_x_dd这样的格式。其中,x表示订单hash值,dd表示01-31循环日。订单hash纬度,是为了将订单打散到不同的DB服务器中,均衡压力。订单日期循环日纬度,是为了避免单表数据无限扩张,使每天都是一张空表。

另外,红包的订单访问热度,是非常典型的冷热型。热数据集中在一两天内,且随时间急剧消减。线上热数据库只需要存几天的数据,其他数据可以定时移到成本低的冷数据库中。循环日表也使得历史数据的迁移变得方便。

柔性降级方案

系统到处存在发生异常的可能,需要对所有的环节做好应对的预案。下面列举微信红包对系统异常的主要降级考虑。

1、 下单cache故障降级DB

下单cache有两个作用,生成红包订单与订单缓存。缓存故障情况下,降级为直接落地DB,并使用id生成器独立生成订单号。

2、 抢时cache故障降级DB

抢红包时,查询cache,拦截红包已经抢完、用户已经抢过、红包已经过期等无效请求。当cache故障时,降级DB查询,同时打开DB限流保护开关,防止DB压力过大导致服务不可用。

另外,cache故障降级DB时,DB不存储用户头像、用户昵称等(上文提到的优化),此时一并降级为实时接口查询。查询失败,继续降级为展示默认头像与昵称。

3、 拆时资金入账多级柔性

拆红包时,DB记录拆红包单据,然后执行资金转账。单据需要实时落地,而资金转账,这里做了多个层级的柔性降级方案:

大额红包实时转账,小额红包入队列异步转账 所有红包进队列异步转账 实时流程不执行转账,事后凭单据批量入账。

总之,单据落地后,真实入账可实时、可异步,最终保证一致即可。

4、 用户列表降级

用户列表数据在微信红包系统中,属于非关键路径信息,属于可被降级部分。

首先,写入时通过MQ异步写,通过定时对账保证一致性。

其次,cache中只缓存两屏,用户查询超过两屏则查用户列表DB。在系统压力大的情况下,可以限制用户只查两屏。

调整后的系统经过了16年春节的实践检验,平稳地度过了除夕业务高峰,保障了红包用户的体验。

红包算法

关于红包算法,网上说的很多了,这里就不啰嗦了,金额是拆的时候实时算出来,不是预先分配的,采用的是纯内存计算,不需要预算空间存储,具体的算法是在在0.01和剩余平均值的2倍之间随机生成一个金额

比如现在发了一个红包,分为5份。有100个人抢。

可能有10个人成功进入请求队列(抢到红包和打开红包之间有一定间隔,所以先抢到的不一定先打开),拿到了打开红包token,其余90个人拿到的是红包已经抢完的token,那么当打开红包的时候,这90个人甚至都不需要发送请求,直接显示红包已抢完

这10个人打开红包的时候,其中5个人抢到,剩下的5个也显示红包已经抢完

本来5个红包100个人抢,会请求201次(生成红包1次+抢红包100次+打开红包100次)
那么这种情况下,只需要111次(生成红包1次+抢红包100次+打开红包10次),并且,其中90次请求很快就返回,大大减轻服务端压力

总结

衡量一个架构设计的标准主要是性能,扩展性,伸缩性,可用性,安全性等指标,本例中,由于红包业务是一个很具体的业务,并非通用服务,所以不讨论扩展性。

性能角度,由于采用了缓存以及请求过滤等手段,性能肯定比常规实现大大提高

伸缩性角度,应该采用服务器集群,redis集群,mysql集群的方式部署,注意redis集群中的一致性hash的实现

可用性角度,本例中决定可用性的关键还是在于redis集群是否能够保证高可用,所以sentinel机制必不可少,甚至还应该加上其他的各种检测节点,替换节点方案,以保证高可用。而且需要考虑极端情况,例如缓存被击穿导致mysql压力过大的情况下,如何确保服务不宕机。

安全性角度,红包涉及资金来往,所以需要格外注意安全性。红包业务中,是通过调用接口来实现资金往来,故账目平衡的风险不是由红包系统来承担而是由红包所调用的接口保证的,但红包系统也可能存在以下几种安全隐患,单一用户重复抢红包,所有用户抢的的红包金额总数大于发放者设置的红包金额等。所以其关键在于确保抢红包,打开红包均为一次原子性操作。如有必要,甚至可以通过加锁等方式实现,但需要考虑加锁对于性能上的影响

你可能感兴趣的:(系统架构)