20-SpringCloudAlibaba-2

六 分布式流量防护

1 认识分布式流量防护

在这里插入图片描述

在分布式系统中,服务之间的相互调用会生成分布式流量。如何通过组件进行流量防护,并有效控制流量,是分布式系统的技术挑战之一。

什么是服务雪崩

假设我有一个微服务系统,这个系统内包含了 ABCD 四个微服务,这四个服务都是以集群模式构建的。

在这里插入图片描述

雪崩问题:

微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。

解决方案

在这里插入图片描述

服务保护技术

在SpringCloud当中支持多种服务保护技术:

  • Hystrix
  • Resilience4J
  • Sentinel

Sentinel 服务容错的思路

Sentinel 是 Spring Cloud Alibaba 的一款服务容错组件,我们也经常把它叫做“防流量哨兵”。它是阿里巴巴双十一促销核心场景的保护神,内置了丰富的服务容错应用场景。它以流量作为切入点,通过各种内外防控手段达到维持服务稳定性的目的。

在这里插入图片描述

内部异常治理

在 Sentinel 中,我们可以采用降级和熔断的方式处理内部的异常。所谓降级,是指当服务调用发生了响应超时、服务异常等情况时,我们在服务内部可以执行一段“降级逻辑”。

在这里插入图片描述

而所谓熔断,是指当异常调用量达到一定的判定条件,比如在异常降级和慢调用请求的比例达到一个阈值、窗口时间内降级请求达到一定数量的情况下,微服务在一段时间内停止对目标服务发起调用,所有来访请求直接执行降级逻辑。所以,熔断是“多次服务调用异常”累积的结果

在这里插入图片描述

外部流量控制

限流是流量整形流控方案的一种。在 Sentinel 中我们可以根据集群的处理能力,为每个服务设置一个限流规则,从 QPS 维度或者并发线程数的维度控制外部的访问流量。一旦访问量超过阈值,后续的请求就会被 “fast fail”,这是最为常用的一种限流手段。

在这里插入图片描述

2 认识Sentinel

在这里插入图片描述

Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。

Sentinel 的主要特性

在这里插入图片描述

特性:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近10年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel
  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel 分为两个部分

  • 控制台(Dashboard):控制台主要负责管理推送规则、监控、集群限流分配管理、机器发现等。
  • 核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。

在这里插入图片描述

注意:

Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。

Sentinel谁在用

在这里插入图片描述

Sentinel 与 Hystrix、resilience4j 的对比

在这里插入图片描述

3 Sentinel控制台环境安装基于Linux

在这里插入图片描述

获取 Sentinel 控制台

下载https://github.com/alibaba/Sentinel/releases

上传sentinel-dashboard-1.8.3.jar到虚拟机

启动 Sentinel 控制台

linux模式

java -Dserver.port=8878 \
-Dcsp.sentinel.dashboard.server=114.117.183.67:8878 \
-Dproject.name=sentinel-dashboard \
-jar ./sentinel-dashboard-1.8.3.jar

后台运行:

nohup java -server -Xms64m -Xmx256m \
-Dserver.port=8878 \
-Dcsp.sentinel.dashboard.server=114.117.183.67:8878 \
-Dproject.name=sentinel-dashboard \
-jar ./sentinel-dashboard-1.8.3.jar >> /opt/sentinel.log 2>&1 &

windows模式

java -jar ./sentinel-dashboard-1.8.3.jar --server.port=8878 --csp.sentinel.dashboard.server=localhost:8878 --project.name=sentinel-dashboard

注意:

强烈建议使用JRE8启动Sentinel控制台,高版本JDK可能存在未知BUG。

测试访问

访问http://114.117.183.67:8878/即可,登录用户名密码都是 sentinel。

在这里插入图片描述

在这里插入图片描述

备注

sentinel控制台服务器部署在云端,首先打算在本地启动微服务连接云上的sentinel

发现仅能注册进服务,却不能显示监控信息和链路信息。

查询日志后发现,云上的sentinel只能从注册中心拿到微服务,但是还是没有真正的连上本地8179端口,连接会报超时

总结:

由于sentinel需要主动连接到你的微服务才能工作,所以sentinel如果想要部署在云端,则必须让云端能够访问到你的微服务(而不是你的微服务能访问到云端,即双边的连接都必须是通的),将微服务放到云上就是保证了你的微服务能被sentinel访问到。

(本文中,我就在windows上本地localhost启动sentinel-dashboard)

4 Sentinel控制台环境安装基于Docker

在这里插入图片描述

拉取镜像

docker pull docker.io/bladex/sentinel-dashboard

创建启动容器

docker run --name sentinel --restart=always -d -p 8878:8858 docker.io/bladex/sentinel-dashboard

查看容器

docker ps

5 将应用接入Sentinel

在这里插入图片描述

创建工程cloud-sentinel-payment8001

在这里插入图片描述

POM引入依赖

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--借用监控开放端口-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- Nacos 依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- sentinel依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

编写主启动类

/**
 * 主启动类
 */
@SpringBootApplication
@Slf4j
@EnableDiscoveryClient
public class PaymentSentinelMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentSentinelMain8001.class,args);
        log.info("*********  PaymentSentinelMain8001 启动成功 ********");
    }
}

编写YML配置文件

server:
  port: 8001
spring:
  application:
    name: payment-provider-sentinel
  cloud:
    nacos:
      discovery:
        # Nacos服务地址
        server-addr: 114.117.183.67:8848
    sentinel:
      transport:
        # Sentinel控制台地址
        dashboard: localhost:8878
        # Sentinel api端口号,默认8719,
        # 假如被占用了会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719




logging:
  pattern:
    #日志打印格式
    console: logging.pattern.console=%d{MM/dd HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n

编写测试controller

/**
 * 流控规则控制层
 */
@RestController
public class FlowLimitController {

    /**
     * 测试
     *
     * @return
     */
    @GetMapping("/testA")
    public String testA() {
        return "testA";
    }
}

查看Sentinel控制台

在这里插入图片描述

注意:
Sentinel采用懒加载,发送一次请求即可。

发送请求
发送http://localhost:8878

在这里插入图片描述

6 流量控制概述

在这里插入图片描述

监控应用流量的 QPS并发线程数,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

在这里插入图片描述

流量控制设计理念

流量控制有以下几个角度:

  • 资源的调用关系:例如资源的调用链路,资源和资源之间的关系;
  • 运行指标:例如 QPS、线程池、系统负载等;
  • 控制的效果:例如直接限流、冷启动、排队等。

注意:

Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

流控规则

在这里插入图片描述

参数:

  • 资源名:其实就是我们请求的资源路径
  • 针对来源:这个是此流控规则对应那些微服务进行流控管理,一般填写调用方的微服务名称,多个用","分割
  • 阈值类型:一般有2中类型,QPS(每秒的最大请求数2)和线程数(并发线程数)
  • 单机阈值:单机状态下的最大限制值
  • 是否集群:根据实际情况选择

流控模式

在这里插入图片描述

参数:

  • 直接:直接作用于当前资源,如果访问压力大于某个阈值,后续请求将被直接拦下来;
  • 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
  • 链路:当指定链路上的访问量大于某个阈值时,对当前资源进行限流,这里的“指定链路”是细化到 API 级别的限流维度。

流控效果

在这里插入图片描述

参数:

  • 快速失败:默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出 FlowException 。
  • Warm Up:即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压。
  • 排队等待:匀速排队方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法 。这种方式主要用于处理间隔性突发的流量。

7 流控模式之直接模式

在这里插入图片描述

当 QPS 超过某个阈值的时候,则采取措施进行流量控制。

注意

若使用除了直接拒绝之外的流量控制效果,则调用关系限流策略(strategy)会被忽略。

配置QPS流控规则

在这里插入图片描述

注意:

表示1秒钟内查询1次就ok,若超过1次,就直接快速失败,报默认错误。

在这里插入图片描述

测试流量控制QPS

发送请求http://localhost:8001/testA

在这里插入图片描述

快速发送请求

在这里插入图片描述

8 流控模式之关联模式

在这里插入图片描述

什么是关联
当与之关联的资源达到阈值后,就限流自己。如下图:当与/testB关联的资源/testA达到阈值后,就限流/testB

新增测试接口

/**
 * 流控规则控制层
 */
@RestController
public class FlowLimitController {

    /**
     * 测试
     *
     * @return
     */
    @GetMapping("/testA")
    public String testA() {
        return "testA";
    }

    @GetMapping("/testB")
    public String testB() {
        return "testB";
    }
}

配置关联规则

在这里插入图片描述

预期效果

由于对/testB的限流控制采用QPS 关联 ,所以直接访问不会被限流, 会发现一直刷新 /testB 不会出现限流,如图:

在这里插入图片描述

但是频繁访问/testA到达限流阈值时,此时访问/testB会出现限流提示:

在这里插入图片描述

9 流控模式之链路模式

在这里插入图片描述

链路流控模式指的是,当从某个接口过来的资源达到限流条件时, 开启限流。它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。

配置示例:

例如有两条请求链路:

  • /test1 --> /common
  • /test2 --> /common

如果只希望统计从/test2进入到/common的请求,则可以这样配置:

在这里插入图片描述

实战案例

需求:有查询订单和创建订单业务,两者都需要查询商品。针对从查询订单进入到查询商品的请求统计,并设置限流。

在这里插入图片描述

实现

添加查询商品方法

在支付服务中(cloud-sentinel-payment8001),添加PaymentService类并添加一个queryGoods方法:

@Service
public class PaymentService {

    /**
     * 查询商品
     * 默认情况下,Service中的方法是不被Sentinel监控的,
     * 需要我们自己通过注解来标记要监控的方法。
     *
     * @return
     */
    @SentinelResource("goods")
    public String queryGoods() {
        return "查询商品";
    }


}

添加控制层PaymentController

/**
 * 支付控制层 主要练习链路
 */
@RestController
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    /**
     * 添加订单
     *
     * @return
     */
    @GetMapping("/save")
    public String saveOrder() {
        //添加订单时,查询商品
        paymentService.queryGoods();
        return "新增订单成功";
    }

    /**
     * 查询订单
     *
     * @return
     */
    @GetMapping("/query")
    public String queryOrder() {
        //查询订单时,查询商品
        paymentService.queryGoods();
        return "查询订单成功";
    }


}

给查询商品添加资源标记

默认情况下,PaymentService中的方法是不被Sentinel监控的,需要 我们自己通过注解来标记要监控的方法。 给PaymentService的queryGoods方法添加@SentinelResource注解:

	@SentinelResource("goods")
    public String queryGoods() {
        return "查询商品";
    }

簇点链路

链路模式中,是对不同来源的两个链路做监控。但是sentinel默认会给进入SpringMVC的所有请求设置同一个root资源,会导致链路模式失效。我们需要关闭这种对SpringMVC的资源聚合,修改 application.yml文件。

server:
  port: 8001
spring:
  application:
    name: payment-provider-sentinel
  cloud:
    nacos:
      discovery:
        # Nacos服务地址
        server-addr: 114.117.183.67:8848
    sentinel:
      transport:
        # Sentinel控制台地址
        dashboard: localhost:8878
        # Sentinel api端口号,默认8719,
        # 假如被占用了会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
      # 关闭context整合
      web-context-unify: false

重启服务,访问query和save,可以查看到sentinel的簇点链路规则中,出现了新的资源。

在这里插入图片描述

添加流控规则

点击goods资源后面的流控按钮,在弹出的表单中填写下面信息

在这里插入图片描述

10 流控效果之冷启动

在这里插入图片描述

warm up也叫预热模式,是应对服务冷启动的一种方案。

案例实战

需求:给/payment/warmup这个资源设置限流,最大QPS为10,利用 warm up效果,预热时长为5秒。

@RestController
@RequestMapping("/payment")
public class PaymentController {

    /**
     * 测试冷启动
     *
     * @return
     */
    @GetMapping("/warmup")
    public String warmup() {
        return "warmup";
    }
}

在这里插入图片描述

Jmeter测试

在这里插入图片描述

查看结果树

在这里插入图片描述

注意:

QPS为10.刚刚启动时,大部分请求失败,成功的只有3个,说 明QPS被限定在3,随着时间推移,成功比例越来越高。

11 流控效果之排队等待

在这里插入图片描述

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

工作原理

例如:QPS阈值为5,意味着每200ms处理一个队列中的请求,超时时间2s,现在有100个请求过来,服务器最多处理 5 个,其他慢慢排队,timeout = 2S,意味着预期等待时长超过2S的请求会被拒绝并抛出异常。

在这里插入图片描述

假如不使用排队等待模式,现在有11个请求过来,第1秒同时接收 到10个请求,但第2秒只有1个请求,此时QPS的曲线这样的。

在这里插入图片描述

如果使用队列模式做流控,所有进入的请求都要排队,以固定的200ms的间隔执行,QPS会变的很平滑。

在这里插入图片描述

注意:

平滑的QPS曲线,对于服务器来说是更友好的。

应用场景

在这里插入图片描述

注意:

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

案例

需求:给/payment/queue这个资源设置限流,最大QPS为10,利用排队的流控效果,超时时长设置为5s。

@RestController
@RequestMapping("/payment")
public class PaymentController {

    /**
     * 测试排队等待
     *
     * @return
     */
    @GetMapping("/queue")
    public String queue() {
        return "queue";
    }
}

在这里插入图片描述

Jmeter测试

在这里插入图片描述

QPS为15,已经超过了我们设定的10。如果是之前的快速失败、 warmup模式,超出的请求应该会直接报错。但是我们看看队列模式的运行结果:

在这里插入图片描述

12 热点参数限流

在这里插入图片描述

之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过 QPS阈值。

全局参数限流

例如,一个根据id查询商品的接口:

在这里插入图片描述

访问/goods/{id}的请求中,id参数值会有变化,热点参数限流会根据参数值分别统计QPS,统计结果:

在这里插入图片描述

当id=1的请求触发阈值被限流时,id值不为1的请求不受影响。

配置示例:

在这里插入图片描述

注意

对hot这个资源的0号参数(第一个参数)做统计,每1秒相同参数值的请求数不能超过5

热点参数限流

刚才的配置中,对查询商品这个接口的所有商品一视同仁,QPS都 限定为1。而在实际开发中,可能部分商品是热点商品,例如秒杀商品,我们希望这部分商品的QPS限制与其它商品不一样,高一些。 那就需要配置热点参数限流的高级选项了:

在这里插入图片描述

注意

结合上一个配置,这里的含义是对0号的long类型参数限流,每 1秒相同参数的QPS不能超过5,有两个例外: 如果参数值是100,则每1秒允许的QPS为10 如果参数值是101,则每1秒允许的QPS为15

案例需求

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

  • 默认的热点参数规则是每1秒请求量不超过2
  • 给102这个参数设置例外:每1秒请求量不超过4
  • 给103这个参数设置例外:每1秒请求量不超过10

注意事项:

热点参数限流对默认的SpringMVC资源无效,需要利用 @SentinelResource注解标记资源

@RestController
@RequestMapping("/goods")
public class GoodsController {

    @SentinelResource("getGoods")
    @GetMapping("/{id}")
    public String getGoods(@PathVariable String id) {
        return id + "号商品";
    }
}

热点参数限流规则

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Jmeter测试

13 线程隔离

在这里插入图片描述

线程隔离(舱壁模式)

  • 线程池隔离
  • 信号量隔离(Sentinel默认采用)

在这里插入图片描述

注意:

  • 线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
  • 信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。

sentinel的线程隔离

在添加限流规则时,可以选择两种阈值类型:

在这里插入图片描述

注意:

  • QPS:每秒的请求数
  • 线程数:该资源能使用用的Tomcat线程数的最大值。也就是通过限制线程数量,实现线程隔离(舱壁模式)。

流控测试

新建线程组

我们新建一个线程组,在启动的时候同时发送10个请求,如下所示:

在这里插入图片描述

新建HTTP请求

在这里插入图片描述

创建查看结果树

在这里插入图片描述

14 熔断降级

在这里插入图片描述

熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即 拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问 该服务的请求。

断路器控制熔断和放行是通过状态机来完成的:

在这里插入图片描述

状态机包括三个状态:

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
  • open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。 请求成功:则切换到closed状态请求失败:则切换到open状态

熔断降级策略

  • 慢调用

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

  • 异常比例、异常数

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

15 熔断降级之慢调用

在这里插入图片描述

平均响应时间当1s内持续进入5个请求,对应时刻的平均响应时间 (秒级)均超过阈值,那么在接下的时间窗口之内,对这个方法的调用都会自动地熔断(抛出DegradeException )。

新增接口

    @GetMapping("/testC")
    public String testC(Integer id) {
        if (id == 1) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return "------------testC";
    }

新增RT配置

在这里插入图片描述

参数:

超过50ms的请求都会被认为是慢请求,当异常比例达到百分之 40,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了。过5秒钟由断路器又打开状态变为半开状态放一部分请求进来。

Jemeter压力测试

创建线程组

在这里插入图片描述

设置HTTP请求路径

在这里插入图片描述

测试

未使用Jemeter情况下测试/testC接口

在这里插入图片描述

使用Jemeter情况下测试/testC接口(id==1,达到慢调用阈值触发熔断)。

在这里插入图片描述

结果访问失败

在这里插入图片描述

注意:

后续我停止Jmeter,没有这么大的访问量了,断路器关闭(保险 丝恢复),微服务恢复OK。

16 熔断降级之异常比例

在这里插入图片描述

概述

当资源每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态。异常比率的阈值范围是 [0.0,1.0]。

新增接口

 /**
     * 测试异常比例
     * RT 平均响应时间
     *
     * @return
     */
    @GetMapping("testD")
    public String testD(Integer id) {
        if (id == 1) {
            throw new RuntimeException("故意抛出异常,触发异常比例熔断。");
        }
        return "testD";
    }

设置熔断规则

在这里插入图片描述

注意:

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

Jemeter压力测试

创建线程组

在这里插入图片描述

配置HTTP请求(id=1,抛出异常,触发异常比例阈值,开启熔断)

在这里插入图片描述

测试接口

发送请求localhost:8001/testD?id=2

在这里插入图片描述

17 熔断降级之异常数

在这里插入图片描述

概念

异常数:当资源近1分钟的异常数目超过阈值之后会进行熔断。

新增接口

    /*
     * 测试异常数
     */
    @GetMapping("/testF")
    public String testF() {
        int age = 10 / 0;
        return "------testF 测试异常数";
    }

配置异常数规则

在这里插入图片描述

注意: 设置异常数5。

测试

请求http://localhost:8001/testF,第一次访问绝对报错,因为除数不能为零, 我们看到error窗口

在这里插入图片描述

但是达到5次报错后,进入熔断后降级。

在这里插入图片描述

18 授权规则

在这里插入图片描述

授权规则

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

  • 白名单:来源(origin)在白名单内的调用者允许访问
  • 黑名单:来源(origin)在黑名单内的调用者不允许访问

点击左侧菜单的授权,可以看到授权规则:

在这里插入图片描述

  • 资源名:就是受保护的资源,例如/payment/{query}
  • 流控应用:是来源者的名单,
    • 如果是勾选白名单,则名单中的来源被许可访问。
    • 如果是勾选黑名单,则名单中的来源被禁止访问。

在这里插入图片描述

注意:

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

案列实现

创建网关工程cloud-gateway9090

在这里插入图片描述

POM文件引入依赖

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

编写主启动类

@EnableDiscoveryClient
@SpringBootApplication
@Slf4j
public class Gateway9090 {
    public static void main(String[] args) {

        SpringApplication.run(Gateway9090.class, args);
        log.info("********** Gateway9090 启动成功 *********");
    }
}

创建配置文件

server:
  port: 9090
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        # Nacos服务地址
        server-addr: 114.117.183.67:8848
    gateway:
      routes:
        - id: payment
          uri: lb://payment-provider-sentinel
          predicates:
            - Path=/payment/*

logging:
  pattern:
    #日志打印格式
    console: logging.pattern.console=%d{MM/dd HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n

测试网关服务

发送请求http://localhost:9090/payment/query

在这里插入图片描述

如何获取origin

在这里插入图片描述

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

public interface RequestOriginParser {
    /**
     * 从请求request对象中获取origin,获取方式自定义
     */
    String parseOrigin(HttpServletRequest request);
}

注意:

这个方法的作用就是从request对象中,获取请求者的origin值并返回。默认情况下,sentinel不管请求者从哪里来,返回值永远是default,也就是说一切请求的来源都被认为是一样的值 default。因此,我们需要自定义这个接口的实现,让不同的请求,返回不同的origin。

支付微服务(cloud-sentinel-payment8001)定义一个RequestOriginParser的实现类

@Component
public class HeaderOriginParser implements RequestOriginParser {
    @Override
    public String
    parseOrigin(HttpServletRequest request) {
        // 1.获取请求头
        String origin = request.getHeader("origin");
        // 2.非空判断
        if (StringUtils.isEmpty(origin)) {
            origin = "blank";
        }
        return origin;
    }
}

给网关添加请求头

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

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

注意:

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

配置授权规则

接下来,我们添加一个授权规则,放行origin值为gateway的请求。

在这里插入图片描述

配置规则

在这里插入图片描述

测试网关请求

请求localhost:9090/payment/query

在这里插入图片描述

测试非网关请求

请求localhost:8001/payment/query

在这里插入图片描述

19 系统自适应限流

在这里插入图片描述

引入系统自适应限流的主要的目的

  • 保证系统不被拖垮
  • 在系统稳定的前提下保证系统的吞吐量。目前我们接触的限流的防护思路都是设定一个指标(阈值),例如系统的负载 load 超过某个阔值后就阻止或减少流量的继续进入,当系统负载降低到某一水平后则恢复流量的进入。通常都是被动的,其实际效果取决于阈值设置是否合理,但往往设置合理不是一件容易的事情。

系统规则

Sentinel的系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

在这里插入图片描述

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量( EntryType.IN ),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

在这里插入图片描述

系统规则支持以下的模式

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5 。
  • CPU usage:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

配置系统规则

在这里插入图片描述

添加规则

在这里插入图片描述

20 SentinelResource注解配置详解之只配置fallback

在这里插入图片描述

服务降级功能,但是只是限制后,返回不可控的结果肯定是不行的,我们还要保证调用者在调用那些被限制的服务时候,不管是不是被限制,都要让他们拿到一个合理的结果,而不是扔回去一个异常就完事了。

Sentinel提供了这样的功能,让我们可以另外定义一个方法来代替被限制或异常服务返回数据,这就是fallback和blockHandler。

  • fallback:若本接口出现未知异常,则调用fallback指定的接口。
  • blockHandler:若本次访问被限流或服务降级,则调用blockHandler指定的接口。

@SentinelResource注解用于定义资源,并提供可选的 BlockException 异常处理(仅处理Sentinel控制台配置相关异常)和 fallback 配置项(运行时异常以及自定义异常)。

注解属性

  • value :资源名称,必需项(不能为空)
  • entryType :entry 类型,可选项(默认为 EntryType.OUT )
  • blockHandler / blockHandlerClass : blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public ,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException 。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback / fallbackClass :fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面 排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有fallback会生效。
  • defaultFallback (since 1.6.0):默认的 fallback 函数名称
  • exceptionsToIgnore (since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出

实例编写

在服务生产者(cloud-provider-payment8001)新增接口

@RestController
@RequestMapping("/payment")
public class PaymentController {

    @GetMapping("/findById")
    public String findById(String id) {
        return "payment findById " + id;
    }

}

把基于open-fegin的服务消费者(cloud-consumer-openfeign-order80)接入sentinel控制台

pom中加入:

        <!-- 引入降级依赖  -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

配置文件中加入:

spring:
  cloud:
    sentinel:
      transport:
        # Sentinel控制台地址
        dashboard: localhost:8878
        # Sentinel api端口号,默认8719,
        port: 8719

编写消费者远程调用service

@Service
@FeignClient(value = "provider-payment")
public interface PaymentService {
    @GetMapping("/payment/findById")
    String findById(@RequestParam("id") String id);
}

编写消费者controller,配置fallback

此回调是针对接口出现异常了,就进入此fallback方法。

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private PaymentService paymentService;

    @SentinelResource(value = "testfallback",
            fallback = "findByIdFallBalk",
            fallbackClass = PaymentServiceFallback.class)
    @GetMapping("/findById")
    public String findById(String id) {
        if (id.equals("1")) {
            throw new RuntimeException("出异常了。");
        }
        return paymentService.findById(id);
    }
}

代码看出,只要请求http://localhost:80/order/findById?id=1 ,接口就会报异常,继而会调用fallback中的方法,走到了testFallback 方法中。

外置类

fallback 函数位置是有要求的,必须和原方法在同一个类中,但在实际需求中,我们需要放在其他类中。@SentinelResource提供了通过fallbackClass指定对应的类的Class对象,添加一个static,否则无法解析。

public class PaymentServiceFallback {
    /**
     * 降级方法
     *
     * @return
     */
    public static String findByIdFallBalk(String id, Throwable e) {
        return "支付系统服务繁忙稍等一会~~~~";
    }

}

21 SentinelResource配置详解之只配置 blockHandler

超出流量限制的部分是否会进入到blockHandler的方法,要注意是超出流量限制的请求调用,会进入blockHandler方法。

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private PaymentService paymentService;

    @GetMapping("/index")
    public String index() {
        return paymentService.index();
    }

    @SentinelResource(value = "testblockHandler",
            blockHandler = "testBlockHandler",
            blockHandlerClass = PaymentServiceBlockHandler.class)
    @GetMapping("/findById")
    public String findById(String id) {
        if (id.equals("1")) {
            throw new RuntimeException("出异常了。");
        }
        return paymentService.findById(id);
    }
}

public class PaymentServiceBlockHandler {

    public static String testBlockHandler(String id, BlockException e) {
        return "限流降级处理";
    }
}

22 SentinelResource配置详解之fallback和 blockHandler都配置

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private PaymentService paymentService;

    @GetMapping("/index")
    public String index() {
        return paymentService.index();
    }

    @SentinelResource(value = "testfallback",
            fallback = "findByIdFallBalk",
            fallbackClass = PaymentServiceFallback.class,
            blockHandler = "testBlockHandler",
            blockHandlerClass = PaymentServiceBlockHandler.class)
    @GetMapping("/findById")
    public String findById(String id) {
        if (id.equals("1")) {
            throw new RuntimeException("出异常了。");
        }
        return paymentService.findById(id);
    }
}

总结

fallback是针对方法出现异常了,则会进入fallback方法。blockhandler是针对流控设置,超出规则,则会进入blockhandler方法。若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出BlockException时只会进入 blockHandler处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出。

23 实时监控数据

在这里插入图片描述

Sentinel 提供对所有资源的实时监控。如果需要实时监控,客户端需引入以下依赖(以 Maven 为例):

<!-- 在netty的基础上实现,通过http协议传输数据 -->
        <dependency>
          	<groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-netty-http</artifactId>
            <version>1.8.3</version>
        </dependency>

注意: 引入上述依赖后,客户端便会主动连接 Sentinel 控制台。通过 Sentinel 控制台即可查看客户端的实时监控。

设定启动参数

只使用基本的流控功能的话,是不用额外指定启动参数的。但如果 要接入Dashboard进行实时监控,则需要在应用启动时加入JVM参 数指定控制台地址和端口。

在这里插入图片描述

内容如下

-Dcsp.sentinel.dashboard.server=192.168.66.101:8878

其他相关参数有:

在这里插入图片描述

界面展示

应用的监控指标数据已经保存到Dashboard了,这个时候,我们在控制台界面来查看监控数据:

在这里插入图片描述

24 Sentinel为什么需要持久化

在这里插入图片描述

为什么需要持久化

Sentinel的所有规则都是内存存储,重启后所有规则都会丢失。在 生产环境下,我们必须确保这些规则的持久化,避免丢失。

项目启动成功日志打印:

在这里插入图片描述

Sentinel支持三种规则管理模式

原始模式

Sentinel的默认模式,将规则保存在内存,重启服务会丢失。

pull模式

控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。

在这里插入图片描述

push模式

控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。

在这里插入图片描述

25 Sentinel组件二次开发

在这里插入图片描述

在开始二次开发之前,我们需要将Sentinel 的代码下载到本地。你 可以从GitHub 的Releases页面中找到1.8.3版本,在该版本下的 Assets面板中下载 Source code源文件。下载地址https://github.com/alibaba/Sentinel/releases

在这里插入图片描述

改造过程三个步骤

  • 修改 Nacos 依赖项的应用范围,将其打入jar 包中;
  • 后端程序对接 Nacos,将 Sentinel 限流规则同步到 Nacos;
  • 开放单独的前端限流规则配置页面。

修改Nacos依赖项

首先,你需要打开 sentinel-dashboard 项⽬的 pom.xml 文件,找 到其中的依赖项 sentinel-datasource-nacos,它是连接 Nacos Config 所依赖的必要组件。

<dependency>        
			<groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasourcenacos</artifactId>
<!--           将scope注释掉 改为编译器打包-->
<!--           <scope>test</scope>-->
</dependency>

后端程序对接Nacos

首先,你需要打开 sentinel-dashboard 项目下的 src/test/java目 录。定位到 com.alibaba.csp.sentinel.dashboard.rule.nacos目录 下。

在这里插入图片描述

解释:

NacosConfig:初始化 Nacos Config 的连接;

NacosConfigUtil:约定了 Nacos 配置⽂件所属的 Group 和⽂件命名后缀等常量字段; FlowRuleNacosProvider:从 Nacos Config 上获取限流规则;

FlowRuleNacosPublisher:将限流规则发布到 Nacos Config。

为了让这些类在 Sentinel 运行期可以发挥作用,你需要在 src/main/java 下创建同样的包路径,然后将这四个文件从 test 路径拷贝到 main 路径下。

实现Nacos数据同步

在NacosConfig中

 @Bean
    public ConfigService nacosConfigService() throws Exception {
        Properties properties = new Properties();
        // Nacos服务地址
        properties.setProperty("serverAddr", "localhost:8848");
        // NameSpace id
        properties.setProperty("namespace", "0a726f6a-0290-48ff-9e49-334ba2e51551");
        return ConfigFactory.createConfigService(properties);
    }

FlowControllerV2接入Nacos

在这里插入图片描述

解释:

在代码中,我通过Qualifer标签将FlowRuleNacosProvider注入 到了ruleProvier变量中,又采用同样的方式将 FlowRuleNacosPublisher注入到了rulePublisher变量中。 FlowRuleNacosProvider和 FlowRuleNacosPublisher就是上一 步我们刚从 test目录 Copy到main目录下的两个类。

查看FlowRuleNacosPublisher源码

    @Override
    public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
        AssertUtil.notEmpty(app, "app name cannot be empty");
        if (rules == null) {
            return;
        }
        //将规则发布到Nacos配置中心
        configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
            NacosConfigUtil.GROUP_ID, converter.convert(rules));
    }

NacosConfigUtil.FLOW_DATA_ID_POSTFIX

public final class NacosConfigUtil {

    // 这个是Sentinel 注册的配置项所在的分组
    public static final String GROUP_ID = "SENTINEL_GROUP";
    // 流量整形规则的后缀
    public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules";

查看FlowRuleNacosProvider源码

   @Override
    public List<FlowRuleEntity> getRules(String appName) throws Exception {
        // 从配置中心获取规则
        // app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX   +NacosConfigUtil.GROUP_ID
        String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
                NacosConfigUtil.GROUP_ID, 3000);
        if (StringUtil.isEmpty(rules)) {
            return new ArrayList<>();
        }
        return converter.convert(rules);
    }

解析:

我们就完成了对后端程序的改造,将 Sentinel 限流规则同步到 了 Nacos。接下来我们需要对前端页面稍加修改,开放⼀个独立的页面,用来维护那些被同步到 Nacos 上的限流规则。

前端页面改造

首先,我们打开 sentinel-dashboard 模块下的 webapp 目录,该目录存放了 Sentinel 控制台的前端页面资源。我们需要改造的文件是 sidebar.html,这个 html文件定义了控制台的左侧导航栏。

在这里插入图片描述

新增内容

<li ui-sref-active="active">
            <a ui-sref="dashboard.flow({app:entry.app})">
            <i class="glyphicon glyphiconfilter"></i>&nbsp;&nbsp;流控规则lxx</a>
</li>

微服务改造

微服务端的改造非常常简单,我们不需要对代码做任何改动,只需 要添加⼀个新的依赖项,并在配置文件中添加 sentinel datasource 连接信息就可以了。

        <!--   引入nacos数据源     -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

我们在 application.yml 配置文件中找到 spring.cloud.sentinel 节点,在这个节点下添加⼀段Nacos 数据源的配置。

spring:
  cloud:
    sentinel:
      datasource:
        # 数据源key 可以自由命名
        geekbang-flow:
          # 指定当前数据源是Nacos
          nacos:
            # 设置Nacos的连接地址,命名空间和GroupID
            server-addr: localhost:8848
            # Nacos 命名空间
            namespace: 0a726f6a-0290-48ff-9e49-334ba2e51551
            group-id: SENTINEL_GROUP
            # 设置Nacos中配置⽂件的命名规则
            data-id: ${spring.application.name}-flow-rules
            # 必填的重要字段,指定当前规则类型是"限流"
            rule-type: flow

完整:

server:
  port: 8001
spring:
  application:
    name: payment-provider-sentinel
  cloud:
    nacos:
      discovery:
        # Nacos服务地址
        server-addr: localhost:8848
    sentinel:
      transport:
        # Sentinel控制台地址
        dashboard: localhost:8080
        # Sentinel api端口号,默认8719,
        # 假如被占用了会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
      # 关闭context整合
      web-context-unify: false
      datasource:
        # 数据源key 可以自由命名
        geekbang-flow:
          # 指定当前数据源是Nacos
          nacos:
            # 设置Nacos的连接地址,命名空间和GroupID
            server-addr: localhost:8848
            # Nacos 命名空间
            namespace: 0a726f6a-0290-48ff-9e49-334ba2e51551
            group-id: SENTINEL_GROUP
            # 设置Nacos中配置⽂件的命名规则
            data-id: ${spring.application.name}-flow-rules
            # 必填的重要字段,指定当前规则类型是"限流"
            rule-type: flow



logging:
  pattern:
    #日志打印格式
    console: logging.pattern.console=%d{MM/dd HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n

重点强调

  • 我们在微服务端的 sentinal 数据源中配置的 namespace 和 groupID,⼀定要和 Sentinal Dashoboard 二次改造中的中的配置相同,否则将无法正常同步限流规则。
  • dataId 的文件命名规则,需要和 Sentinel 二次改造中的FlowRuleNacosPublisher 类保持⼀致。

验证限流规则同步效果

启动Sentinel-dashboard子项目,找主启动类启动Sentinel控制台。

在这里插入图片描述

请求http://localhost:8080,你可以登录 Sentinel Dashboard 服 务。这时你会看到左侧的导航栏多了⼀个“流控规则lxx”的选项。 你可以点击这个选项,并手动在当前页面右上方点击“新增流控规则”,为 requestCoupon 添加⼀条“QPS=1 快速失败”的流控规则。

在这里插入图片描述

最后,打开 Nacos Config 的配置列表页,你就可以看到⼀个 cloud-sentinel-payment- flow-rules 的配置文件被创建了出来。

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值