微服务容错及解决

学前必备知识

学之前我们要理解一些概念,之后我们会遇到QPS,并发量,线程等专有名词。

一文搞懂高并发性能指标:QPS、TPS、RT、并发数、吞吐量 - 知乎 (zhihu.com)

雪崩问题

1 是什么

在微服务远程调用的过程中,还存在几个问题需要解决。

首先是业务健壮性问题:

例如在之前的查询购物车列表业务中,购物车服务需要查询最新的商品信息,与购物车数据做对比,提醒用户。大家设想一下,如果商品服务查询时发生故障,查询购物车列表在调用商品服 务时,是不是也会异常?从而导致购物车查询失败。但从业务角度来说,为了提升用户体验,即便是商品查询失败,购物车列表也应该正确展示出来,哪怕是不包含最新的商品信息。

还有级联 失败问题:

还是查询购物车的业务,假如商品服务业务并发较高,占用过多Tomcat连接。可能会导致商品服务的所有接口响应时间增加,延迟变高,甚至是长时间阻塞直至查询失败。

此时查询购物车业务需要查询并等待商品查询结果,从而导致查询购物车列表业务的响应时间也变长,甚至也阻塞直至无法访问。而此时如果查询购物车的请求较多,可能导致购物车服务的Tomcat连接占用较多,所有接口的响应时间都会增加,整个服务性能很差, 甚至不可用。

依次类推,整个微服务群中与购物车服务、商品服务等有调用关系的服务可能都会出现问题,最终导致整个集群不可用

这就是级联 失败 问题,或者叫雪崩问题。

2 产生原因

微服务相互调用,服务提供者出现故障或阻塞。

服务调用者没有做好异常处理,导致自身故障。

调用链中的所有服务级联失败,导致整个集群故障

3 解决问题的思路

尽量避免服务出现故障或阻塞。 保证代码的健壮性;

保证网络畅通; 能应对较高的并发请求;

服务调用者做好远程调用异常的后备方案,避免故障扩散

4 解决方案

微服务保护的方案有很多,比如:

  • 请求限流

  • 线程隔离

  • 服务熔断

这些方案或多或少都会导致服务的体验上略有下降,比如请求限流, 降低了并发上限;线程隔离,降低了可用资源数量 ;服务熔断,降低了服务的完整度 ,部分服务变的不可用或弱可用。因此这些方案都属于服务降级的方案。但通过这些方案,服务的健壮性得到了提升,

接下来,我们就逐一了解这些方案的原理。

4.1 请求限流

服务故障最重要原因,就是并发太高!解决了这个问题,就能避免大部分故障。当然,接口的并发不是一直很高,而是突发的。因此请求限流,就是限制或控制接口访问的并发流量,避免服务因流量激增而出现故障。

请求限流往往会有一个限流器,数量高低起伏的并发请求曲线,经过限流器就变的非常平稳。这就像是水电站的大坝,起到蓄水的作用,可以通过开关控制水流出的大小,让下游水流始终维持在一个平稳的量。

4.2 线程隔离

当一个业务接口响应时间长,而且并发高时,就可能耗尽服务器的线程资源,导致服务内的其它接口受到影响。所以我们必须把这种影响降低,或者缩减影响的范围。线程隔离正是解决这个问题的好办法。

线程隔离的思想来自轮船的舱壁模式:

轮船的船舱会被隔板分割为N个相互隔离的密闭舱,假如轮船触礁进水,只有损坏的部分密闭舱会进水,而其他舱由于相互隔离,并不会进水。这样就把进水控制在部分船体,避免了整个船舱进水而沉没。

为了避免某个接口故障或压力过大导致整个服务不可用,我们可以限定每个接口可以使用的资源范围,也就是将其"隔离"起来。

如图所示,我们给查询购物车业务限定可用线程数量上限为20,这样即便查询购物车的请求因为查询商品服务而出现故障,也不会导致服务器的线程资源被耗尽,不会影响到其它接口。

4.3 服务熔断

线程隔离虽然避免了雪崩问题,但故障服务(商品服务)依然会拖慢购物车服务(服务调用方)的接口响应速度。而且商品查询的故障依然会导致查询购物车功能出现故障,购物车业务也变的不可用了。

所以,我们要做两件事情:

  • 编写服务降级逻辑:就是服务调用失败后的处理逻辑,根据业务场景,可以抛出异常,也可以返回友好提示或默认数据。

  • 异常统计和熔断:统计服务提供方的异常比例,当比例过高表明该接口会影响到其它服务,应该拒绝调用该接口,而是直接走降级逻辑。

4.4 失败处理

快速失败:给业务编写一个调用失败时的处理的逻辑,称为fallback。当调用出现故障(比如无线程可用)时,按照失败处理逻辑执行业务并返回,而不是直接抛出异常。

5 服务保护技术

我们要学习的是sentinel技术

Sentinel

1 介绍

微服务保护的技术有很多,但在目前国内使用较多的还是Sentinel,所以接下来我们学习Sentinel的使用

Sentinel是阿里巴巴开源的一款服务保护框架,目前已经加入SpringCloudAlibaba中。官方网站:home | Sentinel (sentinelguard.io)

Sentinel 的使用可以分为两个部分:

  • 核心库(Jar包):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。

  • 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。

2 快速入门

2.1 下载jar包

2.2 运行

将jar包放在任意非中文、不包含特殊字符的目录下,重命名为sentinel-dashboard.jar

然后运行如下命令启动控制台:

我们将端口号设置成8090防止冲突

bash 复制代码
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

2.3 访问

访问http://localhost:8090页面,就可以看到sentinel的控制台了:

需要输入账号和密码,默认都是:sentinel

登录后,即可看到控制台,默认会监控sentinel-dashboard服务本身:

2.4 整合到微服务中

步骤一 引入依赖

java 复制代码
<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2022.0.0.0-RC2</version>
            <type>pom</type>
        </dependency>
        <!--sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2022.0.0.0-RC2</version>
        </dependency>

步骤二 配置控制台

修改application.yaml文件,添加下面内容:

java 复制代码
spring:
  cloud: 
    sentinel:
      transport:
        dashboard: localhost:8090

这样我们的Sentinel就基本配置好了

3 初步使用

3.1 服务的搭建

由于我们初步学习,我们先搭建一个简单的服务。里面就只含有一个Controller。我们之后就对这一个接口进行访问

java 复制代码
@Slf4j
@RestController
@RequestMapping("/customer/info")
public class HelloWController {

    @GetMapping("/HelloWorld")
    public void HW(){
        System.out.println("HelloWorld");
    }
    
}

3.2 访问接口

当我们去访问上面的接口后,sentinel的客户端就会将服务访问的信息提交到sentinel-dashboard控制台。并展示出统计信息:

点击簇点链路菜单,会看到下面的页面:

所谓簇点链路,就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel监控的资源。默认情况下,Sentinel会监控SpringMVC的每一个Endpoint(接口),就是controller向外面暴露的接口。

因此,我们看到/carts这个接口路径就是其中一个簇点,我们可以对其进行限流、熔断、隔离等保护措施。

不过,需要注意的是,我们的SpringMVC接口是按照Restful风格设计,因此接口全部都是/customer/info路径。默认情况下Sentinel会把路径作为簇点资源的名称,无法区分路径相同但请求方式不同的接口,查询、删除、修改等都被识别为一个簇点资源,这显然是不合适的

所以我们可以选择打开Sentinel的请求方式前缀,把**请求方式 + 请求路径作为簇点资源名**:

首先,在application.yml中添加下面的配置:

java 复制代码
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090
      http-method-specify: true # 开启请求方式前缀

然后,重启服务,通过页面访问相关接口,可以看到sentinel控制台的簇点链路发生了变化:

4 请求限流

在簇点链路后面点击流控按钮,即可对其做限流配置

在弹出的菜单中这样填写,这样就把这个簇点资源的流量限制在了每秒6个,也就是最大QPS为6.:

我们利用Jemeter做限流测试,我们每秒发出10个请求

最终监控结果如下:

我们一秒发10个请求,然后限流6个,所以拒绝4个

5 线程隔离

限流可以降低服务器压力,尽量减少因并发流量引起的服务故障的概率,但并不能完全避免服务故障。一旦某个服务出现故障,我们必须隔离对这个服务的调用,避免发生雪崩。

比如,查询购物车的时候需要查询商品,为了避免因商品服务出现故障导致购物车服务级联失败,我们可以把购物车业务中查询商品的部分隔离起来,限制可用的线程资源:

首先点击簇点资源后面的流控按钮注意

这里勾选的是并发线程数限制,也就是说这个查询功能最多使用5个线程,而不是5QPS。如果查询商品的接口每秒处理2个请求,则5个线程的实际QPS在10左右,而超出的请求自然会被拒绝:

其次,调整访问代码,每一次执行需要睡眠500毫秒。这就是说,每一秒并发为2,QPS为2.那么有五个线程,QPS就是10.

最后,我们测试每秒并发量100

结果,QPS是10,拒绝的请求是90,符合预期。

6 编写降级逻辑 FallBack

触发限流或熔断后的请求不一定要直接报错,也可以返回一些默认数据或者友好提示,用户体验会更好。

给FeignClient编写失败后的降级逻辑有两种方式:

  • 方式一:FallbackClass,无法对远程调用的异常做处理

  • 方式二:FallbackFactory,可以对远程调用的异常做处理,我们一般选择这种方式。

注意!!!

我们不建议对整个服务做限流做隔离,只简易对远程调用这些接口来小规模地隔离

1 代码解读

我这里编写了一个简单的调用逻辑来实现

1.1 接受前段传来请求的controller

这里我们远程调用,因为fallback就是要远程调用框架实现,所以多了远程调用部分

java 复制代码
@Slf4j
@RestController
@RequiredArgsConstructor
public class HelloWController {

    private final idClient idClient;

    @GetMapping("/HelloWorld")
    public void HW() throws InterruptedException {
        Thread.sleep(500);
        idClient.helloworld();
    }
}

1.2 远程调用接口(具体实现)

这里比正常的远程调用在注解那多了一个fallbackFactory参数 ,这里表示我们请求失败之后会执行的地方,也就是远程调用失败后执行fallback的逻辑

java 复制代码
@FeignClient(value = "LearnSentinel", fallbackFactory = UserClientFallbackFactory.class)
public interface idClient {

    @GetMapping("/customer/info/HelloWorld/client")
    void helloworld();
}

1.3 fallback逻辑(具体实现)

我们定义了一个 Fallback 工厂,实现了 FallbackFactory 类。泛型就是我们这个逻辑是在哪个远程调用失败后执行的类,然后去重写里面的 create方法 ,fallback逻辑就是在这里定义的

java 复制代码
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<idClient>{
    @Override
    public idClient create(Throwable cause) {
        log.error("查询用户失败", cause);
        return null;
    }
}

1.4 将 Fallback工厂注入到spring环境(具体实现)

java 复制代码
@Configuration
public class UserClientFallbackFactoryConfig {

    @Bean
    public UserClientFallbackFactory factory(){
        return new UserClientFallbackFactory();
    }
}

1.5 远程调用的接口

java 复制代码
@RestController
@Slf4j
@RequestMapping("/customer/info")
@RequiredArgsConstructor
public class hewcc {

    @GetMapping("/HelloWorld/client")
    public void hw(){
        log.info("helloworld");
    }
}

2 测试启动!!!

我将模拟并发参数改成QPS为10,线程隔离给服务分配了一个线程,也就是允许QPS为2,则失败的QPS为8,结果符合

7 服务熔断

7.1 使用场景

查询商品的RT较高(模拟的500ms),从而导致查询购物车的RT也变的很长。这样不仅拖慢了购物车服务,消耗了购物车服务的更多资源,而且用户体验也很差。

对于商品服务这种不太健康的接口,我们应该停止调用,直接走降级逻辑,避免影响到当前服务。也就是将商品查询接口熔断 。当商品服务接口恢复正常后,再允许调用。这其实就是断路器的工作模式了。

Sentinel中的断路器不仅可以统计某个接口的慢请求比例 ,还可以统计异常请求比例 。当这些比例超出阈值时,就会熔断该接口,即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。

7.2 工作原理

断路器的工作状态切换有一个状态机来控制。

如果我们服务正常,就会放行请求,closed状态;如果服务响应时间达到熔断阈值,进入open状态,来拦截请求,进入Fallback逻辑。等到熔断时间结束,就进入Half-open状态,放行一次看看结果。如果结果还是失败就再次进入open状态。如果结果成功就进入closed状态,放行。

状态机包括三个状态:

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态

  • open :打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态

  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。

    • 请求成功:则切换到closed状态

    • 请求失败:则切换到open状态

7.3 在控制台配置熔断逻辑

这里我们选择慢调用比例

最大RT: 指最大的相应时间,单位为ms

比例阈值:只失败和成功的比例。0.5表示失败率为百分之50

熔断时长:从open到half-open的状态

最小请求数:如果测试数超过5以后比例阈值大于0.5,那么就熔断。判断熔断的次数

统计时长:在这段时间内来统计上面所说的条件。

当然,我们熔断功能还包含着其他判断熔断条件:异常比例和异常数 参数也可以理解,和上面的差不多

相关推荐
liux35286 分钟前
k8s之Ingress讲解
云原生·容器·kubernetes
拉丁解牛说技术11 分钟前
AI大模型进阶系列(01)AI大模型的主流技术 | AI对普通人的本质影响是什么?
后端·架构·openai
阿里云云原生19 分钟前
函数计算支持热门 MCP Server 一键部署
云原生
r0ad21 分钟前
文生图架构设计原来如此简单之交互流程优化
架构·aigc
热爱运维的小七32 分钟前
从数据透视到AI分析,用四层架构解决运维难题
运维·人工智能·架构
阿里云云原生41 分钟前
AI 网关代理 LLMs 最佳实践
云原生
桂月二二1 小时前
实时事件流处理架构的容错设计
架构·wpf
喵个咪2 小时前
开箱即用的GO后台管理系统 Kratos Admin - 定时任务
后端·微服务·消息队列
小刘爱喇石( ˝ᗢ̈˝ )4 小时前
玛卡巴卡的k8s知识点问答题(六)
云原生·容器·kubernetes