服务降级

降级概念

服务降级指的是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。

开关降级指的是在代码中预先埋设一些“开关”,用来控制服务调用的返回结果。开关降级一般将开关的值存储在配置中心中,通过配置中心动态调整服务处理情况。

一般来说,降级牺牲掉的东西为:

  • 降低一致性:强一致性 最终一致性
  • 停止次要功能:去掉不必要的推荐等功能
  • 简化功能:返回全量数据 部分数据

降级应用

  • 降低一致性
    • 使用异步简化流程
      • 双 11 购物,可以将分配库存变成异步操作。同时提示用户,“当前系统繁忙,订单收到,正努力处理订单中”。后续根据情况去掉该商品并退回对应资金。
      • 甚至下订单后的付款也可以改为货到付款,将一致性变为最终一致性。
    • 降低数据一致性
      • “读降级”:1️⃣ 直接从缓存中读取数据,而不再读后端服务。2️⃣ 轮询查询降低频率,从 30 秒轮询降低为 10 分钟。
      • “写降级”:只更新缓存,异步扣减库存到 DB。
  • 停止次要功能
    • 关闭相关文章的推荐、用户的评论功能。
  • 简化功能
    • 一个 API 会有两个版本,一个版本返回全量数据,另一个版本只返回部分或最小的可用的数据。
    • 例如,一篇文章的某个 API,可以返回文章+评论数据,也可以只返回文章数据。
  • 其他
    • 延迟服务: 比如发表了评论,重要服务,比如在文章中显示正常,但是延迟给用户增加积分,只是放到一个缓存中,等服务平稳之后再执行。
    • 异步请求降级: 比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级;
    • 页面跳转(页面降级): 比如可以有相关文章推荐,但是更多的页面则直接跳转到某一个地址

降级自动化

降级按照是否自动化可分为:

  • 自动开关降级(超时、失败次数、故障、限流)
  • 人工开关降级(秒杀、电商大促等)

自动降级分类又分为 :

  1. 超时降级: 主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况
  2. 失败次数降级: 主要是一些不稳定的 api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况
  3. 故障降级: 比如要调用的远程服务挂掉了(网络故障、DNS 故障、http 服务返回错误的状态码、rpc 服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)
  4. 限流降级: 当我们去秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时开发者会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)

大规模分布式系统如何降级

提前对业务和系统进行梳理,根据梳理结果确定哪些服务可以降级,哪些服务不可以降级,降级策略是什么,降级顺序怎么样。比如手动降级开关,批量降级顺序管理,熔断阈值动态设置,限流阈值动态设置等。

熔断

熔断概念

雪崩效应
微服务之间的数据交互是通过远程调用来完成的。服务 A 调用服务 B,服务 B 调用服务 C,某一时间链路上对服务 C 的调用响应时间过长或者服务 C 不可用,随着时间的增长,对服务 C 的调用也越来越多,然后服务 C 崩溃了,但是链路调用还在,对服务 B 的调用也在持续增多,然后服务 B 崩溃,随之 A 也崩溃,导致雪崩效应。

服务熔断是应对雪崩效应的一种微服务链路保护机制。例如在高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。同样,在微服务架构中,熔断机制也是起着类似的作用。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。

熔断机制

  • 闭合状态。最近失败次数超过单位时间失败阈值,则 断开状态。同时开启超时时钟,当超过该时间后,切换到 半断开状态
  • 断开状态。立即返回错误响应,而不调用后段服务。当然,也可以用 cache 缓存一些错误时的返回信息。
  • 半开状态。允许应用程序一定数量的请求服务。如果请求调用成功,则认为错误修正,切换为 闭合状态,错误计数器重制。反之,回到 断开状态,并重置超时时钟。

熔断应用

不仅仅微服务之间调用需要熔断的机制,我们在调用 Redis、Memcached 等资源的时候也可以引入这套机制。在我的团队自己封装的 Redis 客户端中,就实现了一套简单的熔断机制。首先,在系统初始化的时候,我们定义了一个定时器,当熔断器处于 Open 状态时,定期地检测 Redis 组件是否可用。

new Timer("RedisPort-Recover", true).scheduleAtFixedRate(new TimerTask() {
    @Override
    public void run() {
        if (breaker.isOpen()) {
            Jedis jedis = null;
            try {
                jedis = connPool.getResource();
                jedis.ping(); //验证redis是否可用
                successCount.set(0); //重置连续成功的计数
                breaker.setHalfOpen(); //设置为半打开态
            } catch (Exception ignored) {
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }
    }
}, 0, recoverInterval); //初始化定时器定期检测redis是否可用

在通过 Redis 客户端操作 Redis 中的数据时,我们会在其中加入熔断器的逻辑。比如,当节点处于熔断状态时,直接返回空值以及熔断器三种状态之间的转换,具体的示例代码像下面这样:

if (breaker.isOpen()) {
    return null;  // 断路器打开则直接返回空值
}
 
K value = null;
Jedis jedis = null;
 
try {
     jedis = connPool.getResource();
     value = callback.call(jedis);
     if(breaker.isHalfOpen()) { //如果是半打开状态
          if(successCount.incrementAndGet() >= SUCCESS_THRESHOLD) {//成功次数超过阈值
                failCount.set(0);  //清空失败数
                breaker.setClose(); //设置为关闭态
          }
     }
     return value;
} catch (JedisException je) {
     if(breaker.isClose()){  //如果是关闭态
         if(failCount.incrementAndGet() >= FAILS_THRESHOLD){ //失败次数超过阈值
            breaker.setOpen();  //设置为打开态
         }
     } else if(breaker.isHalfOpen()) {  //如果是半打开态
         breaker.setOpen();    //直接设置为打开态
     }
     throw  je;
} finally {
     if (jedis != null) {
           jedis.close();
     }
}

这样,当某一个 Redis 节点出现问题,Redis 客户端中的熔断器就会实时监测到,并且不再请求有问题的 Redis 节点,避免单个节点的故障导致整体系统的雪崩。

降级和熔断有什么区别

降级的目的在于应对系统自身的故障,而熔断的目的在于应对当前系统依赖的外部系统或者第三方系统的故障。

现成解决方案

Spring Cloud 官方目前推荐的熔断器组件:Hystrix、Resilience4J、Sentinel、Spring Retry。

Hystrix 是 Netflix 开源的熔断降级组件,Sentinel 是阿里中间件团队开源的一款不光具有熔断降级功能,同时还支持系统负载保护的组件。Sentinel 的 wiki 中已经详细描述了其与 Hystrix 的区别,地址: https://github.com/alibaba/Sentinel/

如果你想了解 Sentinel、Hystrix、resilience4j 三者的对比的话,可以查看 Sentinel 的相关 wiki: https://github.com/alibaba/Sentinel/wiki/Guideline :从-Hystrix-迁移到-Sentinel-功能对比。

熔断补充

  • 首先下游服务可以分为第三方服务跟微服务,第三方服务没见过谁直接调用某个服务节点的,都是通过网关去负载均衡,所以服务不可用即代表了网关服务不可用,对上游服务来说,跟集群不可用没有区别,而微服务的话,调用的时候单节点不可用会自动给你负载到可用服务节点,除非服务提供者全部挂了(网络波动导致连不通也算),否则不会触发这个断路器,设置了超时时间的断路另说。
  • 弹性设计相关,看一看《Spring 微服务实战》很有帮助。或者把 Spring Cloud 的组件,背景了解一下,对于微服务为什么要这样做就有谱了。大型互联网公司里在没有通用组件前都会有自研的类似组件,比如负载均衡,通用网关,鉴权,流水日志等。有一定经历,再来看微服务的设计会觉得他们其实非常相似。

扩展阅读