前置知识
比如双十一之类的大促高峰,平台是会关闭一些服务的,比如退款服务。这就是降级的典型应用,不过它是一种手动的跨服务降级,对于整个系统来说,提供了一部分服务,但是没有提供另外一部分服务,在整个系统层面上是降级的。
好处有两个,一方面是腾出了服务器资源,可以给订单服务或支付服务;另外一方面是减少了对公共组件的压力,比如减少了对数据库的写入压力。
如果仅仅是针对退款服务而言,也可以认为是退款服务整个熔断了
降级与熔断
降级有熔断非常像,两个关键点
- 如何判定服务健康,在降级中是判断一个服务要不要降级
- 降级之后怎么恢复,也是要考虑抖动的问题
在一些场景下,既可以用熔断,也可以用降级。比如响应时间超出阈值之后,可以选择熔断,完全不提供服务;也可以考虑降级,提供有损服务。
原则上来说,应该优先使用降级。但是有些服务无法降级,尤其是写服务。例如你要从前端接收数据,然后写到数据库,这种场景是无法降级的。另外,如果你希望系统负载尽快降低,那么熔断要优于降级。
如何降级
- 跨服务降级:资源不够的时候可以暂停某些服务,将腾出来的资源给其他更加重要、更加核心的服务使用。
- 本服务提供有损服务:例如APP首页都会有降级策略,在没有触发降级的时候,APP首页是针对个人画像的个性化推荐。而触发降级之后,则可能是使用榜单数据,或是运营提前配置好的静态页面。要点是要知道服务调用者能够接受什么程度的有损。
跨服务降级的措施常见的有三个: - 整个服务停掉,例如前面提到的停掉退款服务
- 停掉服务的部分节点
- 停止访问某些资源。例如日志中心压力很大的时候,发信号给某些不重要的服务,让他们停止上传日志,只在本地保存日志
而对服务本身,也有一些常见的降级思路 - 返回默认值,这算是一种简单的情况
- 禁用可观测性组件,正常来说业务里充满了各种各样的埋点,这些埋点本身是会带来消耗的,性能瓶颈的时候可以考虑停用,或是降低采样率
- 同步转异步,即正常情况下,服务收到请求后会立马处理。但是在降级的情况下,服务收到一个请求之后只会返回一个代表已接收的响应。后续服务会异步开启线程处理,或是定时任务处理
- 简化流程:如果你处理一个请求需要很多步骤,后续如果有一些步骤不关键的话,可以考虑不执行,或者异步执行。例如在内容生产平台,一般新内容要被推送到推荐系统里面。那么在降级的情况下你可以不推,而后可以考虑异步推送过去,也可以考虑等系统恢复之后再推送过去
面试准备
使用降级的情况
- 网站、APP之类的产品,在首页、核心页面有没有采取降级措施。如果采用了降级,降级前后的逻辑是什么样的
- 有没有使用降级来保护系统,如果有,需要了解清楚什么情况下会触发降级,降级前后的逻辑,怎么从降级中恢复过来
最佳面试策略是把降级作为构建高可用微服务架构的一个措施
A系统是我们公司的核心系统,主要职责是保障该系统的高可用,为了达到这个目标,合理运用了熔断、降级、隔离等措施
常见问题:
- 是否了解服务治理
- 如何提高系统可用性
- 如果系统负载高怎么办
- 依赖的下游服务或下游中间件崩溃了怎么办
同时为了展示亮点,需要记住后面给出的两个方案:读写服务降级写服务和快慢路径降级慢路径。非常建议参考这两个方案的思路,基于自己的实际业务情况设计自己独有的降级面试案例
基本思路
可以先介绍降级的基本概念,比如前面的大促和APP首页的例子。如果之前和面试官没有聊过熔断,可以在这里补充熔断里面讨论判断服务健康的要点,结合自己公司内部使用降级的例子,或是不是自己亲手落地但是自己也了解详情的案例。
我在公司也用了降级来保护我维护的服务。举例来说,正常情况下我的服务都会全量采集各种监控指标。那么在系统触及性能瓶颈的时候,我就会调整采集的比率。甚至在关键的时候,我会直接停用掉所有的指标采集,将资源集中在提供服务上。
讲完一个案例后,可以进一步总结常规的降级思路。
如果不了解细节的话,可以大方承认这就是听说过的措施,并没有实际落地。
最关键的问题就是抖动,可以将熔断与降级结合,总结升华一下。
总的来说,在任何的故障处理里面,都要考虑恢复策略会不会引起抖动问题
读写服务降级写服务
如果某个服务里同时提供了读服务和写服务,并且读服务明显比写服务更重要,这时候降级写服务。
假如我有一个针对商家的服务,商家调用这些 API 来录入一些数据,比如他们门店的基本信息,上传一些门店图片等。同时我还有一个针对 C 端普通用户的服务,这个服务就是把商家录入的数据展示在商家门店的首页上。所以你可以看到在这个场景下,读服务 QPS 更高,也更加重要。
那么如果这两个服务是一起部署的,在需要降级的时候,就可以考虑将针对商家的写服务停掉,将资源都腾出来给针对 C 端用户的读服务。所以你可以介绍这个方案,关键词是降级写服务。
我在公司维护了一个服务,它的接口可以分成两类:一类是给 B 端商家使用的录入数据的接口,另外一类是给 C 端用户展示这些录入的数据。所以从重要性上来说,读服务要比写服务重要得多,而且读服务也是一个高并发的服务。
于是我接入了一个b。当我发现读服务的响应时间超过了阈值的时候,或者响应时间开始显著上升的时候,我就会将针对 B 端商家用户的服务临时停掉,腾出来的资源都给 C 端用户使用。对于 B 端用户来说,他们这个阶段是没有办法修改已经录入的数据的。但是这并不是一个特别大的问题。当 C 端接口的响应时间恢复正常之后,会自动恢复 B 端商家接口,商家又可以修改或者录入数据了。
同时可以从数据库性能影响的角度来进一步解释降级写服务的优点
虽然整体来说写服务 QPS 占比很低,但是对于数据库来说,一次写请求对性能的压力要远比一次读请求大。所以暂停了写服务之后,数据库的负载能够减轻不少。
除了这种 B 端录入 C 端查询的场景,还有很多类似的场景也适用。
- 在内容生产平台,作者生产内容,C 端用户查看生产的内容。那么在资源不足的情况下可以考虑停掉内容生产端的服务,只保留 C 端用户查看内容的功能。
- 如果你的用户分成普通用户和 VIP 用户,那么你也可以考虑停掉给普通用户的服务。甚至,如果一个服务既提供给普通用户,也提供给 VIP 用户,你可以考虑将普通用户请求拒绝掉,只服务 VIP 用户。
总结
这个方案就是典型的跨服务降级。跨服务降级可以在大部分合并部署的服务里面使用,一般的原则就是 B、C 端合并部署降级 B 端;付费服务和非付费服务降级非付费服务。当然也可以根据自己的业务价值,将这些部署在同一个节点上的服务分成三六九等。而后在触发降级的时候从不重要的服务开始降级,将资源调配给重要服务。
可能会问怎么确定一个服务的业务价值,又或者你可以自己引出这个话题,关键词就是赚钱。
判断一个服务的业务价值最简单的方法就是问产品经理,产品经理自然是清楚什么东西带来了多少业务价值。又或者根据公司的主要营收来源确定服务的业务价值,越是能赚钱的就越重要。唯一的例外是跟合规相关的。比如说内容审核,它不仅不赚钱,还是一块巨大的成本支出。但是不管怎么降级,内容审核是绝对不敢降级的,不然就等着被请去喝茶交代问题吧。
这里我们还可以进一步展示亮点,让人感觉你对微服务框架有很深研究。关键词就是跨节点。
不过这种跨服务降级都是只能降级处在同一个节点的不同服务。而如果服务本身就分布在不同节点上的话,是比较难设计这种降级方案的。比如说大促时关闭退款服务这种,就需要人手工介入。 从理论上来说,网关其实是可以考虑支持这种跨节点的服务降级的。假如说我们有 A、B 两个服务,A 比 B 更加有业务价值。那么在 A 服务所需资源不足的时候,网关可以考虑停掉 B 的一部分节点,而后在这些节点上部署 A 服务。对于 B 服务来说,它只剩下一部分节点,所以也算是被降级了。很可惜,大部分网关的降级设计都没考虑过这种跨服务降级的功能。 微服务框架做得就更差了。大部分微服务框架提供的降级功能都是针对本服务的,比如说在触发降级的时候返回一个默认值。
最后对网关的评价可能会让面试官将话题引向网关,所以你要在对面试网关内容有把握的情况下再说。
快慢路径降级慢路径
熔断里有一个例子,即如果Redis崩溃了,可以直接触发熔断。这种做法是为了保护数据库,防止把数据库打挂。
也可以使用降级来保护这个缓存 - 数据库结构。正常来说,使用缓存基本都是先从缓存里读数据,然后缓存里没有数据,再从数据库中读取。
在触发降级的情况下,可以考虑只从缓存里读取。如果缓存里没有,也不会再去数据库读取。这样可以保证缓存里有数据的请求可以得到正常处理,也就是提供了有损服务。
这种降级方案背后的逻辑也很简单**。如果完全不考虑从数据库里取数据,那么你的性能瓶颈就完全取决于缓存或者说 Redis,那么服务能够撑住的 QPS 会非常高**。但是,如果缓存不命中的时候要去数据库取数据,那么服务的性能会衰退得非常快,即极少数缓存未命中的请求会占据大部分的系统资源。
你可以这样回答,关键词是只查缓存。
我还用过另外一个降级方案。正常来说在我的业务里面,就是查询缓存,如果缓存有数据,那么就直接返回。如果缓存没有,那么就需要去数据库查询。如果此时系统的并发非常高,那么我就会采取降级策略,将请求标记为降级请求。降级请求只会查询缓存,而不会查询数据库。如果缓存没有,那就直接返回错误。这样能够有效防止因为少部分请求缓存未命中而占据大量系统资源,导致系统吞吐量下降和响应时间显著升高。
同样地,你也需要总结拔高一下,关键词是快慢路径。
这种思路其实可以在很多微服务里面应用。如果一个服务可以分成快路径和慢路径两种逻辑,那么在降级之前就可以先走快路径,再走慢路径。而触发了降级之后,就只允许走快路径。在前面的例子里面,从缓存里加载数据就是快路径,从数据库里面加载数据就是慢路径。 慢路径还可以是发起服务调用或者复杂计算。比如说一个服务快路径是直接查询缓存,而慢路径可能是发起很多微服务调用,拿到所有响应之后一起计算,算出来一个结果并缓存起来。那么在降级的时候,可以有效提高吞吐量。不过这种吞吐量是有损的,毕竟部分请求如果没有在缓存中找到数据,那么就会直接返回失败响应。
很自然地,你的关键服务都应该有类似的降级措施。当任何下游崩溃,或者第三方中间件崩溃,你都可以不再调用这些崩溃的下游服务或中间件,以确保提供有损服务。
如果你选择这个作为亮点方案的话,那么自然就可以将话题引导到缓存的使用上来,你就可以使用课程后面缓存相关的内容来阐述了。