Spring cloud alibaba--Sentinel服务流控降级

目录

1.服务雪崩效应

2.服务雪崩解决方案

2.1常见的容器机制

3.Sentinel:分布式系统的流量防卫兵

4.Sentinel快速入门--流控规则

 5.使用@SentinelResource改善接口中资源定义和被流控后的处理

6.Degrade熔断规则

 7.Sentinel控制台安装启动

 8.Sentinel整合控制台

 9.Spring cloud alibaba整合sentinel

 10.Sentinel控制台--簇点链路

 11.BlockException统一异常处理

 12.Sentinel流控模式--关联

 13.Sentinel流控模式--链路

 14.流控效果

15.熔断降级规则

 15.1慢调用比例

15.2异常比例

15.3异常数

16.Sentinel整合OpenFeign

 17.热点参数限流 热点识别流控

 18.系统规则

19.规则持久化

20.Sentinel实现原理


1.服务雪崩效应

因服务提供者的不可用,导致服务调用者的不可用,并将不可用逐渐放大的过程,就叫服务雪崩效应。

导致服务不可用的原因:

(1)激增流量

①激增流量导致系统CPU/Load飙高,无法正常处理请求

②激增流量打垮冷系统(数据库连接未创建,缓存未预热)

③消息投递过快,导致消息处理积压

(2)不稳定服务依赖

①慢Sql查询卡爆连接池

②第三方服务不响应,卡爆线程池

③业务调用持续出现异常,产生大量的副作用

2.服务雪崩解决方案

切入点:稳定性、恢复性

2.1常见的容器机制

(1)超时机制

在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于资源释放速度较快,一定程度上可以抑制资源耗尽的问题。

(2)服务限流

每秒请求的并发数大于服务器能提供的最大QPS,对大于的部分进行限流,返回“稍后重试”或者直拒绝处理

 (3)服务熔断

远程服务不稳定或者网络抖动时暂时关闭,就叫服务熔断。

(4)服务降级

当某个服务熔断之后,服务将不再调用,此时客户端可以自己准备一个本地的fallback(回退)回调,返回一个缺省值。虽然服务水平下降,但好歹可用,总比直接宕机要强,这也要看适合的场景。

3.Sentinel:分布式系统的流量防卫兵

什么是Sentinel

sentinel是阿里巴巴开源的,面向分布式服务架构的高可用防护组件。

Sentinel是面向分布式服务框架的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

Sentinel具有以下特征:

(1)丰富的运用场景

(2)完备的实时监控

(3)广泛的开源生态

(4)完善的SPI扩展点

4.Sentinel快速入门--流控规则

sentinel使用参考地址:如何使用 · alibaba/Sentinel Wiki · GitHub

(1)创建maven项目

(2)pom.xml中导入需要的依赖文件 

<?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>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>SentinelDemo</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
        </dependency>

        <!--如果要使用@SentinelResource-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-annotation-aspectj</artifactId>
            <version>1.8.0</version>
        </dependency>

    </dependencies>

</project>

(3)创建程序启动类,默认application.properties配置文件

(4)写一个后台controller类,使用qps限流的方式。实现的方式为给接口添加一个资源限制,资源限制的规则放在配置类方法中,对此资源进行限流方式的设置,当符合规则的时候,触发catch方法,我们可以在catch中写其他的处理操作。


@RestController
public class HelloController {

    //定义管理的资源名
    private static final String RESOURCE_NAME="hello";

    @RequestMapping("/hello")
    public String hello(){
        Entry entry = null;
        try {
             //sentinel针对资源进行限制的
             entry = SphU.entry(RESOURCE_NAME); //一般资源名与接口名保持一致
             //被保护的业务逻辑
             String str = "hello world";
             return str;
        } catch (BlockException e){
            //资源访问被阻止、限流或者降级时进入catch方法
            //进行相应的处理操作
            return "被流控了!!!";
        } catch (Exception exception){
            //若需要配置降级规则,需要通过这种方式记录业务异常
            Tracer.traceEntry(exception,entry);
        } finally {  //退出entry包含
            if (null != entry){
                entry.exit();
            }
        }
        return null;
    }

    /**
     * 定义流控规则
     * 使用 @PostConstruct修饰,程序启动时,加载到spring容器中
     */
    @PostConstruct
    public static void initFlowRules(){

        //流控规则集合,可以定义多个资源的流控
        List<FlowRule> flowRuleList = new ArrayList<FlowRule>();

        //创建流控
        FlowRule flowRule = new FlowRule();

        //设置受保护的资源
        flowRule.setResource(RESOURCE_NAME);

        //设置流控规则--这里使用qps
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);

        //设置受保护的资源阈值
        flowRule.setCount(1);

        //添加到集合中
        flowRuleList.add(flowRule);

        //加载配置好的规则集合
        FlowRuleManager.loadRules(flowRuleList);
    }
}

 流量规则控制方式可以参看官网:

(5)上面代码中是以qps为1的规则,就是1秒中这个接口只能被调用处理一次,启动程序访问接口。

 当我们刷新请求加快到一秒好几次后,被保护的实现流程没有执行,跳转到BlockException异常抓取到的方法里面。

 5.使用@SentinelResource改善接口中资源定义和被流控后的处理

使用api参考地址:https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81

(1)pom.xml中引入依赖

 <!--如果要使用@SentinelResource-->
 <dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.0</version>
 </dependency>

(2)写一个后台Controller控制类

@RestController
public class UserController {

    //定义管理的资源名
    private static final String RESOURCE_NAME_USER="user";

    /**
     * 定义流控规则
     * 使用 @PostConstruct修饰,程序启动时,加载到spring容器中
     */
    @PostConstruct
    public static void initFlowRules(){

        //流控规则集合,可以定义多个资源的流控
        List<FlowRule> flowRuleList = new ArrayList<FlowRule>();

        //创建流控
        FlowRule flowRule = new FlowRule();

        //设置受保护的资源
        flowRule.setResource(RESOURCE_NAME_USER);

        //设置流控规则--这里使用qps
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);

        //设置受保护的资源阈值
        flowRule.setCount(1);

        //添加到集合中
        flowRuleList.add(flowRule);

        //加载配置好的规则集合
        FlowRuleManager.loadRules(flowRuleList);
    }

    /**
     * 使用@SentinelResource改善接口中资源定义和被流控后的处理
     * 1.添加<artifactId>sentinel-annotation-aspectj</artifactId>
     * 2.配置Bean-SentinelResourceAspect
     *
     *  value:对应资源名
     *  blockHandler:设置流控降级后的方法(默认改方法必须写在同一个类中)
     *                如果处理方法不想在一个类中,可以配置blockHandlerClass=xxx.class,但是方法必须是static
     *  fallback:当接口出现异常,可以交个fallback指定的方法处理(默认改方法必须写在同一个类中)
     *            如果处理方法不想在同一个类中,可以配置fallbackClass=xxx.class,但是方法必须是static
     *  blockHandler和fallback同时指定了,当触发了限流和异常时,blockHandler优先级更高
     *  exceptionsToIgnore:数组的方式指定排除哪些异常不处理
     * @return
     */
    @RequestMapping("/user")
    @SentinelResource(value = RESOURCE_NAME_USER, fallback = "fallbackHandlerForGetUser", blockHandler = "blockHandlerForGetUser")
    public User getUser(String id){
        return new User("zhangsan");
    }

    /**
     * 处理blockHandler对应的方法
     * 注意:
     * 1.一定要是public
     * 2.返回值一定要和源方法保持一致,包含源方法的参数
     * 3.可以在参数最后添加BlockException,可以区分是什么规则的处理方法
     * @return
     */
    public User blockHandlerForGetUser(String id, BlockException ex){

        return new User("被流控了!!!");
    }

    /**
     * 处理fallback对应的方法
     * 注意:
     * 1.返回值类型必须与原函数返回值类型一致
     * 2.方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常
     * 3.allback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
     * @return
     */
    public User fallbackHandlerForGetUser(String id,Throwable e){
        e.printStackTrace();
        return new User("抛出异常了!!!");
    }

    /**
     * 您需要通过配置的方式将 SentinelResourceAspect 注册为一个 Spring Bean:
     * @return
     */
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}

(3)@SentinelResource配置使用介绍

①基础要求:

* 添加<artifactId>sentinel-annotation-aspectj</artifactId>
* 配置Bean-SentinelResourceAspect

②value:对应资源名

③blockHandler:设置流控降级后的方法(默认改方法必须写在同一个类中)

      如果处理方法不想在一个类中,可以配置blockHandlerClass=xxx.class,但是方法必须是static

④fallback:接口出现异常,可以交个fallback指定的方法处理(默认改方法必须写在同一个类中)

   如果处理方法不想在同一个类中,可以配置fallbackClass=xxx.class,但是方法必须是static

⑤blockHandler和fallback同时指定了,当触发了限流和异常时,blockHandler优先级更高

⑥exceptionsToIgnore:数组的方式指定排除哪些异常不处理

⑦处理blockHandler对应的方法:

* 一定要是public
* 返回值一定要和源方法保持一致,包含源方法的参数
* 可以在参数最后添加BlockException,可以区分是什么规则的处理方法

⑧处理fallback对应的方法

* 返回值类型必须与原函数返回值类型一致
* 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常
* fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析

6.Degrade熔断规则

(1)初始化定义熔断规则

 //定义熔断降级规则
    @PostConstruct
    public void initDegradeRules(){
        //存放降级规则的集合
        List<DegradeRule> list = new ArrayList<DegradeRule>();
        //创建熔断规则
        DegradeRule degradeRule = new DegradeRule();
        //设置资源名
        degradeRule.setResource(RESOURCE_NAME_DEGRADE);
        //设置熔断规则:异常数
        degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);

        //触发熔断异常数 :2
        degradeRule.setCount(2);
        //触发熔断最小请求数:2
        degradeRule.setMinRequestAmount(2);
        //统计时长:单位毫秒
        degradeRule.setStatIntervalMs(60*1000);
        //以上规则含义:一分钟内,请求两次,出现两次异常,触发熔断

        //熔断持续时长:单位秒
        //一旦触发了熔断,再次调用接口,会直接调用降级方法
        //设置的10过了后--接口半开状态:恢复接口调用请求,如果第一次请求就异常,再次熔断,不会根据之前设置的条件判定
        degradeRule.setTimeWindow(10);

        list.add(degradeRule);
        DegradeRuleManager.loadRules(list);

    }

(2)定义接口和熔断处理方法

   //对外提供的接口
    @RequestMapping("/degrade")
    @SentinelResource(value = RESOURCE_NAME_DEGRADE,
    blockHandler = "blockHandlerForDegrade")
    public User degrade(String id){
        throw new RuntimeException("异常");
    }

    //熔断降级之后对应的方法
    public User blockHandlerForDegrade(String id, BlockException e){

        return new User("熔断降级");
    }

(3)完整的controller代码

@RestController
public class DegradeController {
    //定义管理的资源名
    private static final String RESOURCE_NAME_DEGRADE="degrade";

    //定义熔断降级规则
    @PostConstruct
    public void initDegradeRules(){
        //存放降级规则的集合
        List<DegradeRule> list = new ArrayList<DegradeRule>();
        //创建熔断规则
        DegradeRule degradeRule = new DegradeRule();
        //设置资源名
        degradeRule.setResource(RESOURCE_NAME_DEGRADE);
        //设置熔断规则:异常数
        degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);

        //触发熔断异常数 :2
        degradeRule.setCount(2);
        //触发熔断最小请求数:2
        degradeRule.setMinRequestAmount(2);
        //统计时长:单位毫秒
        degradeRule.setStatIntervalMs(60*1000);
        //以上规则含义:一分钟内,请求两次,出现两次异常,触发熔断

        //熔断持续时长:单位秒
        //一旦触发了熔断,再次调用接口,会直接调用降级方法
        //设置的10过了后--接口半开状态:恢复接口调用请求,如果第一次请求就异常,再次熔断,不会根据之前设置的条件判定
        degradeRule.setTimeWindow(10);

        list.add(degradeRule);
        DegradeRuleManager.loadRules(list);

    }

    //对外提供的接口
    @RequestMapping("/degrade")
    @SentinelResource(value = RESOURCE_NAME_DEGRADE,
    blockHandler = "blockHandlerForDegrade")
    public User degrade(String id){
        throw new RuntimeException("异常");
    }

    //熔断降级之后对应的方法
    public User blockHandlerForDegrade(String id, BlockException e){

        return new User("熔断降级");
    }

}

(4)页面访问查看效果

初始访问,一份钟之内(代码配置的)前三次都是走的接口定义的方法体,第三次后已经熔断服务

 第四次访问时,走的是熔断处理的方法

 十秒(代码配置的)之内,访问还是熔断降级的方法

 十秒(代码配置的)之后,接口半开状态:恢复接口调用请求,发现异常,再次熔断服务

再次调用进入熔断方法

 7.Sentinel控制台安装启动

官网参考地址:https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0

(1)启动程序jar下载

地址:Releases · alibaba/Sentinel · GitHub

下载的版本根据整合的spring cloud alibaba来确定

 

 (2)启动控制台

cmd调出控制台,到sentinel-dashboard-1.8.1.jar目录下

 启动命令:

java -jar sentinel-dashboard-1.8.1.jar

默认监听端口8080,使用127.0.0.1:8080访问 

 默认用户名密码都是sentinel,进入管理界面 

(3)参数设置

Dsentinel.dashboard.auth.username=sentinel :用于指定控制台的登录用户名为sentinel

Dsentinel.dashboard.auth.password=123456:用于指定控制台的登陆命名为123456

Dserver.servlet.session.timeout=7200:用于指定Spring Boot服务器session的过期时间,例7200标示7200秒;60m标识60分钟,默认30分钟

Dserver.port=8090:用于指定sentinel监听端口

启动命令例:java -Dserver.port=8090 -Dsentinel.dashboard.auth.username=qingyun -Dsentinel.dashboard.auth.password=123456 -jar sentinel-dashboard-1.8.1.jar

表示服务启动监听8090端口,用户名为yingyun,密码为123456

 为了方便快捷启动,可以在桌面添加.bat文件,启动命名:

java -Dserver.port=8090 -Dsentinel.dashboard.auth.username=qingyun -Dsentinel.dashboard.auth.password=123456 -jar D:\software\sentinel\sentinel-dashboard-1.8.1.jar

 8.Sentinel整合控制台

(1)pom.xml中添加需要的依赖

<!--整合sentinel控制台-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.8.1</version>
</dependency>

(2)maven项目启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口

 (3)随便找个接口访问下,触发下后台,刷新sentinel控制台,出现以我们启动类包名命名的控制界面

 (4)访问我们的接口,可以在控制台看到一个实时监控情况

 9.Spring cloud alibaba整合sentinel

(1)创建一个maven项目,在pom.xml中添加sentinel启动器依赖

pom.xml配置:

<?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>springcloudalibaba</artifactId>
        <groupId>com.qingyun</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>OrderSentinel</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!--继承了父项目,不需要添加版本号-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--sentinel启动器-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
    </dependencies>

</project>

(2)在application.properties中配置控制台连接地址

server.port=8092

#服务名称
spring.application.name=order-sentinel
#sentinel控制台连接配置
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8090

(3)启动程序,访问下服务接口,在sentinel控制台查看信息

 10.Sentinel控制台--簇点链路

(1)流控-QPS设置:每秒请求接口的次数,代码中不用配置流控规则,直接在控制台中配置

 每秒请求大于我们设置的qps,会出现sentinel默认的流控信息

 流控的方法,我能可以使用@SentinelResource的blockHandler自己定义,规则直接在控制台配置

 每秒大于QPS设置的值后,进入流控方法

 (2)流控--线程数:一个接口只能等某个线程调用结束了才进行下一个线程调用的处理

写一个睡眠5秒的接口方法:

    @RequestMapping("/addThread")
    @SentinelResource(value="order/addThread",blockHandler = "addBlockHandler")
    public String addThread() throws InterruptedException {
        TimeUnit.SECONDS.sleep( 5);  //线程睡5秒
        return "hello sentinel";
    }

控制台设置线程数阈值:

 两个浏览器先后访问接口,第二个请求直接被限流处理

 11.BlockException统一异常处理

定义一个统一处理BlockException异常的类,接口名中不需要写@SentinelResource指定异常调用的方法,但是需要具体异常处理的方法,还是需要使用@SentinelResource指定。

(1)写一个返回的提示类Message

public class Message<T> {
    private Integer code;
    private String msg;
    private T data;

    public Message( ) {

    }

    public Message(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public Message(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static Message error(Integer code,String msg){
        return new Message(code,msg);
    }

}

 (2)异常统一处理类

/**
 * 统一异常处理类
 */
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
    Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        Message message = null;
        log.info("BlockException======="+e.getRule());
        if(e instanceof FlowException){
            message = message.error(300,"接口被限流了");
        } else if(e instanceof DegradeException){
            message = message.error(301,"接口被降级了");
        } else if(e instanceof ParamFlowException){
            message =  message.error(302,"接口热点参数限流了");
        } else if(e instanceof SystemBlockException){
            message =  message.error(303,"接口触发系统保护规则了");
        } else if(e instanceof AuthorityException){
            message =  message.error(304,"授权接口不通过");
        }
        //返回json格式
        httpServletResponse.setStatus(500);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(httpServletResponse.getWriter(),message);
    }
}

 (3)接口去掉@SentinelResource指定异常处理方法

 (4)控制台加上接口的流控规则

 (5)访问接口,触发BlockException异常

 12.Sentinel流控模式--关联

关联的资源访问达到配置的阈值,则此资源的访问会被流控,关联的资源自己不需要做配置。

(1)业务场景举例:在获取订单的接口/order/get中配置了管理资源为/order/add,并且设置了QPS为2,则当/order/add接口每秒访问大于2次,/order/get接口直接限流;此处接口资源/order/add没有做任何配置

(2)写后台接口

    @RequestMapping("/add")
   // @SentinelResource(value="order/add",blockHandler = "addBlockHandler")
    public String add(){
        return "下单成功";
    }

    @RequestMapping("/get")
    public String get(){
        return "获取订单";
    }

(3)sentinel控制台设置流控,关联资源

 (4)借助jmeter工具测试添加订单/order/add接口,是QPS大于2

下载jmeter工具,地址:Index of /dist/jmeter/binaries,直接解压,双击jmeter.bat启动

初始是英文字符,设置中文字符

 创建线程组,设置线程数/时间大于设置的qps

 创建HTTP请求

 创建观察结果数,点击绿色的RUN运行

 (5)此时访问获取订单的接口/order/get,直接被限流

 13.Sentinel流控模式--链路

 资源通过调用关系,形成调用链路,构成一棵调用树。

(1)业务场景举例:资源/order/test1和/order/test2都调用了资源getUser,Sentinel只允许根据某个入口的统计信息做限流。同时调用getUser方法,对/order/test1进行限流,/order/test2不限流。

 (2)写后台接口

新建一个service类,写被调用的方法,getUser()方法只是被调用,自己没有请求的requestmapping映射接口名,给它添加Sentinel配置


@Service
public class OrderService {

    @SentinelResource(value="getUser",blockHandler = "blockHandlerGetUser")
    public String getUser(){
        return "张三";
    }

    public String blockHandlerGetUser(BlockException e){
        return "被限流了...";
    }
}

写连个接口,调用service的方法

    //注入serive类
    @Autowired
    OrderService orderService;

    @RequestMapping(value="/test1")
    public String test1(){
        return orderService.getUser();
    }

    @RequestMapping(value="/test2")
    public String test2(){
        return orderService.getUser();
    }

(3)在application.properties中配置展开调用链路

#展开调用链路,默认不展开
spring.cloud.sentinel.web-context-unify=false

没配置展开调用链路之前:

 配置展开调用链路之后:

 (4)在sentinel控制配置调用getUser资源链路的入口资源为/order/test1

 (5)访问测试,分别快速访问/order/test1和/order/test2,test1会出现限流,test2不会出现限流

 14.流控效果

 快速失败:默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于处理能力已知的情况下,比如通过压测确定了系统的准确水位后。

Warm up(激增流量):预热/冷启动方式,当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到可能瞬间把系统拉跨。通过“冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

冷加载因子:codeFactor默认是3,即QPS从threshold/3开始,经过预热时长逐渐升至指定的QPS阈值。

排队等待(脉冲流量):严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。该方式的作用如下图所示:

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

注意:暂不支持QPS>1000的排队等待处理

15.熔断降级规则

降级规则:除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的措施之一。我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定性调用,避免局部不稳定性因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户断案(调用端)进行配置。

熔断降级规则(DegradeRule)包含下面几个重要的属性:

 15.1慢调用比例

选择以慢调用比例作为阈值,需要设置运行慢调用的RT(即最大的响应时间ms),请求的响应时间大于该值则统计为慢调用。当单位统计时长内请求数目大于设置的最小请求数,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会被熔断,经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。

例子:每次请求处理的最大时长为1秒,大于1秒则为慢调用;2秒内,请求数大于10,且有1次是慢调用,则熔断服务3秒

 (1)后台代码,处理流程睡眠2秒

    @RequestMapping(value = "flowThread")
    public String flowThread() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        return "hello flowThread";
    }

(2)使用jmeter配置请求,1秒请求大于10次 

 (3)浏览器中在jmeter请求完的3秒内,开始请求直接降级

15.2异常比例

当单位统计时长内请求数目大于设置的最小请求数,并且异常的比例大于阈值,则接下来的熔断时长内请求自动融断

15.3异常数

当单位统计时长内的异常数目超过阈值之后会自动进行熔断。

16.Sentinel整合OpenFeign

(1)服务提供端,添加接口服务,方法内抛出异常。

 (2)创建一个maven项目

(3)pom.xml中添加sentinel依赖

<?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>springcloudalibaba</artifactId>
        <groupId>com.qingyun</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>OrderOpenFeignSentinel</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!--继承了父项目,不需要添加版本号-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Nacos服务注册发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- 添加springcloud 的openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
           <!-- 排除冲突的jar包文件-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-web</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-commons</artifactId>
                </exclusion>
            </exclusions>

        </dependency>

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

    </dependencies>

</project>

(4)使用openfeign接口调用提供者服务,fallback调用异常后的处理类

@FeignClient(value = "stock-service",path ="/stock",fallback = StockOpenFeignImpl.class)
public interface StockOpenFeign {

    @RequestMapping("/reduct2")
    String reduct2();


}

(5)异常后处理类,需要实现接口

@Component
public class StockOpenFeignImpl implements StockOpenFeign{

    @Override
    public String reduct2() {
        return "服务熔断降级了!!!";
    }
}

(6)控制层调用接口方法

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

    @Autowired
    StockOpenFeign stockOpenFeign;

    @RequestMapping("/add")
    public String add(){
        System.out.println("下单成功");
        String reduct = stockOpenFeign.reduct2();
        return "add order "+reduct;
    }
}

(7)配置文件application.properties中配置openfeign整合sentinel

server.port=8084
#应用名称,nacos会将该名称当做服务名称
spring.application.name=order-service
#nacos服务连接地址
spring.cloud.nacos.server-addr=127.0.0.1:8848
#nacos discovery连接用户名
spring.cloud.nacos.discovery.username=nacos
#nacos discovery连接密码
spring.cloud.nacos.discovery.password=nacos
#nacos discovery工作空间
spring.cloud.nacos.discovery.workspace=dev

#feign整合sentinel
feign.sentinel.enabled=true

(8)页面访问接口,异常直接跳转到fallback指定的方法

 17.热点参数限流 热点识别流控

我们希望统计某个热点数据中访问频次最高的数据,并对它进行流控。

(1)写一个带参数的后台接口,必须使用@SentinelResource修饰资源

    @RequestMapping("/getById/{id}")
    @SentinelResource(value="getById",blockHandler = "hotBlockHandler")
    public String getById(@PathVariable("id") Integer id){
        return "根据id获取信息";
    }

    public String hotBlockHandler(@PathVariable("id") Integer id,BlockException e) {
        return "热点数据被流控"+id;
    }

(2)新建一个热点,参数索引:对应接口的@PathVariable修饰的参数索引,单机阈值:在统计窗口时长内的qps数量。

 (3)新建后保存,再点击编辑,添加热点参数,限流阈值。此配置为给资源getById的第1个参数添加热点,非热点数据,1秒的qps最大为10;参数为1(热点),1秒最大运行通过2次

 (4)当参数为1的时候,1秒内访问大于2次,进行限流

(5)使用jmeter访问,其它参数,qps大于10才会限流

 18.系统规则

 load自适应(仅对Linux/unix-like机器生效):系统的load1作为启发指标,进行自适应系统保护。当系统load1超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时,才会触发系统保护(BBR阶段)。

RT:当单台机器上所有入口流量的评价RT达到阈值时触发系统保护,单位是毫秒。

线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护

入口QPS:当单台机器上所有入口流量的QPS到阈值即触发系统保护

CPU使用率:当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵活。

例:当设置的cpu使用率阈值为5%,系统使用的cpu大于5%时,访问接口,直接触发系统保护

 

19.规则持久化

配置的规则保存在内存中,重启后规则丢失,需要持久化规则

基于nacos配置中心控制台实现推送

(1)pom.xml中添加依赖

        <!-- 实例化sentinel规则到nacos-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

(2)在nacos中新建配置管理,使用json格式,设置资源名、流控规则、阈值、流控效果等信息 

配置值说明:

对应sentinel控制台:

 (3)在application.properties配置文件中配置nacos的链接信息,流控方式

server.port=8092

#服务名称
spring.application.name=order-sentinel
#sentinel控制台连接配置
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8090

#展开调用链路,默认不展开
spring.cloud.sentinel.web-context-unify=false

#配置sentinel数据源,nacos服务地址
spring.cloud.sentinel.datasource.flow-rule.nacos.server-addr=192.168.31.138:8848
#连接nacos的用户名
spring.cloud.sentinel.datasource.flow-rule.nacos.username=nacos
#连接nacos的密码
spring.cloud.sentinel.datasource.flow-rule.nacos.password=nacos
spring.cloud.sentinel.datasource.flow-rule.nacos.namespace=public
#nacos配置的数据
spring.cloud.sentinel.datasource.flow-rule.nacos.data-id=order-sentinel-flow-rule
#规则类型--流控
spring.cloud.sentinel.datasource.flow-rule.nacos.rule-type=flow

 (4)启动程序,访问接口,刷新sentinel控制台,规则已经被加载进来

 (5)需要主要一点,在sentinel中修改规则后,nacos中不会联动修改

20.Sentinel实现原理

Sentinel流控涉及两个方面:

①方法的拦截、处理

②多种流控规则、策略的实现

在Sentinel里,所有的资源都对应一个资源名称(resourceName),每次资源调用都会创建一个Entry对象。Entry可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用SphU API显式创建。Entry创建的时候,同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责,例如:
1、NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
2、ClusterBuilderSlot则用于存储资源的统计信息以及调用者信息,例如该资源的RT、QPS、thread count等等,这些信息将用作多维度限流、降级的依据
3、StatisticSlot则用于记录、统计不同维度的runtime指标监控信息
4、FlowSlot则用于根据预设的限流规则以及前面slot统计的状态,来进行流量控制
5、AuthoritySlot则根据配置的黑白名单和调用来源信息,来做黑白名单控制
6、DegradeSlot则通过统计信息以及预设的规则,来做熔断降级
7、SystemSlot则通过系统的状态,例如load1等,来控制总的入口流量

现在以SphU.entry方法为切入点来开始分析。这个方法会去申请一个entry,如果能够申请成功,则说明没有被限流,否则会抛出BlockException,表明已经被限流了。
从SphU.entry()方法往下执行会进入到Sph.entry(),Sph的默认实现类是CtSph,在CtSph中最终会执行到entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException这个方法。

sentinel的责任链的传递方式:每个Slot节点执行完自己的业务后,会调用fireEntry来触发下一个节点的entry方法,就通过SlotChain完成了对每个节点的entry()方法的调用,每个节点会根据创建的规则,进行自己的逻辑处理,当统计的结果达到设置的阈值时,就会触发限流、降级等事件,具体就是抛出BlockException异常。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值