SpringCloud 第八期 Sentinel 熔断限流

sentinel可以作为监控平台使用,下载jar包运行

官网说明文档,有中文 Wiki - Gitee.com

sentinel下载地址 https://github.com/alibaba/Sentinel/releases/tag/1.7.0

下载完毕 java -jar sentinel-dashboard-1.7.0.jar 运行客户端

访问http://localhost:8080/ 端口 可以看到sentinel登录界面 默认账号密码都是sentinel

配置被监控的模块

被监控的模块引入sentinel依赖

这次我在9001 模块引入了依赖

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2021</artifactId>
        <groupId>com.liuxu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-nacos-provider9001</artifactId>
    <dependencies>
    <dependency>
        <groupId>com.liuxu</groupId>
        <artifactId>cloud-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--springboot 必须有的-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
        <!--使用nacos-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <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>
    </dependencies>

</project>

yaml中添加sentinel监控相关内容

sentinel:
transport: #设置sentinel dashboard 监控平台地址
dashboard: localhost:8080
port: 8719 # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
#默认8719 端口 假如被占用则从8719+1开始依次向后扫描 直到找到未被占用的端口

server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
   nacos:
    discovery:
      server-addr: localhost:8848
   sentinel:
     transport: #设置sentinel dashboard 监控平台地址
       dashboard: localhost:8080
       port: 8719 # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
       #默认8719 端口 假如被占用则从8719+1开始依次向后扫描 直到找到未被占用的端口

management:
  endpoints:
    web:
      exposure:
        include: '*'

启动注册中心nacos,启动项目9001

登录sentinel

访问9001端口

http://localhost:9001/echo/1

刷新sentinal监控界面, 可以看到sentinal监控了9001 模块

sentinel 流控规则 Qps和线程数流控展示

首先在9001 添加SentinelController 类

package liuxu.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

/**
 * @author liuxu
 * @date 2021/11/6 17:30
 */
@RestController
@RequestMapping("/myFlow")
public class SentinelController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/testA")
    public String sentinelTestA() {
        return" hello word端口是:"+serverPort;
    }

    @RequestMapping(value = "/testB")
    public String sentinelTestB(@RequestParam("parm") String string) {
        return string+" hello word端口是:"+serverPort;
    }

}

在NacosController 添加testA testB接口

package liuxu.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.sql.Time;
import java.util.concurrent.TimeUnit;

/**
 * @author liuxu
 * @date 2020/7/13 16:51
 */
@RestController
public class NacosController
{
    @Value("${server.port}")
    private String serverPort;

        @GetMapping(value = "/echo/{String}")
        public String echo(@PathVariable ("String") String string) {
            return string+" hello word端口是:"+serverPort;
        }

    @GetMapping(value = "/testA")
    public String sentinelTestA() {
        return" hello word端口是:"+serverPort;
    }

    @RequestMapping(value = "/testB")
    public String sentinelTestB(@RequestParam("parm") String string) {
        try {
            TimeUnit.SECONDS.sleep(3L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return string+" hello word端口是:"+serverPort;
    }

}

重启9001 发现监控到的只有 http://localhost:9001/testA http://localhost:9001/testA这两个请求

http://localhost:9001/myFlow/testA http://localhost:9001/myFlow/testB 是监控不到的

原因是sentinel简单的接口可以直接以路径名作为资源名进行监控,而复杂的接口需要添加

@SentinelResource注解,之后会详细介绍

配置流控规则

testA 配置为QPS 规则 ,testB配置为线程数

点击资源后面"流控+"可以配置流控规则

访问 http://localhost:9001/testA 连续快速点击两次以上就会出现流控异常

访问 http://localhost:9001/testB 连续快速点击两次以上也会出现流控异常

用jemeter 压测可以看出两种配置方式的区别

线程组均设为单线程内的10个请求

testA请求配置

testB请求配置

发送请求后你会发现testA只有一个会成功

而testB全成功,原因是QPS 会把请求挡在服务之外,只要超过阈值就会失败

而线程数会将请求全部放到服务中,超过线程数才会失败

将请求配置为2个线程就会失败。

QBS会将请求挡在服务外,消耗小

线程数流控对于服务消耗大

流控模式有直接 关联(关联资源名,a关联b,b达到流控峰值,a被限流) 链路(关联入口资源)三种方式

只有QBS阈值类型有流控效果,

流控效果有 快速失败(默认)

Warm up(将qbs阈值初3取整进行预热 ,超预热时间才能达到qps阈值流控)

排队等待 (超过阈值不会失败,会进行排队等待)三种效果。

sentinel流控介绍完毕 可自行验证

sentinel降级规则

降级规则分 RT (超时) 异常比例 异常数

在簇点链路新增降级规则 默认1s 时间窗口期为 5,则会有如下效果

5s时间窗口期内请求达到5个(必要条件) 且有 5个请求中有响应时间超过1s的 就会触发降级规则

用jemter压测发现

10个请求中只有前5个成功,第六个开始出现熔断

同样异常比例配置 就会有如下效果 时间窗口期当请求超出5个 且异常比例超出配置异常比例就会熔断,触发降级,熔断后时间窗口期结束 就会关闭降级

异常数配置就是 是将窗口期内请求超出5个 且异常数达到设置的异常个数就会熔断,触发降级, 熔断后时间窗口期结束 就会关闭降级

下面验证

在controller中加入以下代码故意制造异常

 @RequestMapping(value = "/testC")
    public String sentinelTestB() {
        int i=10/0;
        return "testC";
    }

重启9001,并配置testC的降级规则

此刻如果每秒访问一次 http://localhost:9001/testC并不会触发降级

而是直接抛出异常

如果用jemeter压力测试,5秒发5次以上请求,就会触发降级

上面提到的降级策略中的达到5个请求这个必要条件,在sentinel官网可以看到,是一个必要的统计起点,只有在请求持续达到5个才会触发降级规则配置的策略。

以上限流调方法返回sentinel默认提示,Blocked by Sentinel (flow limiting)

用@SentinelResource可以配置自定义的热点key和回调方法,类似于@HystrixCommand

修改SentinelController类

重启9001访问 http://localhost:9001/myFlow/testB?parm=11

http://localhost:9001/myFlow/testA?p1="aa"

package liuxu.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

/**
 * @author liuxu
 * @date 2021/11/6 17:30
 */
@RestController
@RequestMapping("/myFlow")
public class SentinelController {

    @Value("${server.port}")
    private String serverPort;
    @SentinelResource(value = "testHostKey",blockHandler = "deal_testHostKey")
    @GetMapping(value = "/testA")
    @ResponseBody
    public String sentinelTestA(@RequestParam(value = "p1",required = false)String p1,
                                @RequestParam(value = "p2",required = false)String p2) {
        return "p1==>"+p1+"p2==>"+p2;
    }
    @SentinelResource(value = "testB",blockHandler = "deal_testB")
    @RequestMapping(value = "/testB")
    @ResponseBody
    public String sentinelTestB(@RequestParam("parm") String string) {
        return string+" hello word端口是:"+serverPort;
    }
public String deal_testHostKey(@RequestParam(value = "p1",required = false)String p1,
                               @RequestParam(value = "p2",required = false)String p2,BlockException e){
        System.out.println(e);
        System.out.println("降级原因是:"+e.getMessage());

        return "热点hostKey触发降级";

}
    public String deal_testB(@RequestParam("parm") String string, BlockException e){


        return "testB热点hostKey触发降级"+e;

    }
}

在sentinel客户端刷新可以看到 @SentinelResource 中value值为资源名的资源

分别给两个接口配置限流规则,和热点限流规则

此时 http://localhost:9001/myFlow/testB?parm=11被限流,超过每秒1次会返回blockHandler指定方法返回值

http://localhost:9001/myFlow/testA?p1="aa"参数0也就是第一个参数被限流,访问超过每秒1次会被限流

http://localhost:9001/myFlow/testA?p2="aa"第二个参数没有被限流

可以超过每秒1次

此时上述controller代码存在逻辑和限流处理方法混杂的现象

我们可以抽取公共限流处理类Coustomer

package liuxu.controller.handler;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author liuxu
 * @date 2021/11/7 17:49
 */
public class CoustomerBlockHandler {

    public static String deal_testHostKey(@RequestParam(value = "p1",required = false)String p1,
                                   @RequestParam(value = "p2",required = false)String p2, BlockException e){
        System.out.println(e);
        System.out.println("降级原因是:"+e.getMessage());

        return "热点hostKey触发降级";

    }
    public static String deal_testB(@RequestParam("parm") String string, BlockException e){


        return "testB热点hostKey触发降级"+e;

    }
}

controller改造

package liuxu.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import liuxu.controller.handler.CoustomerBlockHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

/**
 * @author liuxu
 * @date 2021/11/6 17:30
 */
@RestController
@RequestMapping("/myFlow")
public class SentinelController {

    @Value("${server.port}")
    private String serverPort;
    @SentinelResource(value = "testHostKey",blockHandlerClass = CoustomerBlockHandler.class,blockHandler = "deal_testHostKey")
    @GetMapping(value = "/testA")
    @ResponseBody
    public String sentinelTestA(@RequestParam(value = "p1",required = false)String p1,
                                @RequestParam(value = "p2",required = false)String p2) {
        return "p1==>"+p1+"p2==>"+p2;
    }
    @SentinelResource(value = "testB",blockHandlerClass = CoustomerBlockHandler.class,blockHandler = "deal_testB")
    @RequestMapping(value = "/testB")
    @ResponseBody
    public String sentinelTestB(@RequestParam("parm") String string) {
        return string+" hello word端口是:"+serverPort;
    }

}

此时限流规则依然生效

@SentinelResource 使用需要定下面参数

value 资源名

blockHandlerClass 限流处理的资源类

blockHandler 限流处理的方法名

限流处理的方法必须是public 方法 如果放在blockHandlerClass 里面需要为静态的

而且参数返回值必须和被限流的方法相同 ,参数列表需要被限流方法的参数列表的基础上添加

BlockException 用于捕获限流规则异常

和限流方法类似 ,我们添加一个有异常的接口,用于验证异常熔断

controller层添加testC testD接口

package liuxu.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import liuxu.controller.handler.CoustomerBlockHandler;
import liuxu.controller.handler.CoustomerFallBackHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

/**
 * @author liuxu
 * @date 2021/11/6 17:30
 */
@RestController
@RequestMapping("/myFlow")
public class SentinelController {

    @Value("${server.port}")
    private String serverPort;
    @SentinelResource(value = "testHostKey",blockHandlerClass = CoustomerBlockHandler.class,blockHandler = "deal_testHostKey")
    @GetMapping(value = "/testA")
    @ResponseBody
    public String sentinelTestA(@RequestParam(value = "p1",required = false)String p1,
                                @RequestParam(value = "p2",required = false)String p2) {
        return "p1==>"+p1+"p2==>"+p2;
    }
    @SentinelResource(value = "testB",blockHandlerClass = CoustomerBlockHandler.class,blockHandler = "deal_testB")
    @RequestMapping(value = "/testB")
    @ResponseBody
    public String sentinelTestB(@RequestParam("parm") String string) {
        return string+" hello word端口是:"+serverPort;
    }


    @SentinelResource(value = "testC",fallbackClass = CoustomerFallBackHandler.class,fallback = "testCFallBack")
    @RequestMapping(value = "/testC")
    @ResponseBody
    public String sentinelError(@RequestParam("parm") String string) {
        int i=10/0;
        return string+" hello word端口是:"+serverPort;
    }
    @SentinelResource(value = "testD",fallbackClass = CoustomerFallBackHandler.class,fallback = "testDFallBack")
    @RequestMapping(value = "/testD")
    @ResponseBody
    public String sentinelError01(@RequestParam("parm") String string) {
        int i=10/0;
        return string+" hello word端口是:"+serverPort;
    }
}

异常处理类CoustomerFallBackHandler

package liuxu.controller.handler;

import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author liuxu
 * @date 2021/11/7 18:13
 */
public class CoustomerFallBackHandler {
    public static String  testCFallBack(@RequestParam("parm") String string,Throwable throwable){
        return "testC"+"入参是"+string+"异常是"+throwable;
    }

    public static String  testDFallBack(@RequestParam("parm") String string,Throwable throwable){
        return "testD"+"入参是"+string+"异常是"+throwable;
    }
}

配置降级规则

访问http://localhost:9001/myFlow/testD?parm=11触发熔断回调

如果在@SentinelResource 添加 exceptionsToIgnore = ArithmeticException.class,

就会忽略该异常, 该异常不会触发降级。

sentinel 有关熔断降级介绍完毕

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值