java系列 SpringCloudAlibaba组件 -Sentinel

前言

本文主要介绍如何使用alibaba的开源框架 - Sentinel去实现流量控制,熔断降级,系统自适应过载保护,热点流量防护等,从多维度来帮组我们保障微服务的稳定性,接下来我们会从安装开始,一步一步的讲解如何去使用Sentinel怎么去定义资源和对资源去进行流控和降级!

Sentinel同Nacos一样也是分为客户端和服务端,客户端就是我们的微服务,服务端就是一个图形化的后台管理平台,主要负责管理推送规则、监控、管理机器信息等!

Sentinel服务端安装

环境要求 :

jdk1.8 +

服务端jar包下载地址 : https://github.com/alibaba/Sentinel/releases

image

我们通过xftp工具把jar包上传到我们的服务器中,我这里上传到了/usr/sentinel目录下

image

然后我们使用java命令启动jar包

java -Dserver.port=12000 -Dcsp.sentinel.dashboard.server=localhost:12000 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar

其中 -Dserver.port=12000 用于指定 Sentinel 控制台端口为 12000,然后放开12000端口号后我们就可以使用服务器ip地址加端口号请求sentinel控制台了
ip:12000 , 默认账号 : sentinel 密码 : sentinel

image

服务端就搭建完成了

Sentinel客户端配置

sentinel客户端也就是我们的微服务,首先我们需要导入Sentinel依赖以及连接控制台的依赖


        <!--sentinel-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-core</artifactId>
            <version>1.8.0</version>
        </dependency>

        <!--spring cloud集成sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2021.1</version>
        </dependency>

然后配置连接控制台的参数以及Sentinel的一些参数

image

spring:
  cloud:
    sentinel:
      transport:
        port: 8719 #这个端口是用来本机与控制台进行交互的,如果被占用会向上兼容也就是8719被占用就会用8720
        dashboard: 120.27.239.99:12000 #控制台的ip端口
      eager: true #启动是否自动注册,如果为false,只有当该微服务有请求时才会注册到sentinel

我们打开控制台就可以看见我们注册的服务了

image

这里需要注意,当我们的服务注册到sentinel的时候里面是没有管控资源(接口)的,sentinel是一种懒加载机制,只有当接口被请求的时候才会被sentinel发现并管控!

如何使用Sentnael

Sentinel资源可以理解为我们需要管控的代码块,在Sentinel中叫资源!

名词注释 :

  • 流控规则 - 就是控制台配置的流控限流规则
  • 降级规则 - 就是控制台配置的降级限流规则 , 分为慢调用,异常比例,异常数
  • 降级 - 就是当方法出现异常或者慢调用的时候,我们为了保证系统后续的正常运行就会终止该方法并调用他的降级方法,去完成接口调用
  • 降级方法 - 通俗的说就是当接口发生异常或者被流控了导致接口运行失败,系统就会调用你配置的一个兜底方法

流控/降级

我们可以通过在不同的方法或者服务实现类上增加注解来标识该方法或类被sentinel管控,这里我们推荐在service层的方法上进行增加注解,WEB层我们可以通过埋点的方式对其进行自动注册并管控

image

我们需要请求一下该接口才会在sentinel中显示该接口

image

流控规则配置

然后我们就可以对其设置流控,降级规则了,同时@SentinelResource注解还提供了额外属性,如blockHandler,blockHandlerClass,fallback用来实现当接口被限流或者降级后的操作,如果不配置这些属性的话,那当该接口被限流或者降级时会抛出UndeclaredThrowableException异常,如我们把该接口调整为1秒只能请求一次

image

image

在项目日志里面可以看见

image

流控/降级后的降级方案

那流控后抛出错误肯定不是我们想要的结果,所以我们可以设置注解的blockHandler属性来配置当该接口被流控之后执行的降级方法,如下

image

需要注意,降级方法必须和原方法在同一个类中,而且相同入参的前提下新增一个BlockException参数,同时需要相同类型的返回值

然后我们再来请求一下该接口

image

可以发现,接口被流控后并不会抛出异常了,而是会直接执行我们给他配置的降级方法!不过大家也会发现一个问题,就是我们定义的降级方法必须要和原方法在同一个类中,这未免有些太局限了,那我们如果想使用其他类中的方法来作为降级方法的话,我们就可以使用blockHandlerClass属性来定义使用那个类下的方法来作为降级方法,具体如下

我们先在其他类中定义一个降级方法

image

然后我们在原方法上增加以下属性

image

然后我们再去请求接口

image

发现使用外部类的降级方法也是可以实现的

出现异常进行降级

上述我们讲到了当接口触发我们的制定的流控规则的时候才会触发降级处理,那在生产环境中我们项目也可能会因为很多情况出现接口报错抛出异常等问题,那我们肯定不可以将异常抛出给用户看到或者接口因为异常原因去中断接口,那或多或少可能会出现数据丢失等情况,那我们就可以使用SentinelResource注解中fallback属性来捕获异常并执行对应方法,具体操作如下

image

同流控降级一样,也是如果要调用其他类中的降级方法的话需要配置fallbackClass属性,用来指向降级方法所存在的类即可,我们就不在演示了!

同时我们也可能出现一些情况就是我们在捕获异常的时候,可能会有一些异常我们希望他抛出而不要进行降级,这种情况的话sentinel提供了exceptionsToIgnore属性用来标记那些异常不需要降级,具体操作如下

image

那我们再去请求该接口,看看他捕获了除0异常以后还会进行降级么

image

发现他不会对除0异常进行捕获并进行降级处理了.

总结

首先我们需要明白他的运行机制,就是当我们接口被流控了以后同时我们注释中并没有配置blockHandler属性和fallback属性,那我们的接口就会抛出一个FlowException异常,如果是降级就会抛出一个DegradeException异常!

在项目中我们的流控和降级后并不会走同一条降级服务路线,比如我们流控以后可能就只会写个日志而已,然后返回一句话请求繁忙等,因为到达流控的瓶颈时那就是服务器的瓶颈,但是降级不一样,因为降级他一般都是因为我们的方法报出了异常等一些我们代码或者其他的一些问题,这些问题我们需要进行研究以及处理,所以当降级触发以后我们不但要记录日志,还要记录数据库弄一个定时再去重试请求,所以当我们项目触发流控的时候就可以新增blockHandler属性去处理流控的降级处理,而当我们项目发生降级以后我们就可以新增fallback属性,然后再降级方法中if判断错误是否为DegradeException异常,如果是的话就需要进行持久化到数据库的操作了,具体操作如下

image

热点规则

热点规则不同上面的流控和降级,主要机制如下

image

就是如果你sentnael中添加了一个热点规则为

image

那他的意思就是,当你请求findGoodsService这个资源的时候他会看你第一个参数(索引0)是否为0000,如果为0000的话他就会进行限流,通俗意思也就是参数为0000的每秒不能通过5个请求!

系统规则

系统规则也称为是系统自适应保护规则,是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 和线程数四个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN

  • Load(负载,仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps(每秒最大请求数) * minRt(最小请求时间) 计算得出。设定参考值一般是 CPU cores(cpu核数) * 2.5
    top命令第一行,就是系统负载信息,对应这1分钟,5分钟,15分钟 内平均负载
    负载的意思就是假设你cpu是一个单核的然后每分钟能处理100个请求

    • 负载值为 0 = 没有进程需要cpu
    • 负载值为0.5 = 处理了50个请求
    • 负载值为1 = 处理了100和请求
    • 负债值为1.5 = 处理了100个请求,有50个请求在排队,这就应该是系统超负荷运行了

为了系统流畅运行,load值最好不好超过1.0,这样子就没有线程等待了,每个线程都可以第一时间被处理,load的最大负载和核心数有关,单核cpu最大就是1.0,双核cpu那就是2.0了以此类推

image

  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
  • RT:当单台机器上所有入口流量的平均 RT(响应时间) 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

集群流控

对于集群流控,官方文档就介绍的比较清晰,具体如下

介绍

为什么要使用集群流控呢?假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很多(比如有 100 台)。这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判断是否可以调用。这就是最基础的集群流控的方式。

另外集群流控还可以解决流量不均匀导致总体限流效果不佳的问题。假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。

集群流控中共有两种身份:

  • Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流。
  • Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过)。

image

通过以上官方文档我们大致了解了集群流控的作用,那他如何配置,如下,我们启动了两台goods实例,

  1. 我们需要先在集群流控中进行服务端与客户端绑定

image

然后我们再去对某个资源进行单机配置

image

image

集群阈值模式分为单机均摊和总体阈值,具体意思就是 , 如果是单机均摊,那服务端会统计每个客户端处理该资源的请求数,当一个客户端超过均摊值的时候就返回限流命令,如果是总体阈值的话就等同于整个集群的总阈值,他不会再去计算每一个客户端收到的该资源请求数而是直接统计整体的,当超过总值,那所以有的客户端来询问是否限流都会返回"是"!

授权规则

授权规则也叫来源访问控制,控制台可以进行白名单和黑名单两种模式进行管控,如果你选择了白名单管控,那只有请求来源的值(Origin)在白名单中存在才可以通过,其他的都会拒绝,如果使用的黑名单,那只会拒绝请求来源的值(Origin)存在于黑名单中的.具体如下

image

在控制台配置好以后,我们需要再项目中增加一个配置类,因为sentinel他默认每个请求返回的来源都为Default,这会使我们控制台配置的授权规则失效,所以我们要重写一下他的parseOrigin方法,在方法中我们获取到请求头中的来源值并返回,具体代码如下

image

然后我们模拟一个请求来测试一下

image

image

image

发现是请求失败的,无权限访问,那我们把来源配置为appa呢

image

然后请求一下

image

发现请求成功了,同理我们也可以使用SentinelResource注解中的fallback属性去配置捕获AuthorityException异常然后去做一些被拒绝访问后的一些降级操作比如跳转到一个401页面之类的;

使用总结

以上就是各种流控规则的使用方式了,我们可以根据自己业务场景去选择合适的流控规则,然后进行降级操作!

开源框架适配

RestTemplate

sentinel支持对RestTemplate的服务调用使用sentinel进行保护,使用方法如下,

  1. 我们只需要在构造RestTemplate的bean的时候加上SentinelRestTemplate注解即可

image

@SentinelRestTemplate和@SentinelResource注解中的属性值一致,blockHandler和blockHandlerClass用来指定如果发生限流后的降级方法,fallback和fallbackClass用来指定如果调用发生异常后进行的降级方法,这两个属性在流控和降级中我们有着重讲解,这里就不在叙述了

  1. 我们来构建一个restTemplate的降级方法

image

public class RestTemplateBlockHandler {

    public static ClientHttpResponse restTemplateBlockHandler(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException blockException) throws IOException {
        //sentinel提供的
        SentinelClientHttpResponse sentinelClientHttpResponse = new SentinelClientHttpResponse();
        //自定义的
        DiyClientHttpResponse diyClientHttpResponse = new DiyClientHttpResponse();
        return diyClientHttpResponse;
    }
    /**
     * 自定义的响应类
     */
    static class DiyClientHttpResponse extends AbstractClientHttpResponse {
        private String blockResponse = "被降级了";

        //返回请求状态码如 401 200,我这里返回10001,然后可以方便我们调用方进行判断
        @Override
        public int getRawStatusCode() throws IOException {
            //return HttpStatus.OK.value();
            return 10001;
        }

        @Override
        public String getStatusText() throws IOException {
            return this.blockResponse;
        }
        @Override
        public void close() {

        }
        //返回body
        @Override
        public InputStream getBody() throws IOException {
            return new ByteArrayInputStream(this.blockResponse.getBytes());
        }
        //返回响应头
        @Override
        public HttpHeaders getHeaders() {
            Map<String, List<String>> headers = new HashMap();
            headers.put("Content-Type", Arrays.asList("application/json;charset=UTF-8"));
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.putAll(headers);
            return httpHeaders;
        }
    }
}
  1. 然后我们搞一个测试方法区测试一下是否可以被限流

image

    @Override
    @SentinelResource(value = "findGoodsService",
            blockHandlerClass = GoodsServiceBlockHandler.class, blockHandler = "findGoodsBlockHandlerExt",
            fallback = "findGoodsFallback")
    public String findGoods(String param,String param1) {

        //String forObject = template.getForObject("http://120.27.239.99:10001/goods/testA", String.class); //这个api直接返回请求的body了,无法判断该次请求是否被流控了!
        ResponseEntity<String> forEntity = template.getForEntity("http://120.27.239.99:10001/goods/testA", String.class);//我们获取到响应头中的状态码,然后判断是否是被限流回来的

        //我们在降级方法中设置了如果被限流了就会在响应头中返回10001状态码
        int statusCodeValue = forEntity.getStatusCodeValue();
        if (statusCodeValue == 10001){
            return "restTemplate请求被限流了!";
        }

        return forEntity.getBody();

    }

然后我们设置restTemplate请求的/goods/testA接口的的流控为一秒只能通过一个请求

image

然后我们去请求一下findGoods接口

image

这样子我们就完成了对restTemplate的保护并实行了降级操作,来保证我们的请求高可用性以及安全性!

feign

Sentinel 也支持feign组件,可以对feign请求进行流控或者降级,具体操作如下,需要引入以下依赖

        <!--openFeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

并配置sentinel对feign的支持

image

feign:
  sentinel:
    enabled: true

启动类开启feign服务

image

我们配置一个orderFeign客户端

image

然后配置他的降级类

image

然后我们在接口中通过feign请求order服务的findOrder接口

image

然后请求一次以后,我们在sentinel看见该feign请求然后就可以对其进行流控或者降级操作了

image

集成feign还是很简单,只需要在配置中开启sentinel对feign的支持就可以了,其他步骤其实都是部署feign环境,我们学习过feign的话都熟悉!

Gateway

限流控制配置

上述我们讲解的都是按接口或者请求的粒度进行的限流和降级,同时我们也可以将sentinel与gateway结合对我们的单独的整个微服务进行一个流控以及降级,具体操作如下!

具体网关搭建就不在这里详细介绍了,后续我们会专门出一期gateway文章,我们只需要导入一下pom依赖地址

        <!--gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!--nacos 如果eureka就填eureka依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2021.1</version>
        </dependency>

        <!--ribbon 的负载均衡依赖 如果cloud版本是低于2021版本,不需要填,因为2021以上版本的cloud移除了ribbon组件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <!--sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2021.1</version>
        </dependency>

        <!--sentinel 适配 gateway -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
            <version>2021.1</version>
        </dependency>

然后对gateway进行配置,配置如下

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: ***.***.***.***:10010 # nacos ip地址:端口号
        username: nacos #nacos的账号
        password: nacos #nacos的密码
      #        namespace: 6d950d5e-e66f-4594-8917-179408dbeb3b
    sentinel:
      transport:
        port: 8719 #sentinel与控制台的通讯端口
        dashboard: **.**.**.**:12000 #sentinel服务端的ip端口号
      eager: true #启动是否自动注册,如果为false,只有当该微服务有请求时才会注册到sentinel
      filter: #排除了接口的粒度,只显示以下routes的id,因为也没可能在网关里面写接口!
        enabled: false
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
      routes:
        - id: goods
          uri: lb://goods
          predicates:
            - Path=/sem_goods/**
          filters:
            - StripPrefix= 1
        - id: order
          uri: lb://order
          predicates:
            - Path=/sem_order/**
          filters:
            - StripPrefix= 1
server:
  port: 8080

然后我们通过网关进行对goods的请求

image

然后我们再去sentinel控制台就可以看见goods微服务了,然后就可以对其进行限流以及降级了

image

自定义降级规则

当我们对其单个routesID进行限流后,并触发以后会返回"Blocked by Sentinel",这肯定不是我们想要的结果,所以就需要我们进行自定义一个降级规则,当触发流控或者降级以后执行我们自己的代码,并返回我们自己想要的响应体,具体实现方法有两种

第一种 : application中配置

在application中配置分两种返回数据,一种是redirect(返回重定向url)一种是response(直接返回json字符串)

  • 第一种response - 返回json字符串,如下配置
spring:
  cloud:
    sentinel:
      scg:
        fallback:
          mode: response #返回数据类型 , 分为response(返回json数据)和redirect(返回重定向url)
          response-status: 200
          response: '{"code": 200,"message": "服务被降级处理了!!!"}'
  • 第二种redirect - 重定向到自定义的url,配置如下
spring:
  cloud:
    sentinel:
      scg:
        fallback:
          mode: redirect #返回数据类型 , 分为response(返回json数据)和redirect(返回重定向url)
          redirect: http://www.baidu.com
第二种 : 自编码方式

上述application配置方式只能决定返回的数据格式,往往我们在返回自定义数据的时候还会有一些其他的降级操作比如我们需要记录一下日志或者将该次请求放入队列MQ中,去进行定时任务去重试请求等操作,那就需要我们自己去写代码实现了,具体方法如下

  1. 我们先创建一个自定义的降级规则,也简单就是实现一个sentinel提供的BlockRequestHandler的接口然后根据他的规则进行数据返回即可,代码如下

image

public class GatewayBlockRequestHandler implements BlockRequestHandler {

    @Override
    public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {

        //自定义逻辑代码区
        System.out.println("写入日志");
        System.out.println("写入队列");
        System.out.println("......");

        //自定义返回值
        ErrorResult errorResult = new GatewayBlockRequestHandler.ErrorResult(
                HttpStatus.TOO_MANY_REQUESTS.value(),
                "该次请求被降级了!!!");
        // JSON result by default.
        return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(BodyInserters.fromObject(errorResult));
    }

    private static class ErrorResult {
        private final int code;
        private final String message;

        ErrorResult(int code, String message) {
            this.code = code;
            this.message = message;
        }

        public int getCode() {
            return code;
        }

        public String getMessage() {
            return message;
        }
    }
}
  1. 然后我们在启动类中将我们配置的降级方法设置到sentinel框架中,sentinel提供有一个函数setBlockHandler可以将他默认的降级规则替换成我们自己的,如下

image

然后我们就实现了自定义降级规则!

总结

除了这三个我们经常使用的开源框架之外,sentinel还适配了很多其他开源框架

image

不过官方文档就是写的含糊不清所以我挑了三个经常使用的帮住大家更好的理解并使用,其他的适配我就不一一介绍了,其实都是大差不差的,大家也可以去根据以上学的机制去理解一下,如果遇到问题可以随时评论区留言,官方文档地址 : https://sentinelguard.io/zh-cn/index.html

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇