流量防卫兵sentinel

sentinel简介

Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性,被称作是分布式系统的流量防卫兵。Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

github主页:https://github.com/alibaba/Sentinel

sentinel环境配置

1.启动sentinel控制台

下载sentinel控制台jar包:https://github.com/alibaba/Sentinel/releases

Sentinel 控制台是一个标准的 Spring Boot 应用,以 Spring Boot 的方式运行 jar 包即可。命令提示符下输入下面命令:

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.2.jar

如果端口号冲突的时候可以通过 -Dserver.port=新端口 设置新的端口,注意参数最后面的jar名要跟你下载的sentinel的jar名一致。启动成功之后从流览器中访问localhost:8080,默认的用户名和密码都是sentinel。

2.添加依赖

在buy模块中添加sentinel的依赖

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

3.修改buy模块中的配置文件

在配置文件中,将sentinel控制台的地址配置进去

#跟控制台交流的端口,选择一个未占用的即可
spring.cloud.sentinel.transport.port=8888
#sentinel的地址
spring.cloud.sentinel.transport.dashboard=localhost:8080

4.在buy模块中添加一个退款操作

BuyController中添加方法

/*
   取消交易
*/
@GetMapping("cancel")
public String cancel() {

    return buyService.refund();
}

这里使用feign,在BuyService中添加方法

@GetMapping("refund")
String refund();

5.在pay模块中添加退款处理

PayController中添加方法

@GetMapping("refund")
public String refund() {
    return "退款成功";
}

启动buy模块进行访问,然后回到sentinel中刷新即可看到buy模块相关的信息了。注意要先访问buy模块才能在sentinel中看到相关信息。按照下图在sentinel控制台中设置(这里设置的是单机QPS为1)

设置完成之后在浏览器中访问cancel时1秒内刷新次数超过1就可以看都流控效果了。

sentinel中的规则

对于开发来说,通常在sentinel控制台中进行一些列的配置就可以了,因此接下来我们来看下sentinel中的规则。官方文档:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

流控规则

1.阈值类型
  • qps上面的操作就是qps直接失败
  • 线程数将线程数的单机阈值设置为1,此时开启两个浏览器,在访问的时候就可以看到流控效果了。为了便于测试,可以在调用的服务中添加代码睡眠一定的时间。
2.流控模式

下面以qps为例进行设置

  • 直接不做其他特殊的处理,当达到阈值时直接对资源进行流控处理,最开始上面操作的就是直接。
  • 关联比如有两个资源A和B进行了关联,当A资源达到阈值的时候,就会流控资源B。如下所示,设置buy和cancel进行关联,当cancel达到阈值的时候,buy就会被流控了。
  • 链路记录指定链路上的流量,在Service中新增testChain方法,Controller中的buy方法和cancel方法都对其进行了调用,这就相当于是两个链路了,对于testChain方法来说可以针对buy或者cancel单独进行流控设置,从而达到更细粒度的控制。如果要想达到该效果的话,需要进行一些特殊的设置。sentinel的1.7.0 版本开始(对应Spring cloud alibaba的2.1.1.RELEASE),官方在CommonFilter 引入了WEB_CONTEXT_UNIFY 参数,将其配置为 false 即可根据不同的URL 进行链路限流。在spring cloud alibaba的2.1.1.RELEASE之后的版本,可以通过如下配置即可
    spring.cloud.sentinel.web-context-unify=false
    

    2.1.1.RELEASE之前版本不支持上面的配置,所以需要手动进行设置。首先添加依赖

    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-web-servlet</artifactId>
        <version>1.7.2</version>
    </dependency>
    

    配置文件中关闭sentinel过滤器

    spring.cloud.sentinel.filter.enabled=false
    

    自己编写filter

    package com.monkey1024.config;
    
    import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class FilterContextConfig {
        @Bean
        public FilterRegistrationBean sentinelFilterRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new CommonFilter());
            registration.addUrlPatterns("/*");
            // 入口资源关闭
            registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
            registration.setName("sentinelFilter");
            registration.setOrder(1);
            return registration;
        }
    }
    

    创建BuyServiceImpl并在controller中的buy和cancel方法中进行调用

    package com.monkey1024.service;
    
    import com.alibaba.csp.sentinel.annotation.SentinelResource;
    import org.springframework.stereotype.Service;
    
    @Service
    public class BuyServiceImpl implements BuyService {
        @Override
        @SentinelResource("testChain")//资源名称,会显示在sentinel控制台中
        public void testChain() {
            System.out.println("testChain");
        }
    }
    

    从sentinel控制台中进行链路设置之后,浏览器访问/cancel超过设置的QPS之后会看到流控效果

3.流控效果
  • 快速失败直接让请求失败
  • warm up预热系统长期处于低流量情况下,流量突然增加时,启动资源需要额外开销,例如建立数据库连接等,可能瞬间把系统压垮。此时可以通过预热让流量缓慢增长,给系统一个预热时间来解决这个问题。如下图所示,阈值设置为6,而默认 coldFactor 为 3,qps会从6/3即2开始经过5秒钟之后预热到6。
  • 排队等待此时如果有多个请求过来的时候,会依次排队就行处理。

降级规则

当某个资源出现不稳定状态时,例如调用超时或异常比例升高等,sentinel会对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致系统崩溃。当一个资源被降级后,在接下来的一定时间内,对该资源的调用都自动熔断。降级规则有如下三个。

  • 平均响应时间 :当 1s 内持续进入 多个请求,平均响应时间(秒级)均超过阈值(毫秒级),那么在接下的一定时间(秒)之内,对这个方法的调用都会自动地熔断。注意 Sentinel 默认统计的 RT 最大值是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。如下图设置的内容是当平均响应时间超过1ms的时候,在接下来的5秒内对该资源的调用自动熔断,5秒后恢复,重新判断。
  • 异常比例:当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值之后,资源熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% – 100%。为了便于演示修改BuyController中的代码如下,满足一定的异常比例:
    //控制异常的出现
    public int count;
    
    @GetMapping("buy")
    public String buy() {
    
        //出现异常的概率是0.33333
        if (count++ % 3 == 0) {
            throw new RuntimeException();
        }
    
        return buyService.pay();
    }
    

    通过下图中的配置,当异常比例超过0.25的时候,在接下来的5秒内会对资源进行熔断,5秒后恢复,重新判断。

  • 异常数 :当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意统计时间若设置小于 60s,则结束熔断状态后仍可能再进入熔断状态。如下图所示,当1分钟内的异常数超过2个的时候,接下来的65秒内会对该资源的调用进行熔断,之后恢复并开始重新判断

热点规则

热点规则可以将流控规则细化到参数上面,接下来通过代码演示,在BuyController中添加一个测试方法:

@GetMapping("order")
@SentinelResource("order")
public String order(Integer id,String name) {
    return "下单成功";
}

在sentinel控制台中设置如下,其中参数索引表示对应的参数的索引值,上面中的id的索引是0,name索引是1。

配置好之后,当请求参数包含id的时候就会被流控了。除此之外我们还可以针对参数的具体值来设置流控,在sentinel控制台中点击编辑,在高级选项中填写下面内容,表示当id的值是8且阈值超过100的时候才会流控

授权规则

当需要根据调用来源来判断该次请求是否允许放行时,可以使用 Sentinel 的授权规则(黑白名单控制)的功能。若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class RequestOriginConfig implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        return request.getParameter("serviceName");
    }
}

在sentinel控制台中进行如下配置,之后请求参数中携带serviceName=test的时候,是不会进行流控的,把test改成其他值就可以看到流控效果了。

显示异常信息

在之前的示例中,无法从页面的信息中判断当前请求是被哪种规则控制了,此时我们需要编写代码来匹配相应的规则,之后将信息返回到前台就一直到当前请求被哪种规则控制了。创建BlockExceptionHandler的实现类类并重写里面的handle方法,通过方法参数中的BlockException可以判断出当前是哪种规则。

  • FlowException 限流异常
  • DegradeException 降级异常
  • ParamFlowException 参数限流异常
  • AuthorityException 授权异常
  • SystemBlockException 系统负载异常
package com.monkey1024.config;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class ExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "";
        //通过BlockException可以判断出当前是哪种规则
        if (e instanceof FlowException) {
            msg = "限流";
        } else if (e instanceof DegradeException) {
            msg = "降级";
        }
        System.out.println(e);

        response.getWriter().write(msg);
    }
}

流控RestTemplate

我们可以通过下面配置开启sentinel流控RestTemplate,当然,默认就是开启的。

resttemplate.sentinel.enabled=true

在创建RestTemplate对象的方法上添加注解@SentinelRestTemplate

@Configuration
public class BeanConfig {
    /*
        出现BlockException时会调用MonkeyBlockHandler类中的block方法
     */
    @SentinelRestTemplate(blockHandlerClass = MonkeyBlockHandler.class,blockHandler = "block")
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

创建MonkeyBlockHandler类

package com.monkey1024.handler;

import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;

public class MonkeyBlockHandler {
	
    //必须是static方法
    public static SentinelClientHttpResponse block(HttpRequest request, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution, BlockException blockException) {
        blockException.printStackTrace();
        //会显示在前台页面中
        return new SentinelClientHttpResponse("流控信息");
    }
}

设置好之后就可以在sentinel控制台中看到调用的restTemplate信息了,设置流控规则再访问就可以看到流控效果。

流控feign

sentimel默认并未开启对feign的流控,需要在配置文件中开启

#开启sentinel对feign的流控
feign.sentinel.enabled=true

创建feign业务接口的实现类来处理流控的操作,当调用feign接口被流控后,会调用这里对应的方法。该类的对象需要加入到spring容器中

package com.monkey1024.handler;

import com.monkey1024.service.BuyService;
import org.springframework.stereotype.Component;

@Component
public class BuyServiceFeignHandler implements BuyService {
    @Override
    public String pay() {

        return "支付fein接口被流控";
    }

    @Override
    public String refund() {
        return "退款feign接口被流控";
    }
}

在feign业务接口注解@FeignClient中添加属性fallback,值是上面的类

@FeignClient(name = "${service-url.pay-service}",fallback = BuyServiceFeignHandler.class)
public interface BuyService {

    //服务提供者对应的请求映射,就是
    @GetMapping("/pay")
    String pay();

    @GetMapping("refund")
    String refund();
}

接下来就可以在sentinel控制台中对feign接口设置流控规则了。