一.微服务雪崩问题

一.分布式系统问题

由于网络的不稳定性,决定了任何一个服务的可用性都不是 100% 的。当网络不稳定的时候,作为服务的提供者,自身可能会被拖死,导致服务调用者阻塞,最终可能引发雪崩效应。

二.可能产生雪崩的原因:

1.服务不可用:缓存击穿、大量的请求、程序bug、硬件故障、资源耗尽等导致服务不可用
2.流量过大:由于用户或者代码逻辑重试

三.现象:

1.开始线程1中微服务D不可用了,线程1阻塞在微服务D

2.线程2中,由于微服务C依赖于不可用的微服务D,那么导致微服务C也不可用,线程2阻塞在微服务C中

3.以此类推,该链路上的微服务都会变得不可用,产生雪崩效应。

微服务请求失败1000次 微服务已死_spring cloud


一个微服务故障导致整个链路的微服务都故障(链路连锁故障)。

四.常用解决方法:

1.超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
2.仓壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
3.熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
4.流量限流:限制业务访问的QPS,避免服务因流量的突增而故障。

二.Sentinel

一.Sentinel工具和Hystrix对比

Sentinel

Hystrix

隔离策略

信号量隔离

线程池隔离/信号量隔离

熔断降级策略

基于慢调用比例或异常比例

基于失败比率

实时指标实现

滑动窗口

滑动窗口(基于 RxJava)

规则配置

支持多种数据源

支持多种数据源

扩展性

多个扩展点

插件的形式

基于注解的支持

支持

支持

限流

基于 QPS,支持基于调用关系的限流

有限的支持

流量整形

支持慢启动、匀速排队模式

不支持

系统自适应保护

支持

不支持

控制台

开箱即用,可配置规则、查看秒级监控、机器发现等

不完善

常见框架的适配

Servlet、Spring Cloud、Dubbo、gRPC 等

Servlet、Spring Cloud Netflix

Sentinel是阿里巴巴的流量控制开源技术框架,Hystrix已经不维护了。

Sentinel:

丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。

完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

二.安装启动Sentinel

1.GitHUb下载Sentinel官服提供的jar包

微服务请求失败1000次 微服务已死_java_02


2.命令启动

java -jar sentinel-dashboard-1.8.1.jar

或者

java -Dserver.port=8090 -jar sentinel-dashboard-1.8.1.jar

3.访问localhost:8080,用户名密码都是sentinel

三.spring-cloud整合

微服务请求失败1000次 微服务已死_微服务请求失败1000次_03

1.引入依赖到order-service

<!--sentinel-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>0.9.0.RELEASE</version>
        </dependency>

2.配置order-service的yaml

server:
  port: 8088
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 159735
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: orderservice
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.136.160:8848
    sentinel:
      transport:
        dashboard: localhost:8080

#        namespace: 4d6ce343-9e1b-44df-a90f-2cf2b6b3d177 # dev环境
#        ephemeral: false # 是否是临时实例
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS

userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule  # 负载均衡规则
ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    clients: # 指定饥饿加载的服务名称
      - userservice
feign:
  httpclient:
    enabled: true # 支持HttpClient的开关
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 单个路径的最大连接数

3.父工程的依赖springBoot、SpringCloud、AlibbCloud版本一定要对应上

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http:///POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http:///POM/4.0.0 http:///xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.itcast.demo</groupId>
    <artifactId>cloud-sentinel</artifactId>
    <version>1.0</version>
    <modules>
        <module>user-service</module>
        <module>order-service</module>
        <module>feign-api</module>
        <module>gateway</module>
    </modules>

    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <mysql.version>5.1.47</mysql.version>
        <mybatis.version>2.1.1</mybatis.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- springCloud -->
<!--            阿里巴巴Cloud-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- mysql驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

三.雪崩保护措施

一.超时处理

设置超时时间,超过一定时间返回错误信息,这个是最常用的,但对客户很不友好。

微服务请求失败1000次 微服务已死_微服务_04

二.仓壁模式

类似于一艘船破损,只有部分舱门进水,每个舱门相互独立。限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。

微服务请求失败1000次 微服务已死_微服务请求失败1000次_05


业务A和业务B共用一个线程池,如果业务A调用的微服务B产生异常,会影响整个线程池连接。如果使用线程隔离,一个业务的线程池产生异常,不会影响其他业务,舱壁模式降低依赖服务对整个系统的影响,保护有限的资源不被耗尽,增加了系统得到弹性。

常用的两种隔离方式:
线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果

信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。信号量隔离是用Semaphore控制的

信号量隔离

线程隔离

优点

轻量级,无额外开销

支持主动超时,支持异步调用

缺点

不支持主动超时和异步调用

线程额外开销比较大

场景

高频调用,高扇出

低扇出

三.限流

控制流量:限制业务访问的QPS,避免服务因流量的突增而故障。是雪崩的预防措施。

手段

1.计数器算法:存在临界点问题。
2.滑块算法:就是包含多个计数器算法,所需内存较大。
3.漏桶算法:漏桶算法能够强行限制数据的传输速率。
4.令牌漏桶算法: 令牌桶算法能够在限制数据的平均传输速率的同时还允许某种
程度的突发传输。
5.簇点链路:对controller的监控。

漏桶和令牌对比:在某些情况下,漏桶算法不能够有效地使用网络资源。因为漏桶的漏出速率是固定的,所以即使网络中没有发生拥塞,漏桶算法也不能使某一个单独的数据流达到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。而令牌桶算法则能够满足这些具有突发特性的流量。通常,漏桶算法与令牌桶算法结合起来为网络流量提供更高效的控制。

四.熔断降级

一.服务熔断:

考试过程中当断则断的方式,正好符合微服务架构中的一种安全机制:熔断

在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。

二.服务降级:

当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。

微服务请求失败1000次 微服务已死_微服务_06


三.熔断的状态:

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

2.open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态

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

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

四.簇点链路限流测试

簇点链路: 当请求进入微服务时,首先会访问DispatcherServlet,然后进入Controller、Service、Mapper,这样的一个调用链就叫做簇点链路。簇点链路中被监控的每一个接口就是一个资源

默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint,也就是controller中的方法),因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。

一.流控模式

在添加限流规则时,点击高级选项,可以选择三种流控模式

  • 直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
  • 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
  • 链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

    QPS(TPS):每秒钟request/事务数量= 并发数/平均响应时间

二.测试限流模式

一.直接模式测试

1.给资源/order/query新增限流规则,阈值为5

微服务请求失败1000次 微服务已死_微服务请求失败1000次_07


2.使用JMeter测试,线程数20,2秒内,循环1次,QPS为10,超过了设置的5

微服务请求失败1000次 微服务已死_微服务_08


结果20次请求,2秒内只有4个请求成功

微服务请求失败1000次 微服务已死_java_09

二.关联模式测试

1.给资源/order/query关联/order/update,新增限流规则,阈值为5,如果资源/order/update 请求次数到达阈值5,/order/query请求失败

微服务请求失败1000次 微服务已死_微服务_10


2.query和update两个请求,设置1000个用户,100秒,QPS为1000/100=10,超过了设置的阈值5

微服务请求失败1000次 微服务已死_spring cloud_11


结果order/update达到阈值,order/query请求失败

微服务请求失败1000次 微服务已死_限流_12


微服务请求失败1000次 微服务已死_spring cloud_13

二.链路模式测试

1.分别用order/query和order/save的controller调用service层的queryGoods方法,该方法需要通过注解来被Sentinel监控

微服务请求失败1000次 微服务已死_限流_14


2.链路模式中是对不同来源的链路进行监控,sentinel默认会给进入SpringMVC的所有请求设置同一个root资源,会导致链路模式失效。

需要关闭这种对SpringMVC的资源聚合,修改order-service服务的application.yml文件:

spring:
  cloud:
    sentinel:
      web-context-unify: false # 关闭context整合

重启服务然后分别请求发现goods资源添加上了,记得Sentinel依赖版本要1.8.0以上才生效

微服务请求失败1000次 微服务已死_微服务请求失败1000次_15


3.随便点击一个goods资源后面的流控按钮,因为是同一资源,设置阈值为2,统计从这个路口进入的

微服务请求失败1000次 微服务已死_微服务请求失败1000次_16


4.设置线程数200,时间50秒,那么QPS就是4,超过设置的阈值2

微服务请求失败1000次 微服务已死_微服务_17


5.由于QSP是4,设置的是2,所以一直有两个请求query请求失败

微服务请求失败1000次 微服务已死_限流_18

三.流控效果

流控效果是指请求达到流控阈值时应该采取的措施,包括三种:

  • 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
  • warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
  • 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长

四.测试控流效果

一.快速失败

上面测试限流模式都是用快速失败

二.warm up

阈值一般是一个微服务能承担的最大QPS,但是一个服务刚刚启动时,一切资源尚未初始化(冷启动),如果直接将QPS跑到最大值,可能导致服务瞬间宕机。

warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 maxThreshold / coldFactor,持续指定时长后,逐渐提高到maxThreshold值。而coldFactor的默认值是3.

微服务请求失败1000次 微服务已死_微服务_19


1.配置控流规则:给/order/query这个资源设置限流,最大QPS为10,利用warm up效果,预热时长为5秒

微服务请求失败1000次 微服务已死_限流_20


2.设置JMeter:线程数200,时间20秒,所以QPS是10,等于设置的阈值10

微服务请求失败1000次 微服务已死_限流_21


3.结果:刚开始失败请求多然后成功请求逐渐趋于平稳,最后都成功

微服务请求失败1000次 微服务已死_限流_22


4.去Sentinel控制台查看实时监测

微服务请求失败1000次 微服务已死_spring cloud_23

三.排队等待

排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。

例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待时长超过2000ms的请求会被拒绝并抛出异常。

比如现在一下子来了12 个请求,因为每200ms执行一个请求,那么:

  • 第6个请求的预期等待时长 = 200 * (6 - 1) = 1000ms
  • 第12个请求的预期等待时长 = 200 * (12-1) = 2200ms

1.配置控流规则:设置阈值为10,超时时间5秒

微服务请求失败1000次 微服务已死_微服务_24


2.设置JMeter参数:线程数300,时间20,所以QPS是15,超过阈值10。超出的请求会失败

微服务请求失败1000次 微服务已死_spring cloud_25


3.结果:开始都请求成功了,中间超出部分放在队列中,队列满了会请求失败

微服务请求失败1000次 微服务已死_限流_26

4.查看Sentinel的实时监控

微服务请求失败1000次 微服务已死_限流_27

五.热点参数限流

一.介绍

热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。

微服务请求失败1000次 微服务已死_java_28


热点参数限流会根据参数值分别统计QPS,当id=1的请求触发阈值被限流时,id值不为1的请求不受影响。

参数

QPS

id=1

3

id=2

1

二.热点限流测试

一.标记资源

给order-service中的OrderController中的/order/{orderId}资源添加注解:

微服务请求失败1000次 微服务已死_java_29

二.热点参数限流规则

给/order/{orderId}这个资源添加热点参数限流,规则如下:

•默认的热点参数规则是每1秒请求量不超过2

•给102这个参数设置例外:每1秒请求量不超过4

•给103这个参数设置例外:每1秒请求量不超过10

微服务请求失败1000次 微服务已死_微服务_30

三.配置JMeter参数

线程数500,时间100秒,所以QPS是5,超过参数101、102设置的阈值2,不超过参数103的阈值10

微服务请求失败1000次 微服务已死_spring cloud_31

四.结果

JMeter设置的QPS是5,参数101阈值是2,参数102阈值是4,参数103阈值是10,结果通过的请求效果如下

微服务请求失败1000次 微服务已死_微服务请求失败1000次_32


热点限流是针对某个资源的参数进行限流,一般用于限时秒杀等,也很鸡肋

六.FeignClient整合Sentinel降级

我们的微服务远程调用都是基于Feign来完成的,因此我们需要将Feign与Sentinel整合,在Feign里面实现线程隔离和服务熔断。

一.修改配置,开启sentinel功能

修改OrderService的application.yml文件,开启Feign的Sentinel功能:

feign:
  sentinel:
    enabled: true # 开启feign对sentinel的支持

二.编写失败降级逻辑

给FeignClient编写失败后的降级逻辑

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

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

一.在feign中编写一个服务

微服务请求失败1000次 微服务已死_spring cloud_33

二.注册bean

微服务请求失败1000次 微服务已死_java_34

三.在FeignClient中使用

微服务请求失败1000次 微服务已死_spring cloud_35


要注意啊,因为feign-api这个是被order服务依赖,所有应该让order的主方法,扫到feign项目的对象,这个FallBackFactory工厂才能被注册上

四.访问一次订单

就设置好降级服务了,待会进行熔断的时候用

微服务请求失败1000次 微服务已死_限流_36

七.sentinel的线程隔离

一.配置隔离规则

feign客户端簇点链路这里配置规则

微服务请求失败1000次 微服务已死_微服务_37


给 order-service服务中的UserClient的查询用户接口设置流控规则,线程数不能超过 2

微服务请求失败1000次 微服务已死_java_38

二.JMeter测试

0秒内发送10个请求

微服务请求失败1000次 微服务已死_微服务_39

结果

微服务请求失败1000次 微服务已死_微服务_40

八.熔断降级测试

一.慢调用

慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。

一.设置休眠时间

当order服务调用这个queryById,查询id=1的用户线程会休眠60ms

微服务请求失败1000次 微服务已死_java_41

二.设置熔断规则

给 UserClient的查询用户接口设置降级规则,慢调用的RT阈值为50ms,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5

微服务请求失败1000次 微服务已死_微服务_42

三.浏览器测试

1秒内有5*0.4=2个请求超过60ms,会触发降级。在浏览器快速刷新5次,发现:

微服务请求失败1000次 微服务已死_微服务_43


前几次user是不为null的,后面触发了熔断的满调用策略,对查询用户资源进行了熔断降级,访问id=102也降级了

微服务请求失败1000次 微服务已死_限流_44

二.异常比例、异常数

异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

一.设置异常

当order服务调用这个queryById,查询id=2的用户,抛异常

微服务请求失败1000次 微服务已死_微服务_45

二.设置熔断规则

给 UserClient的查询用户接口设置降级规则,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5s

微服务请求失败1000次 微服务已死_spring cloud_46


在5次请求中,只要异常比例超过0.4,也就是有2次以上的异常,就会触发熔断。

三.浏览器测试

在浏览器快速刷新5次,发现:

微服务请求失败1000次 微服务已死_微服务_47

九.授权规则

一.基本规则

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式:

  • 白名单:来源(origin)在白名单内的调用者允许访问
  • 黑名单:来源(origin)在黑名单内的调用者不允许访问
  • 资源名:就是受保护的资源,例如/order/{orderId}
  • 流控应用:是来源者的名单,
  • 如果是勾选白名单,则名单中的来源被许可访问。
  • 如果是勾选黑名单,则名单中的来源被禁止访问。

    允许请求从gateway到order-service,不允许浏览器访问order-service,那么白名单中就要填写网关的来源名称(origin)

二.获取origin

Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。

order-service服务中,我们定义一个RequestOriginParser的实现类:

微服务请求失败1000次 微服务已死_限流_48


这个方法的作用就是从request对象中,获取请求者的origin值并返回。

默认情况下,sentinel不管请求者从哪里来,返回值永远是default,也就是说一切请求的来源都被认为是一样的值default。

三.给网关添加请求头

既然获取请求origin的方式是从reques-header中获取origin值,我们必须让所有从gateway路由到微服务的请求都带上origin头

修改gateway服务中的application.yml,添加一个defaultFilter:

spring:
  cloud:
    gateway:
      default-filters:
        - AddRequestHeader=origin,gateway

从gateway路由的所有请求都会带上origin头,值为gateway。而从其它地方到达微服务的请求则没有这个头。

四.配置授权规则

微服务请求失败1000次 微服务已死_spring cloud_49

五.测试

1.跳过网关,访问order-service服务:

微服务请求失败1000次 微服务已死_微服务请求失败1000次_50


2.通过网关访问,因为在网关加了权限校验,所以得在url加上authorizatinotallow=admin:

微服务请求失败1000次 微服务已死_微服务请求失败1000次_51

十.自定义异常结果

默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。异常结果都是flow limmiting(限流)。这样不够友好,无法得知是限流还是降级还是授权拦截。

微服务请求失败1000次 微服务已死_微服务请求失败1000次_52


BlockException包含多个不同的子类:

异常

说明

FlowException

限流异常

ParamFlowException

热点参数限流的异常

DegradeException

降级异常

AuthorityException

授权规则异常

SystemBlockException

系统规则异常

十一.规则持久化

一.规则管理模式

规则是否能持久化,取决于规则管理模式,sentinel支持三种规则管理模式:

  • 原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。
  • pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。
  • 微服务请求失败1000次 微服务已死_微服务_53

  • push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
  • 微服务请求失败1000次 微服务已死_java_54

  • 码农不常用具体配置,不解说