服务的容错

服务瘫痪的问题

微服务架构下的服务之间会频繁进行调用,由于网路或者其他因素,服务不能保证100%可用,倘若一个服务出了问题,就有可能引发整个系统的瘫痪,这是开发者不希望看到的情况。我们把微服务架构的应用比作是一家餐厅,餐厅里面分为前台接待员,点菜员,厨师,端菜员,收银员,这5个岗位好比是5个微服务,他们之间正常的调用才能服务好客户,如果其中一个岗位出了问题导致整个餐厅无法正常工作,那这个餐厅就太脆弱了。

下面通过代码来模拟一个微服务出现问题引发整个系统瘫痪的情况。修改之前pay模块中的Controller,在pay方法中利用线程睡眠模拟网络延迟,然后再添加一个退款的方法:

package com.monkey1024.controller;

import com.monkey1024.service.PayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;

/*
    支付服务
 */
@RestController
public class PayController {

    @Autowired
    private PayService payService;

    @GetMapping("pay")
    public String pay() {
        //模拟网络延迟的问题
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //支付操作
        payService.pay();

        return "支付成功";
    }

    @GetMapping("refund")
    public String refund() {
        return "退款成功";
    }
}

修改pay模块中的配置文件,将tomcat可以处理的线程设置为1

#tomcat端口号
server.port=8084
#最大线程数
server.tomcat.threads.max=1
#给微服务命名
spring.application.name=pay-service
#注册中心nacos的地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

buy模块中再添加一个cancel方法来调用上面的退款服务,下面的payUrl的值是从配置文件中获取的,即将微服务的地址放到配置文件中,通过代码来读取,这样做到了配置和代码的分离。

package com.monkey1024.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/*
    购买
 */
@RestController
public class BuyController {

    @Autowired
    private RestTemplate restTemplate;

    //从配置文件中读取调用服务的地址
    @Value("${service-url.pay-service}")
    private String payUrl;

    @GetMapping("buy")
    public String buy() {

        //通过微服务实例对象获取其地址和端口号进行调用
        String msg = restTemplate.getForObject(payUrl+"/pay", String.class);

        return "购买成功:" + msg;
    }

    /*
        取消交易
     */
    @GetMapping("cancel")
    public String cancel() {
        String msg = restTemplate.getForObject(payUrl + "/refund", String.class);
        return msg;
    }
}

buy模块的配置文件:

#tomcat端口号
server.port=8086
#给微服务命名
spring.application.name=buy-service
#注册中心nacos的地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#要调用微服务的名字,通过controller中的payUrl获取
service-url.pay-service:http://pay-service

将上面两个模块启动之后,打开第一个浏览器访问buy,然后迅速的打开第二个浏览器访问cancel,此时可以看到cancel是无法正常访问的。因为我们设置了tomcat处理的线程是1,并且在buy调用的服务中让线程睡眠了,这期间系统是无法处理其他请求的,倘若此时有大量请求涌入,系统就瘫痪了。

服务雪崩效应

在造船领域中有一个名词叫做水密隔舱,他解决的就是类似雪崩的问题:

微服务架构下,多个微服务之间会进行调用,倘若一个微服务出现了问题,故障会进行传播,此时就导致整个系统出现灾难性的后果,这个就是服务的雪崩效应。

如下图所示,A服务调用了B服务,B服务调用了C服务,C服务出现了问题。

此时B服务会不断的调用C服务,这期间会有新的请求A继续调用B服务,导致调用大量的积压,产生大量的调用等待和重试调用,这样会慢慢的耗尽B服务的资源,导致B出现问题。

道理同上,随着新的请求的到底,A服务的资源也就慢慢耗尽了,这样整个系统就瘫痪了。

服务雪崩出现的原因有很多种,比如流量激增,线程的等待,程序的bug,硬件故障等,开发者无法完全杜绝雪崩源头的发生,只有做好容错,当一个服务出现问题之后不会引发其他服务的故障。

常见的容错方案有下面几个:

  • 限流顾名思义就是限制客户端调用的流量,比如设置qps为100,超出之后直接拒绝请求。
  • 熔断当下游服务出现问题不可调用之后,就不再继续调用了,切断对下游服务的调用,相当于是牺牲局部保全整体。日常生活中电源的保险丝使用的就是这个原理。服务熔断一般有三种状态:
    • 熔断关闭状态(Closed)
      服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制
    • 熔断开启状态(Open)
      后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
    • 半熔断状态(Half-Open)
      尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。
  • 降级当下游服务不可调用之后,直接返给用户一个友好的提示信息或者调用其他备选服务。好比做事的时候列出了A计划和B计划,如果A计划失败的话就执行B计划。
  • 隔离好比是疫情期间,需要对患者进行隔离,这样就会切断病毒的传播路径,减少感染人数。常见的隔离方式有:线程池隔离和信号量隔离。

常见的容错组件

  • Hystrix
    Hystrix是由Netflix开源的一个延迟和容错库,目前已经停止开发了。
  • Resilience4J
    Resilience4J一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,是Hystrix官方推荐的替代产品。
  • Sentinel
    Sentinel 是阿里巴巴开源的一款组件,本身在阿里内部已经被大规模采用,非常稳定。

三者的对比

sentinel Hystrix resilience4j
隔离策略 信号量隔离 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口 滑动窗口 Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
注解的支持 支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式、匀速器模式、预热排队模式 不支持 简单的 Rate Limiter模式
系统自适应保护 支持 不支持 不支持
控制台 可配置规则、查看秒级监控、机器发现等 简单的监控查看 无,可对接其它监控系统