微服务保护

一、微服务保护基础

1.1 雪崩问题的产生原因

  在前面两篇blog中解决了微服务项目中远程调用、网关和配置相关的内容,这已经可以解决微服务项目中大部分的问题了,但是仍存在一些问题,考虑如下场景:
  一个微服务项目,服务A需要调用服务B提供的服务,但是服务B可能会抛出异常;或者服务B并发量很高,导致服务B响应服务A的时延很长,甚至超时导致查询失败。此时会导致服务A的响应时间很长,若在高并发的场景下,Tomcat连接占用很多,会拖垮服务中其他接口的响应时间。
  若还有其他服务调用服务A,同样会拖垮调用者服务,这样会导致级联失败,产生雪崩问题

1.2 服务保护方案

  • 请求限流
  • 线程隔离
  • 服务熔断

请求限流

  产生雪崩问题的原因,归根结底就是并发太高!因此请求限流,就是限制或控制接口访问的并发流量,避免服务因流量激增而出现故障。

线程隔离

  当一个服务接口响应时间长,并且处在高并发状态时,此时会占用大量的Tomcat连接,这样会导致服务内其他接口的请求时延也大大增加。为解决这个问题,可以对服务内的接口设置占用最大线程数,这样该服务接口不会大量占用Tomcat连接,可以保护服务内的其他接口。

服务熔断

  限流和线程隔离策略可以达到对服务内其他接口的保护,但是这种处理策略会让服务调用处堆积大量的请求。
  服务熔断策略中存在一个熔断器,它会统计服务调用的时延和异常次数,若时延过高的响应或者异常次数超过阈值,那么就会中断被请求服务的接口,所有请求走fallback逻辑。
  在这个策略中,需要做两件事:

  • 监控服务提供接口的异常率或者时延过高的响应;
  • 便也fallback处理逻辑,一般是返回默认值或者提示

二、Sentinel

2.1 介绍

  Sentinel是SpringCloudAlibaba提供的一款服务保护框架(https://sentinelguard.io/zh-cn/docs/introduction.html)。
  下载好后,通过如下命令就可以运行

1
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

2.2 快速使用

  1. 步骤一:引入sentinel依赖
    1
    2
    3
    4
    5
    <!--sentinel-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
  2. 步骤二:添加sentinel board地址配置
    1
    2
    3
    4
    5
    6
    spring:
    cloud:
    sentinel:
    transport:
    dashboard: localhost:8090
    http-method-specify: true # 开启请求方式前缀
  3. 步骤三:OpenFeign整合sentinel
    1
    2
    3
    feign:
    sentinel:
    enabled: true # 开启feign对sentinel的支持

  完成以上配置,接下载只需要在sentinel网站上“点点点”就可以实现请求限流和线程隔离啦~

2.3 线程熔断实现

  在1.3小节中提到过,要实现线程熔断需要完成两件事:监控和编写fallback逻辑代码。在sentinel中只提供了监控功能,剩下的fallback代码需要自己编写。

  1. 步骤一:在api模块中编写实现FallbackFactory接口的实现类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Slf4j
    public class ItemClientFallback implements FallbackFactory<ItemClient> {
    @Override
    public ItemClient create(Throwable cause) {
    return new ItemClient() {
    @Override
    public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
    log.error("远程调用ItemClient#queryItemByIds方法出现异常,参数:{}", ids, cause);
    // 查询购物车允许失败,查询失败,返回空集合
    return CollUtils.emptyList();
    }

    @Override
    public void deductStock(List<OrderDetailDTO> items) {
    // 库存扣减业务需要触发事务回滚,查询失败,抛出异常
    throw new BizIllegalException(cause);
    }
    };
    }
    }
  2. 步骤二:在配置类中将刚刚编写的fallback注册为一个bean

    1
    2
    3
    4
    @Bean
    public ItemClientFallback itemClientFallback () {
    return new ItemClientFallback();
    }
  3. 步骤三:在相应的Client类中的FeignClient注解中配置fallbackFactory属性

    1
    2
    3
    4
    @FeignClient(value = "item-service",
    fallbackFactory = ItemClientFallback.class,
    configuration = DefaultFeignConfig.class)
    public interface ItemClient
  4. 步骤四:在sentinel中配置熔断规则