[分布式服务]海量互联网服务设计之降级设计


上周在 [分布式服务]海量互联网服务设计的有损价值观 这篇文章中提到,与金融行业服务要求的强一致性不同,海量互联网服务要求的是能够扛住更高的qps,服务降级研究的问题是在服务器资源有限的情况下,如何提供更大的访问量,并保证系统稳定运行。

最近我搬了个房子,房东还没来得及上面装宽带,所以在家里我是用手机热点上网的。热点网速有限,看视频的时候,为了视频流程的播放,我将视频从蓝光调整到270P,虽然视频清晰度很低,但是可以流畅播放,也基本满足的需求。这就是降级。与此类似,我们的系统在应对一些突发情况的时候,也需要这样的降级。

为此必须放弃掉某些东西,比如:

  • 数据的一致性,无需绝对的一致性,而保障最终一致即可;
  • 系统功能的降级,例如把一些不重要的功能,或是非关键路径的功能暂时关闭;
  • 用户体验降级,分多种方案给用户不同的体验

降级一致性

缓存的使用

在数据库之上,添加一层缓存是降低一致性的一种做法。

缓存的访问速度、包括可承载的访问qps都要远远高于数据库。用户请求到达时优先访问缓存,如果缓存命中,则直接返回缓存中的数据。如果缓存为空,则查询数据库,并且进行缓存的更新。使用缓存要注意的问题是:

  • 缓存的更新机制,是失效过期被动更新,还是主动refresh,策略如何选择;
  • 缓存过期时间。缓存最长可以保存多长的时间?
  • 热点问题,例如“商品库存”这个值,在每次查询商品列表的时候都会进行查询,很容易就会成为热点key。热点key会造成redis集群的流量倾斜。解决热点key,可以将缓存key进行hash打散。或是在缓存之上再构建一层本地内存cache,例如使用Guava cache来做本地缓存。
  • 缓存击穿问题,当数据在数据库中不存在时,如何避免用户的请求每次都穿过缓存打到数据库上。
  • 添加缓存开关,当缓存的数据有问题时,有没有手段快速让缓存失效 或是 临时关闭缓存。
  • ...

除了在数据库之上添加缓存之外,对下游的接口也可以添加缓存。

例如某个接口提供了查询数据的功能,这些数据并不会经常改变;或是业务上对数据变动的敏感度并不高,那么这时候可以牺牲一点数据变动同步的要求,在对下游接口调用上增加一层缓存。

上述的关于缓存的问题,一些缓存框架都提供了方便快捷的使用方式,例如JetCache框架。

流程异步化

通过将流程异步化,将结果延迟给用户,也能够让系统扛住更大的请求。

举个例子,在打款这样的环节中,如果所有的步骤都是同步的,那么整个流程会非常长,并且通常来说,打款接口对qps都有限制, 例如限制一个平台每秒只能有200的转账打款的qps。如果整个转账业务逻辑都是同步的,很有可能因为这些下游接口的限制(尤其是支付环节)导致流程中止,无法继续往下走。

因此,当类似的场景有高qps出现时,就可以将流程中的同步逻辑用异步来实现。例如告知用户正在打款中,到账信息请等待通知等。用户的请求可以使用消息队列先缓存起来,再根据系统的消费能力慢慢消化掉。

行为聚集化

同样是利用异步的思想,在某些特殊的场景下可以将用户的请求聚合起来进行更新。

这个思想在很多中间件中都有,例如kafka中为了减少网络带来的系统开销,可以通过batch.size这个参数来设置批量提交的数据大小,当积压的消息达到这个值的时候就会统一发送。

也可以将这种思维使用在业务上。例如像一款在线种菜的游戏,用户有多块土地可以种植作物,作物统一收成为积分。一般用户在收获的时候会点击多块土地同时进行收获,用户积分账户属于重要数据,可以每次收获时都进行累计。但是其他关注用户累计积分的非关键分支,则可以进行聚合后再统一累计。例如下游有一个任务服务,专门负责收集用户的收集积分行为,让用户达成某个成就完成任务,这个服务也关心用户积分的累计行为。这种情况下,可以不必用户的每次行为都去调用任务服务,而是上游做聚集后统一发送。

具体做法 可以使用类似kafka延时队列的组件,在用户第一次收集积分时,上游服务便发送一个延时2秒的消息出去。上游服务本身会消费这个延时消息,消费后将延时期间的累计值再传给下游的任务服务。虽然用户的成就累计会延迟2秒,但这种聚集行为的做法,会大大降低下游服务的压力。

体验降级

简化流程

功能降级在不得已的一些情况下也可以使用,例如某个流程有非常多的校验逻辑,举个例子,还是使用上面QQ农场的例子。QQ农场用户是可以互相偷菜的,偷菜的时候是否要去校验偷取与被偷取用户之间是否是好友关系。当好友关系校验服务成为一个瓶颈时,能否将好友关系校验去掉?而为了避免这样做带来的风险,可以同时启用一个限制,就是限制在降级的期间,每个用户仅能偷取其他用户1次,避免有黑产用户不断偷取其他用户的菜。

分级体验

当流量过大时,甚至可以只保留种、偷、收的基本功能,其他的模块暂时屏蔽掉入口,例如成就系统、任务系统等暂时下线。又如电商系统,在拉取商品列表的时候,将商品推荐模块移除掉,商品推荐属于锦上添花的东西,去掉了也不影响用户的正常使用。又如在拉取评论列表时,可以只拉取用户的首次评论,追评信息不再拉取,等等。

在类似相册这种场景下,可以将体验细分为正常访问图片、不预拉取图片、用中图代替大图、用默认图代替大图、只允许访问小图或封面、不允许访问相册等多个层级,根据不同的资源状况和场景为不同的用户提供差异化服务。依据不同的服务压力切换到不同的用户体验级别上。

降级服务要点

首先要明确的是降级达到的目的是什么,有些降级是降低对下游的压力,有些降级是降低读压力,有些则是降低写压力。例如上文提到的加缓存是降低读压力,行为聚集、异步是降低写压力。不同的降级方案所针对的场景应该要明确。

第二是进行业务上的梳理,哪些是必须有的基本功能,例如农场的种菜;哪些是锦上添花的功能,例如商品详情页的“好物推荐”;哪些是可以牺牲的体验,例如相册的多个体验分级,这些简化达到的目的都需要梳理出来。

降级的时间段,有些系统存在流量尖刺的情况,例如商品兑换系统,0点可能是更新库存的时候,用户来兑换商品的请求会形成一个流量高峰。又例如QQ相册,可能在晚上20:00 - 21:00是用户浏览的一个高峰时间段。在不同的时间段,可以选择不同的降级方案。

降级也要注意和前端的配合,例如商品抢购列表 高峰时间段降级,不展示具体的剩余数量,只展示“商品抢购中”的文案,需要前端配合一起做降级。

降级可以做到自动化、半自动化、或完全的手动。应该预留手动调整的能力,避免系统会误判,紧急情况下可以人工介入操作。因为降级的功能不总是会真正进行,所以方案都要事先预演好。

posted @ 2020-03-28 19:17  melonstreet  阅读(784)  评论(0编辑  收藏  举报