应对高并发的艺术

应对高并发的艺术

服务层

应用层ngnix->秒杀系统BFF->订单服务,其实两两组合和网关层是一样的场景。应用层ngnix基于ngnix的负载均衡转发请求到秒杀系统BFF,秒杀系统BFF基于RPC框架的负载均衡转发请求到订单服务。都面临着负载均衡策略选择和是否启用本地缓存的问题。不一样的点只是缓存的粒度和启用缓存的技术栈选择。

多级缓存失效

多级缓存因为缓存分散到多个层级,所以很难用单一的技术栈来应对缓存失效的问题,但都等到缓存过期,这种更新时延较长又不一定能被业务接受。所以这里就再提下这个话题。有一个做法是基于DB的binlog监听,各层监听自己相关的binlog信息,在发生缓存被变更的情况时,及时让集成内存的缓存失效。本地缓存在这里还有个缺陷,就是缓存失效时需要广播到所有节点,让每个节点都失效,对于频繁变更的热key就可能产生消息风暴。

3.无损消峰

秒杀活动的特点是瞬时高峰的流量,就像一座高耸的尖塔,短时间内涌入大量请求。为这个峰值准备对应的服务集群,首先成本太高,接着单纯的水平扩展也不一定能做到(分布式架构存在量变引起质变的问题,资源扩展到一定量级,原先的技术方案整个就不适用了。比如,当集群节点太多,服务注册发现可能会有消息风暴;出入口的带宽出现瓶颈,需要在部署上分流)。更别说这个峰值也不受控制,想要高枕无忧就会有很高的冗余浪费。

所以一般我们会采用消峰的方式,一种是直接断头,把超出负荷的流量直接都丢弃掉,也就是我们常见的限流,也称为有损消峰(如果这是大促的订单,砍掉的可能都是钱,这个有损是真的资损);另一种就是分流,也叫消峰填谷,通过技术或者业务手段将请求错开,铺到更长的时间线上,从而降低峰值,常见的有MQ异步消费和验证码问答题。这里我们先聊下无损消峰,有损放后边谈。

MQ异步消费

MQ依赖三个特性可以做到平滑的最终一致,分别是消息堆积,匀速消费和至少成功一次:

有消息堆积才能起到蓄水池的效果,在出水口流速恒定的情况下能接住入水口瞬时的大流量;

有匀速消费才能让下游集群的流量压力恒定,不会被冲击;

有至少成功一次,才能保证事物最终一致。

以秒杀系统BFF下单操作向订单服务创建订单为例。如果没有消息队列(MQ),同时有100W个创建请求,订单系统就必须承担100W个并行连接的压力。但是,如果使用了MQ,那么100W个创建请求的压力将全部转移到MQ服务端,订单系统只需要维持64个并行连接,以稳定地消费MQ服务端的消息。

这样一来,订单系统的集群规模就可以大大减小,而且更重要的是,系统的稳定性得到了保障。由于并行连接数的减少,资源竞争也会降低,整体响应效率也会提高,就像在食堂排队打饭一样,有序排队比乱抢效率更高。但是,用户体验可能会受到影响,因为点击抢购后可能会收到排队提示(其实就是友好提示),需要延迟几十秒甚至几分钟才能收到抢购结果。

验证码问答题

引入验证码问答题其实有两层好处,一层是消峰,用户0.5秒内并发的下单事件,因为个人的手速差异,被平滑的分散到几秒甚至几十秒中;另外一层是防刷,提高机器作弊的成本。

验证码

基本实现步骤如下:

请求到来时生成1串6位随机字符串 verification_code

用特定前缀拼接用户ID作为key,verification_code做为value存redis,超时5s

生成一个图片,将 verification_code 写到图片上,返回给用户

用户输入图片中字符串

从redis里面取出 verification_code 做比对,如果一致,执行下单操作

但这样其实是可以用暴力破解的,比如,用机器仿照一个用户发起10W个请求携带不同的6位随机字符。所以校验验证码时可以使用 GETDEL ,让验证码校验无论对错都让验证码失效。

问答题

基本实现思路和验证码几乎一样。差别在于,问答题的题库要提前生成,请求到来时从题库中拿到一组问题和答案。然后把答案存redis,问题塞到图片里返回给用户。

验证码和问答题具有很好的消峰效果。特别是问答题,想要提高消峰效果只要提高问题难度就行,例如,笔者曾经在12306上连续错了十几次问答题。但是这也是用户体验有损的,例如,虽然笔者当初未能成功抢到票而感到沮丧,但这魔性的题库依然把笔者成功逗笑。

无损消峰,无损了流量,但损失了用户体验。现如今技术水平在不断进步,解决方法在增多,这些有损用户体验的技术方案可能都会慢慢退出历史舞台,就像淘宝取消618预售。

4.库存扣减

我们知道,用户购买商品需要扣减库存,扣减库存需要查询库存是否足够,足够就占用库存,不够则返回库存不足(这里不区分库存可用、占用、已消耗等状态,统一成扣减库存数量,简化场景)。

在并发场景,如果查询库存和扣减库存不具备原子性,就有可能出现超卖,而高并发场景超卖的出现概率会增高,超卖的数额也会增高。处理超卖问题是件麻烦事,一方面,系统全链路刷数会很麻烦(多团队协作),客服外呼也会有额外成本。另一方面,也是最主要的原因,客户抢到了订单又被取消,会严重影响客户体验,甚至引发客诉产生公关危机。

实现逻辑

业内常用的方案就是使用redis+lua,借助redis单线程执行+lua脚本中的逻辑可以在一次执行中顺序完成的特性达到原子性(原子性其实不大准确,叫排它性可能更准确些,因为这里不具备回滚动作,异常情况需要自己回滚)。

lua脚本基本实现大致如下:

相关数据

现金小熊贷款平台:快速借款与安全借贷全攻略
足球365网站网址

现金小熊贷款平台:快速借款与安全借贷全攻略

⌛ 08-29 👁️ 1561
梦幻逆鳞加多少伤害?(梦幻逆鳞加多少伤害)
足球365网站网址

梦幻逆鳞加多少伤害?(梦幻逆鳞加多少伤害)

⌛ 09-14 👁️ 220