Sentinel——熔断规则

发布时间 2023-12-04 00:10:59作者: 谁风霜依旧

熔断规则

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。[1]

Sentinel 提供以下几种熔断策略:

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

  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0],代表 0% - 100%。

  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

慢调用比例

设置说明:1s内的请求数量大于100,且有超过30%的响应时间大于2000ms,服务熔断10s后进入HALF-OPEN 状态,如果下一次请求的响应时间超过2000ms,服务会再次熔断。

发生熔断:

如果是设置的是方法签名的熔断,代表如果发送熔断,处理的方法为自定义的方法。如下面方法,执行getFallBack方法。

    /**
     * 根据id查询部门
     */
    @SentinelResource(fallback = "getFallBack")
    @GetMapping("/get/{id}")
    public Depart get(@PathVariable Long id) {
        return restTemplate.getForObject(PROVIDER_URL + "/get/" + id, Depart.class);
    }

    /**
     * 服务降级使用的方法
     */
    public Depart getFallBack(Long id, Throwable t) {
        log.info("id = " + id);
        log.info("throwable = " + t.getMessage());
        Depart depart = new Depart();
        depart.setId(id);
        depart.setName("no this depart");
        return  depart;
    }

也可以给需要熔断的方法设置别名,如我们给get/{id},设置资源别名为value = "get"。fallback一定要设置,不让会出现500空白页

    /**
     * 根据id查询部门
     */
    @SentinelResource(value = "get", fallback = "getFallBack")
    @GetMapping("/get/{id}")
    public Depart get(@PathVariable Long id) {
        return restTemplate.getForObject(PROVIDER_URL + "/get/" + id, Depart.class);
    }

资源名为我们设置的别名。

需要注意的是,在控制台配置的熔断规则在服务重启后会丢失,但是我们可以在代码中进行设置,这样重启服务配置依然在。

慢比例调用代码实现

可以利用sentinel提供的api在代码中进行设置,或者自定义配置类在配置类中定义。

package com.zjw;

import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.ArrayList;
import java.util.List;

@SpringBootApplication
public class ConsumerSentinel8080Application {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerSentinel8080Application.class, args);
        //初始化熔断规则
        initDegradeRule();
    }

    /**
     * 初始化熔断规则
     */
    private static void initDegradeRule() {
        DegradeRuleManager.loadRules(configDegradeRule());
    }

    private static List<DegradeRule> configDegradeRule(){
        List<DegradeRule> degradeRuleList = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        // sentinel资源名称,这里填入value值,@SentinelResource(value = "get", fallback = "getFallBack")
        rule.setResource("get");
        // 熔断策略,默认是慢调用比例,0: average RT, 异常比例 1: exception ratio, 异常数 2: exception count
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);//慢调用比例
//        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);//异常比例
//        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);//异常数
        // 慢调用比例策略下为最大RT,响应时间,单位ms;异常比例策略下为比例阈值[0.0, 1.0];异常数策略下为异常数,int
        rule.setCount(800);
        // 比例阈值,默认为1.0,即100%。慢调用比例策略才需要设置
        rule.setSlowRatioThreshold(0.5D);
        // 熔断时长,单位s
        rule.setTimeWindow(10);
        // 最小请求数
        rule.setMinRequestAmount(200);
        // 统计时长,单位ms,默认为1000ms
        rule.setStatIntervalMs(1000);

        degradeRuleList.add(rule);
        return degradeRuleList;
    }
}

自定义异常处理器(返回响应流)

默认的降级响应在DefaultBlockExceptionHandler定义,该类继承了BlockExceptionHandler,如果我们自定义异常处理器,可以继承BlockExceptionHandler,并且交给Spring管理。

自定义异常处理类

package com.zjw.handler;

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.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;

/**
 * @since 2023/12/03 23:22
 */
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        // Return 429 (Too Many Requests) by default.
        response.setStatus(429);

        String msg = "";
        if (e instanceof AuthorityException) {
            msg = "Blocked by Sentinel (authority limiting)";
        } else if (e instanceof DegradeException) {
            msg = "Blocked by Sentinel (degrade limiting)";
        } else if (e instanceof FlowException) {
            msg = "Blocked by Sentinel (flow limiting)";
        } else if (e instanceof ParamFlowException) {
            msg = "Blocked by Sentinel (param flow limiting)";
        } else if (e instanceof SystemBlockException) {
            msg = "Blocked by Sentinel (system limiting)";
        } else {
            msg = "Blocked by Sentinel";
        }
        PrintWriter out = response.getWriter();
        out.print(msg);
        out.flush();
        out.close();
    }
}

BlockException有五个子类。

如果定义的fallback处理方法是不会走到自定义异常类的。否则会进入自定义异常类处理。

测试

添加熔断规则

controller方法

    @GetMapping("/list")
    public List<Depart> list() {
        return (List<Depart>)restTemplate.getForObject(PROVIDER_URL + "/list", List.class);
    }

多次请求返回结果

自定义异常处理器(返回页面)

异常处理器

package com.zjw.handler;

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.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

/**
 * 自定义异常处理器 返回页面
 * @since 2023/12/03 23:22
 */
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String page = "/default.html";
        if (e instanceof AuthorityException) {
            page = "/authorityException.html";
        } else if (e instanceof DegradeException) {
            page = "/degradeException.html";
        } else if (e instanceof FlowException) {
            page = "/flowException.html";
        } else if (e instanceof ParamFlowException) {
            page = "/paramFlowException.html";
        } else if (e instanceof SystemBlockException) {
            page = "/systemBlockException.html";
        }
        request.getRequestDispatcher(page).forward(request,response);
    }
}

如果需要重定向,可以这样写:

/**
 * 自定义异常处理器 重定向 适用前后端分离
 * @since 2023/12/03 23:22
 */
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String page = "https://www.taobao.com";
        if (e instanceof AuthorityException) {
            page = "https://www.baidu.com";
        } else if (e instanceof DegradeException) {
            page = "https://www.baidu.com";
        } else if (e instanceof FlowException) {
            page = "https://www.baidu.com";
        } else if (e instanceof ParamFlowException) {
            page = "https://www.baidu.com";
        } else if (e instanceof SystemBlockException) {
            page = "https://www.baidu.com";
        }
        response.sendRedirect(page);
    }
}

定义页面

/resource/METE-INF/resource/degradeException.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>熔断</title>
</head>
<body>
系统发生熔断。
</body>
</html>

测试

测试方法同上。


  1. https://github.com/alibaba/Sentinel/wiki/熔断降级 ↩︎