百木园-与人分享,
就是让自己快乐。

一文说透Sentinel熔断策略、降级规则、流量控制

2 Sentinel 限流熔断降级

Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。我们先来学习Sentinel 核心库的使用,后面再学习Dashboard使用。

file

在我们项目中,用户请求通过hailtaxi-gateway路由到hailtaxi-driver或者hailtaxi-order,还有可能在hailtaxi-order中使用feign调用hailtaxi-driver,所以我们有可能在单个服务中实现熔断限流,也有可能要集成feign调用实现熔断限流,还有可能在微服务网关中实现熔断限流。我们接下来一步一步实现每一种熔断限流操作。

SpringBoot集成:

如果在SpringBoot项目中使用Sentinel,首先需要引入spring-cloud-starter-alibaba-sentinel依赖,并使用@SentinelResource标识资源。

hailtaxi-driver工程中引入spring-cloud-starter-alibaba-sentinel依赖,依赖如下:

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

2.1 @SentinelResource定义资源

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

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

blockHandler/blockHandlerClass

hailtaxi-driver中找到DriverController中的info方法,用户在打车的时候,会查询司机信息,如果司机不存在,此时会报错,代码改造如下:

/****
     * 司机信息
     */
@GetMapping(value = \"/info/{id}\")
//@RequestMapping(value = \"/info/{id}\")
public Driver info(@PathVariable(value = \"id\")String id,HttpServletRequest request){
    log.info(\"当前服务占用的端口为:{}\",port);
    Driver driver = driverService.findById(id);
    if (driver==null) {
        throw new RuntimeException(\"司机id=\"+id+\"不存在\");
    }
    return driver;
}

如果此时访问:http://localhost:18081/driver/info/3 查询司机信息,如果没有ID为3的司机信息,会报如下错误,

file

这种体验非常差,我们可以集成Sentinel使用@SentinelResourceblockHandler返回默认错误信息,形成降级!!!

1、Sentinel 支持在程序中抛出它定义的BlockException异常,该异常会被Sentinel捕获,然后走降级方法,

info()方法添加一个@SentinelResource注解,用来标注资源,表示当前方法需要执行限流、降级,在注解中添加value属性,用来标注资源,说白了就是给当前资源起个名字,blockHandler用来表示当前方法发生BlockException异常的时候,将处理流程交给指定的方法blockExHandler()处理,此时blockExHandler()方法必须和抛出异常的方法在同一个类中,这是一种降级操作,代码如下:

/****
     * 司机信息
     */
@SentinelResource(value = \"info\",blockHandler = \"blockExHandler\")
@RequestMapping(value = \"/info/{id}\")
public Driver info(@PathVariable(value = \"id\")String id) throws BlockException {
    log.info(\"当前服务占用的端口为:{}\",port);
    Driver driver = driverService.findById(id);
    if (driver==null) {
        //throw new RuntimeException(\"司机id=\"+id+\"不存在\");
        throw new SystemBlockException(\"info\", \"司机id=\"+id+\"不存在\",null); // 抛出BlockException
    }
    return driver;
}

/**
     * info资源出现BlockException后的降级处理
     */
public Driver blockExHandler(String id,BlockException e) {
    Driver driver = new Driver();
    driver.setId(id);
    driver.setName(\"系统繁忙,稍后再试\");
    return driver;
}

file

注意:

如果blockHandler方法和资源方法不在同一个类中,我们可以在@SentinelResource中添加blockHandlerClass属性,指定降级处理类的方法所在的类,且要求blockHandler方法是静态的,代码如下:

@SentinelResource(value = \"info\",blockHandler = \"blockExHandler\",blockHandlerClass = \"xxx.xxx.Xxxx\")

2、启动测试,访问:http://localhost:18081/driver/info/3 测试出错效果如下:

file

fallback/fallbackClass

1、如果我们希望抛出任何异常都能处理,都能调用默认处理方法,而并非只是BlockException异常才调用,此时可以使用@SentinelResourcefallback属性,代码如下:

/****
     * 司机信息
     */
@SentinelResource(value = \"info\"/*,blockHandler = \"blockExHandler\"*/,fallback = \"exHandler\")
@RequestMapping(value = \"/info/{id}\")
public Driver info(@PathVariable(value = \"id\")String id) throws BlockException {
    log.info(\"当前服务占用的端口为:{}\",port);
    Driver driver = driverService.findById(id);
    if (driver==null) {
        throw new RuntimeException(\"司机id=\"+id+\"不存在\");
        // throw new SystemBlockException(\"info\", \"司机id=\"+id+\"不存在\",null); // 抛出BlockException
    }
    return driver;
}
/**
     * info资源出现任何类型异常后的降级处理
     * 方法参数可以添加一个Throwable 类型的参数,也可不添加
     */
public Driver exHandler(String id,Throwable e) {
    Driver driver = new Driver();
    driver.setId(id);
    driver.setName(\"系统繁忙,稍后再试\");
    return driver;
}

注意:

如果fallback方法和当前的资源方法不在同一个类中,可以使用@SentinelResource注解的fallbackClass实现,也要求fallback方法是静态的,代码如下:

@SentinelResource(value = \"info\",fallback =\"exHandler\" ,fallbackClass = \"xx.xxx.xxx.xx.Xxx\")

2、访问 http://localhost:18081/driver/info/3 测试出错效果如下:

file

defaultFallback

上面无论是blockHandler还是fallback,每个方法发生异常,都要为方法独立创建一个处理异常的方法,效率非常低,我们可以使用@SentinelResource注解的defaultFallback属性,为一个类指定一个全局的处理错误的方法,代码如下:

@RestController
@RequestMapping(value = \"/driver\")
@Slf4j
@RefreshScope
@SentinelResource(defaultFallback = \"defaultExHandler\")
public class DriverController {
    @Autowired
    private DriverService driverService;

    public Driver defaultExHandler(Throwable e) {
        Driver driver = new Driver();
        driver.setName(\"系统繁忙,稍后再试\");
        return driver;
    }

    /****
     * 司机信息
     */
    //@SentinelResource(value = \"info\"/*,blockHandler = \"blockExHandler\"*/,fallback = \"exHandler\")
    @SentinelResource(\"info\")
    @RequestMapping(value = \"/info/{id}\")
    public Driver info(@PathVariable(value = \"id\")String id) throws BlockException {
        log.info(\"当前服务占用的端口为:{}\",port);
        Driver driver = driverService.findById(id);
        if (driver==null) {
            throw new RuntimeException(\"司机id=\"+id+\"不存在\");
            // throw new SystemBlockException(\"info\", \"司机id=\"+id+\"不存在\",null); // 抛出BlockException
        }
        return driver;
    }

访问 http://localhost:18081/driver/info/3 效果如下:

file

2.2 Sentinel的规则

Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,供您来定制自己的规则策略。

Sentinel 支持以下几种规则:流量控制规则熔断降级规则系统保护规则来源访问控制规则热点参数规则

2.2.1 流量控制规则 (FlowRule)

流量规则的定义,重要属性如下:

Field 说明 默认值
resource 资源名,资源名是限流规则的作用对象
count 限流阈值
grade 限流阈值类型,QPS 模式(1)或并发线程数模式(0) QPS 模式
limitApp 流控针对的调用来源 default,代表不区分调用来源
strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 直接拒绝
clusterMode 是否集群限流

同一个资源可以同时有多个限流规则,检查规则时会依次检查

strategy限流策略说明:

直接:资源达到限流条件时,直接限流。

关联:A资源关联B资源,当关联的B资源达到阈值限流时,A资源也会被限流。

链路:对于某资源C,有两个入口,从资源A->C,从资源B->C, 通过指定入口资源可以达到只记录从该入口进来的流量(指定资源从入口资源进来的流量,如果达到阈值,就可以对其限流)。

controlBehavior流控说明:

直接拒绝:请求直接失败。
WarmUp:当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过\"冷启动\",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
排队等待:排队处理请求。

理解上面规则的定义之后,我们可以通过调用 FlowRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。

QPS流量控制

1、我们先实现基于QPS流量控制,在hailtaxi-driverDriverApplication启动类上添加如下方法加载限流规则,当DriverApplication初始化完成之后加载规则,代码如下:

/***
 * 初始化规则
 */
@PostConstruct
private void initFlowRule() {
    //规则集合
    List<FlowRule> rules = new ArrayList<FlowRule>();
    //定义一个规则
    FlowRule rule = new FlowRule(\"info\");
    // 设置阈值
    rule.setCount(2);
    //设置限流阈值类型
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    //default,代表不区分调用来源
    rule.setLimitApp(\"default\");
    //设置流控效果
    rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
    //将定义的规则添加到集合中
    rules.add(rule);
    //加载规则
    FlowRuleManager.loadRules(rules);
}

2、访问 http://localhost:18081/driver/info/1 此时不会抛出异常,但是频繁刷新,则会调用降级方法,效果如下:

file

线程数流量控制

1、我们修改限流阈值类型,代码如下:

@PostConstruct
public void initFlowRule() {
    //规则集合
    List<FlowRule> rules = new ArrayList<>();
    // 定义一个规则
    FlowRule rule = new FlowRule(\"info\");
    // 设置基流量控制 的类型
    rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);//默认是qps
    //设置流量阈值
    rule.setCount(2);
    // 将 规则添加到 集合中
    rules.add(rule);
    // 加载规则
    FlowRuleManager.loadRules(rules);
}

2、此时再来访问http://localhost:18081/driver/info/1我们发现用浏览器无论怎么访问都不会出现降级现象,但是如果用Jmeter模拟多个线程,效果就不一样了,效果如下:

file

2.2.2 熔断降级规则 (DegradeRule)

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

Field 说明 默认值
resource 资源名,即规则的作用对象
grade 熔断策略,支持慢调用比例/异常比例/异常数策略 慢调用比例
count 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow 熔断时长,单位为 s
minRequestAmount 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) 5
statIntervalMs 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) 1000 ms
slowRatioThreshold 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

同一个资源可以同时有多个降级规则。

理解上面规则的定义之后,我们可以通过调用 DegradeRuleManager.loadRules() 方法来用硬编码的方式定义熔断规则,

1、在DriverApplication规则定义如下:

@PostConstruct
public void initDegradeRule() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule();
    // 设置资源名称
    rule.setResource(\"info\");
    /**
         * 设置熔断策略
         * DEGRADE_GRADE_RT:平均响应时间
         * DEGRADE_GRADE_EXCEPTION_RATIO:异常比例数量
         * DEGRADE_GRADE_EXCEPTION_COUNT:异常数
         */
    rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
    //设置阈值
    rule.setCount(2);
    //设置 熔断时长
    rule.setTimeWindow(30);
    // 统计时长(单位为 ms) 默认1000
    rule.setStatIntervalMs(60*1000);
    //将规则添加到集合中
    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}

2、首先访问:http://localhost:18081/driver/info/1 ,确保没问题,

其次访问:http://localhost:18081/driver/info/3,多访问几次,造成熔断(5+2=7)

然后在访问:http://localhost:18081/driver/info/1,会发现已经有熔断降级效果了,

且查看服务控制台,发现不会有信息输出,表明已经熔断了,且从页面展示效果来看走了降级

file

等待30s后,再次访问:http://localhost:18081/driver/info/1,查看熔断是否结束!

2.2.3 系统保护规则 (SystemRule)

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统规则包含下面几个重要的属性:

Field 说明 默认值
highestSystemLoad load1 触发值,用于触发自适应控制阶段 -1 (不生效)
avgRt 所有入口流量的平均响应时间 -1 (不生效)
maxThread 入口流量的最大并发数 -1 (不生效)
qps 所有入口资源的 QPS -1 (不生效)
highestCpuUsage 当前系统的 CPU 使用率(0.0-1.0) -1 (不生效)

理解上面规则的定义之后,我们可以通过调用 SystemRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。

1、在hailtaxi-driverDriverApplication中创建如下方法,代码如下:

/***
 * 系统自我保护
 */
@PostConstruct
private void initSystemRule() {
    //系统自我保护集合
    List<SystemRule> rules = new ArrayList<>();
    //创建系统自我保护规则
    SystemRule rule = new SystemRule();
    //CPU使用率 值为[0,1],-1 (不生效)
    rule.setHighestCpuUsage(0.2);
    //所有入口资源的 QPS,-1 (不生效)
    rule.setQps(10);
    //入口流量的最大并发数,-1 (不生效)
    rule.setMaxThread(5);
    //所有入口流量的平均响应时间,单位:秒,-1 (不生效)
    rule.setAvgRt(5);
    //load1 触发值,用于触发自适应控制阶段,系统最高负载,建议取值 CPU cores * 2.5
    rule.setHighestSystemLoad(20);
    //将规则加入到集合
    rules.add(rule);
    SystemRuleManager.loadRules(rules);
}

我们可以测试CPU使用率自我保护,使用jmeter测试如下:

file

2.2.4 访问控制规则 (AuthorityRule)

很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的访问控制(黑白名单)的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

授权规则,即黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

  • resource:资源名,即规则的作用对象
  • limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
  • strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式

了解了以上规则后,可以通过AuthorityRuleManager.loadRules来加载规则

1、在hailtaxi-driverDriverApplication中创建如下方法,代码如下:

@PostConstruct
    public void initAuthorityRule() {
        AuthorityRule rule = new AuthorityRule();
        rule.setResource(\"info\");
        rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
        rule.setLimitApp(\"127.0.0.1,appB\");
        AuthorityRuleManager.loadRules(Collections.singletonList(rule));
    }

    /**
     * Sentinel提供了 RequestOriginParser 接口来处理访问来源,Sentinel保护的资源如果被访问,
     * 就会调用 RequestOriginParser解析访问来源
     */
    @Component
    public class IpLimiter implements RequestOriginParser{

        @Override
        public String parseOrigin(HttpServletRequest httpServletRequest) {
            return httpServletRequest.getRemoteAddr();
        }
    }

2、访问:http://localhost:18081/driver/info/1,不通过,走了降级

​ 访问:http://127.0.0.1:18081/driver/info/1

2.2.4 热点规则 (ParamFlowRule)

何为热点?热点即经常访问的数据。

很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

1:商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
2:用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

file

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

要使用热点参数限流功能,需要引入以下依赖,将该依赖加入到hailtaxi-driver中:

<!--热点参数限流-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-parameter-flow-control</artifactId>
    <version>1.8.1</version>
</dependency>

然后为对应的资源配置热点参数限流规则,并在 entry 的时候传入相应的参数,即可使热点参数限流生效。

热点参数规则(ParamFlowRule)类似于流量控制规则(FlowRule):

属性 说明 默认值
resource 资源名,必填
count 限流阈值,必填
grade 限流模式 QPS 模式
durationInSec 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 1s
controlBehavior 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 快速失败
maxQueueingTimeMs 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 0ms
paramIdx 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置
paramFlowItemList 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型
clusterMode 是否是集群参数流控规则 false
clusterConfig 集群流控相关配置

我们可以通过 ParamFlowRuleManagerloadRules 方法更新热点参数规则

1、在DriverController中创建一个司机筛选方法,比如根据城市来筛选,在DriverController中创建一个方法:

/***
     * 搜素指定城市的司机
     */
@SentinelResource(value = \"search\")
@GetMapping(value = \"/search/{city}\")
public Driver search(@PathVariable(value = \"city\")String city){
    System.out.println(\"查询的司机所在城市:\"+city);
    //假设查询到了一个司机信息
    Driver driver = new Driver();
    driver.setName(\"唐僧老师\");
    driver.setId(\"No.1\");
    return driver;
}

2、对热门参数进行控制,对热点数据执行特殊限流,比如资源参数列表中的第一个参数值为恩施的时候执行限流,在DriverApplication中创建限流配置,代码如下:

/***
 * 热点参数初始化
 */
@PostConstruct
private static void initParamFlowRules() {
    ParamFlowRule rule = new ParamFlowRule(\"search\")
            //参数下标为0
            .setParamIdx(0)
            //限流模式为QPS
            .setGrade(RuleConstant.FLOW_GRADE_QPS)
            //统计窗口时间长度(单位为秒)
            .setDurationInSec(10)
            //流控效果(支持快速失败和匀速排队模式)
            //CONTROL_BEHAVIOR_DEFAULT:限流行为,直接拒绝
            //CONTROL_BEHAVIOR_WARM_UP:限流行为,匀速排队
            //CONTROL_BEHAVIOR_RATE_LIMITER:限流行为,匀速排队
            .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)
            //最大排队等待时长(仅在匀速排队模式生效  CONTROL_BEHAVIOR_RATE_LIMITER)
            //.setMaxQueueingTimeMs(600)
            //最大阈值为5
            .setCount(5);

    // 为特定参数单独设置规则
    //如下配置:当下标为0的参数值为恩施的时候,阈值到达2的时候则执行限流
    ParamFlowItem item = new ParamFlowItem()
            //参数类型为String类型
            .setClassType(String.class.getName())
            //设置阈值为2
            .setCount(2)
            //需要统计的值
            .setObject(String.valueOf(\"恩施\"));
    rule.setParamFlowItemList(Collections.singletonList(item)); //返回的是不可变的集合,但是这个长度的集合只有1,可以减少内存空间
    //加载热点数据
    ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}

2、我们访问 http://localhost:18081/driver/search/天津/ 的时候,连续执行5次,才会限流,

我们访问 http://localhost:18081/driver/search/恩施/ 的时候,连续执行2次,就会限流,

2.3 OpenFeign支持

Sentinel 适配了 Feign 组件。如果想使用,除了外还需要 2 个步骤:

1:引入 `spring-cloud-starter-alibaba-sentinel` 的依赖
2:加入 spring-cloud-starter-openfeign 依赖
3:配置文件打开 Sentinel 对 Feign 的支持:feign.sentinel.enabled=true

file

在上面案例中,我们可以实现用户打车成功调用hailtaxi-order执行下单,并且通过feign调用hailtaxi-driver修改司机状态,此时我们可以使用Sentinel实现Feign调用降级、限流。

注意:

在进行操作之前,可以先将hailtaxi-driver中的访问控制规则注释掉,注释掉DriverApplication#initAuthorityRuleDriverApplication#initSystemRule上的注解即可

1、在hailtaxi-order中引入sentinelOpenFeign依赖,配置如下:

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

<!--Openfeign  api模块中已存在也可不引入-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2、在hailtaxi-order的配置文件中开启Feign支持sentinel,配置如下:

feign:
  #开启Sentinel对Feign的支持
  sentinel:
    enabled: true

注意:现在配置信息都存放在了nacos中,所以找到hailtaxi-order.yaml的Data ID配置,将以上配置添加进去!

file

注意修改完后一定要发布才能生效!!!

3、因为hailtaxi-order要通过openfeign去调用hailtaxi-driver中的DriverController#status方法,改造一下该方法

/****
     * 更新司机信息
     */
@PutMapping(value = \"/status/{id}/{status}\")
public Driver status(@PathVariable(value = \"id\")String id,@PathVariable(value = \"status\")Integer status) throws Exception {
    log.info(\"当前服务占用的端口为:{}\",port);
    //修改状态
    driverService.update(id,status);
    //修改状态后的司机信息
    Driver driver = driverService.findById(id);
    if (driver == null) {
        throw new RuntimeException(\"学生id=\"+id+\",不存在\");
    }
    return driver;
}

模拟被调用服务出现异常的情况

3、先验证正确性,启动hailtaxi-gatewayhailtaxi-orderhailtaxi-driver服务,使用postman访问:

http://localhost:8001/order

4、为了测试程序异常能实现降级操作,我们在hailtaxi-order中将OrderInfoController.add()方法的司机ID改成一个不存在的司机ID,让程序报错,测试降级处理,代码如下:

/***
     * 下单
     */
@PostMapping
public OrderInfo add(){
    //修改司机信息  司机ID=1
    Driver driver = driverFeign.status(\"3\",2);// 改成一个不存在的id
    //创建订单
    OrderInfo orderInfo = new OrderInfo(\"No\"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), \"深圳北站\", \"罗湖港\", driver);
    orderInfoService.add(orderInfo);
    return orderInfo;
}

file

5、再次启动测试:

file

注意:服务调用出错要进行降级操作有两个地方都可以进行,一是在被调用方进行降级,一是在调用方进行降级,

在被调用方进行降级前面已经讲过了,所以接下来讲解如何在调用方(openfeign)结合 sentinel 进行降级处理。

2.3.1 fallback

hailtaxi-api模块中找到DriverFeign接口:

1、为Feign接口创建一个实现类:com.itheima.driver.feign.fallback.DriverFeignFallback,在实现类中处理程序异常降级处理方法,代码如下:

package com.itheima.driver.feign.fallback;

import com.itheima.driver.feign.DriverFeign;
import com.itheima.driver.model.Driver;
import org.springframework.stereotype.Component;

@Component
public class DriverFeignFallback implements DriverFeign {

    /**
     * status()降级处理方法
     */
    @Override
    public Driver status(String id, Integer status) {
        Driver driver = new Driver();
        driver.setId(id);
        driver.setStatus(status);
        driver.setName(\"系统比较繁忙,请您稍后再试!\");
        return driver;
    }
}

2、在DriverFeign接口上添加fallback属性指定降级处理的类,代码如下:

@FeignClient(value = \"hailtaxi-driver\",fallback = DriverFeignFallback.class)

注意:修改了hailtaxi-api模块,最好cleanpackage!!

3、启动运行,会发生如下问题:

java.lang.AbstractMethodError: com.alibaba.cloud.sentinel.feign.SentinelContractHolder.parseAndValidatateMetadata(Ljava/lang/Class;)Ljava/util/List;

出现上面问题的主要原因是当前SpringCloud版本存在问题。

Hoxton.SR1 中,fegin.context接口方法的定义为parseAndValidatateMetadata
Hoxton.SR3 中,fegin.context接口方法的定义为parseAndValidateMetadata

我们现在需要把Hoxton.SR1换成Hoxton.SR3,因此需要在hailtaxi-parent修改SpringCloud版本:

file

SpringCloud版本升级至Hoxton.SR3同时将SpringBoot版本升级至2.2.10,如上图:

https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-Hoxton-Release-Notes

此时我们测试,效果如下:

file

2.3.2 fallbackFactory

我们可以为DriverFeign接口创建一个降级处理的工厂对象,在工厂对象中处理程序异常降级处理方法,用工厂对象处理可以拿到异常信息,代码如下:

1、创建:com.itheima.driver.feign.fallback.DriverFeignFallbackFactory

package com.itheima.driver.feign.fallback;

import com.itheima.driver.feign.DriverFeign;
import com.itheima.driver.model.Driver;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class DriverFeignFallbackFactory implements FallbackFactory<DriverFeign> {
    @Override
    public DriverFeign create(Throwable throwable) {
        return new DriverFeign() {
            /**
             * status()降级处理方法
             */
            @Override
            public Driver status(String id, Integer status) {
                Driver driver = new Driver();
                driver.setId(id);
                driver.setStatus(status);
                driver.setName(\"系统比较繁忙,请您稍后再试!\");
                return driver;
            }
        };
    }
}

2、在DriverFeign接口上添加fallbackFactory属性指定讲解处理的类,代码如下:

@FeignClient(value = \"hailtaxi-driver\",fallbackFactory = DriverFeignFallbackFactory.class)

3、再次启动测试,效果如下:

file

好了,接下来,咱么来说一说Sentinel控制台

4 Sentinel控制台

Sentinel 控制台是流量控制、熔断降级规则统一配置和管理的入口,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。在 Sentinel 控制台上,我们可以配置规则并实时查看流量控制效果。

4.1 Sentinel控制台安装

Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。

Sentinel 控制台包含如下功能:

  • 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
  • 监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
  • 规则管理和推送:统一管理推送规则。
  • 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。

Sentinel控制台安装可以基于jar包启动的方式安装,也可以基于docker安装,为了方便操作,我们这里采用docker安装方式:

docker run --name=sentinel-dashboard -d -p 8858:8858 -d --restart=on-failure bladex/sentinel-dashboard:1.8.0

安装好了后,我们可以直接访问 http://192.168.200.129:8858/,默认用户名和密码都是 sentinel

file

登录后,效果如下:

file

4.2 接入控制台

客户端需要引入 Transport 模块来与 Sentinel 控制台进行通信,可以通过 pom.xml 引入 JAR 包:

<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>x.y.z</version>
</dependency>

如果是SpringBoot工程接入Sentinel,可以直接引入如下依赖包:

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

hailtaxi-gateway接入控制台:

1、引入依赖包:

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

2、在核心配置文件中配置Sentinel控制台服务地址

spring:
  cloud:
    sentinel:
      transport:
        port: 8719
        dashboard: 192.168.200.129:8858

注意:

1、这里的 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互,比如限流规则拉取。

2、配置信息现在是存储到nacos中,故要在nacos中找到hailtaxi-gateway.yaml,进行如下配置

file

修改之后记得发布!

3、启动各个服务,

此时我们出发一些请求操作,再看Sentinel控制台会多一个服务监控:

file

4.3 可视化管理

4.3.1 实时监控

同一个服务下的所有机器的簇点信息会被汇总,并且秒级地展示在\"实时监控\"下。

注意: 实时监控仅存储 5 分钟以内的数据,如果需要持久化,需要通过调用实时监控接口来定制。

file

如果要获取监控数据,直接调用 http://localhost:8719/clusterNode 即可获取,效果如下:

file

4.3.2 流控规则

我们可以在【流控规则】页面中新增,点击【流控规则】进入页面新增页面,如下图:

file

资源名:其实可以和请求路径保持一致,这里的流控模式为QPS,触发流控执行阈值为1,流控模式为让当前请求的资源快速直白。

这里的参数和我们程序中的参数其实是一样的,如下说明:

resource:资源名,即限流规则的作用对象
count: 限流阈值
grade: 限流阈值类型(QPS 或并发线程数)
limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
strategy: 调用关系限流策略:直接,关联,链路
controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)

我们测试效果如下:

file

4.3.3 降级规则

我们可以选择降级规则>新增降级规则,如下图:

1614185331968

降级规则的熔断策略有3种,分别是慢调用比例、异常比例、异常数,和程序中是一样的。

4.3.4 热点数据

热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。

file

拓展知识点:

注意:如果流控模式是链路模式,需要引入如下依赖:

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

创建过滤器:

/***
 * CommonFilter过滤器
 * @return
 */
@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;
}

bootstrap.yml配置:web-context-unify: false

  cloud:
    sentinel:
      transport:
        port: 8719
        dashboard: localhost:8858
      web-context-unify: false  #可根据不同的URL 进行链路限流

本文由传智教育博学谷 - 狂野架构师教研团队发布
转载请注明出处!


来源:https://www.cnblogs.com/jiagooushi/p/16355571.html
本站部分图文来源于网络,如有侵权请联系删除。

未经允许不得转载:百木园 » 一文说透Sentinel熔断策略、降级规则、流量控制

相关推荐

  • 暂无文章