Sentinel的降级和热点规则

Java技术迷

共 21057字,需浏览 43分钟

 ·

2022-11-21 10:54

点击关注公众号,Java干货及时送达

作者 | 汪伟俊 

出品 | Java技术迷(ID:JavaFans1024)

降级规则

降级规则就是设置当满足什么条件时,对服务进行降级处理,同样点击左侧菜单的簇点链路,然后点击对应资源的降级按钮:

img

来看一看降级规则有哪些需要设置的内容:

img

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

比如这样的配置:

img

它表示统计时长,也就是1秒内请求数大于1,且慢调用比例大于0.5(这里只要请求的响应时间大于5毫秒则认为是慢调用),若是这两个条件都满足,则熔断降级5秒,5秒过后,探测器会进入探测状态,若接下来的一个请求响应时间小于5毫秒则结束熔断,若大于5毫秒,则再次熔断。

需要注意,Sentinel默认统计的RT上限是4900毫秒,超出此阈值的数据都会被认为是4900毫秒,若确实需要设置比上限还大的RT,则可以通过启动Sentinel时携带参数来实现:

-Dcsp.sentinel.statistic.max.rt=xxx

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

比如这样的配置:

img

它表示的是在1秒内若是请求数大于1,并且异常比例大于25%,也就是说4次请求中有多于1次的请求抛出了异常,则这个异常比例就大于25%了,此时请求会被熔断5秒,5秒后探测器进入探测状态,若是接下来的一个请求成功完成则结束熔断,否则再次熔断。

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

比如:

img

它表示1秒内请求数大于1,并且异常数也大于1,则熔断5秒,5秒后探测器进入探测状态,若接下来的一个请求成功完成,则结束熔断,否则再次熔断。

热点规则

热点规则是一种更细粒度的流控规则,它允许将规则具体到参数上。

编写代码:

@RequestMapping("/order/message3")
@SentinelResource("message3")
public String message3(String name, Integer age) {
    return "message3:" + name + "," + age;
}

现有一个控制方法接收两个参数,我们使用@SentinelResource注解将其声明为一个资源,然后在Sentinel控制台设置一下热点规则:

img

一定要注意应该设置在哪个资源上,因为注解值为message3,所以应该对message3资源进行设置:

img

参数索引指的是方法中的参数,0为第一个参数,1为第二个参数,对索引为0的参数进行热点规则设置,此时携带该参数的请求在1秒内若是多于3个,则直接控制。

img

在底下还能够设置参数例外项,这里的意思是设置携带参数age的请求在1秒内不能多于3个,否则直接控制;但若是age值为20,则限流阈值会被指定为1000,即:1秒内不能多于1000个请求。

授权规则

很多时候,我们需要根据调用来源判断该次请求是否允许放行,这时候可以使用Sentinel的来源访问控制的功能,来源访问控制根据资源的请求来源限制资源是否能够通过。

授权规则是需要通过一个标识来匹配来源是否合法的,所以我们需要编写代码:

@Component
public class RequestOriginParserDefinition implements RequestOriginParser {

    /**
     * 定义区分来源
     *
     * @param request
     * @return
     */

    @Override
    public String parseOrigin(HttpServletRequest request) {
        String serviceName = request.getParameter("serviceName");
        if (StringUtils.isEmpty(serviceName)) {
            throw new RuntimeException("serviceName is not empty");
        }
        return serviceName;
    }
}

该方法将会从请求域中获取一个serviceName属性值,并将它返回给Sentinel进行匹配,所以我们要在Sentinel中进行授权规则的设置:

img

授权规则采用两种形式,白名单和黑名单,若是白名单,则除了当前配置的应用外,其它应用都将被控制;若是黑名单,则相反。

这里我们设置流控应用为A,类型为白名单,则携带serviceName=A属性的请求不会被限制,除此之外的请求都将被控制:

自定义异常返回页面

在前面的各个规则中,我们发现当资源被控制后,显示的错误页面都是一致的,这样我们很难区分究竟是什么问题,对于用户这样的页面也不友好,为此,我们需要自定义异常的返回页面,方式如下:

@Component
public class ExceptionHandlerPage implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        response.setContentType("application/json;charset=utf-8");
        ResponseData responseData = null;
        if (e instanceof FlowException) {
            // 如果是限流异常
            responseData = new ResponseData(-1"限流异常...");
        } else if (e instanceof DegradeException) {
            // 如果是降级异常
            responseData = new ResponseData(-2"降级异常...");
        } else if (e instanceof ParamFlowException) {
            // 如果是参数限流异常
            responseData = new ResponseData(-3"参数限流异常");
        } else if (e instanceof AuthorityException) {
            responseData = new ResponseData(-4"授权异常");
        } else if (e instanceof SystemBlockException) {
            responseData = new ResponseData(-5"系统负载异常");
        }
        response.getWriter().write(JSON.toJSONString(responseData));
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class ResponseData {
    private int code;
    private String message;
}

可以对每个异常进行定制化处理。

@SentinelResource

前面我们已经使用到了@SentinelResource注解,它的作用是声明一个资源,但它的作用远不止如此,它可以定义当资源内部发生异常时的处理逻辑:

@Service
@Slf4j
public class OrderMessageService {

    @SentinelResource(value = "message", blockHandler = "blockHandler", fallback = "fallback")
    public String message(String name) {
        return "message";
    }

    public String blockHandler(String name, BlockException e) {
        log.error("BlockException...{}", e);
        return "BlockException";
    }

    public String fallback(String name, Throwable t) {
        log.error("Throwable...{}", t);
        return "Throwable";
    }
}

@SentineResource注解能够指定两个参数用来处理出异常后的逻辑,blockHandler和fallback。

其中blockHandler捕获的是BlockException,该异常是流控异常、降级异常、热点异常等五个异常的父类,也就是说,Sentinel抛出的异常都会被它捕获,该属性需要指定一个处理方法,该方法的声明也有讲究,返回值和参数必须和原资源方法的声明一致,但可以再多声明一个BlockException参数用于获取异常信息。

而fallback的作用与blockHandler类似,但它的作用域更广,对于资源产生的任何异常,都会被fallback捕获到并进行处理,现在我们对/order/message资源进行流控,则发生流控异常后便会进入我们自定义的处理方法:

img

若是同时定义这两个属性,则对于Sentinel抛出的异常,blockHandler会优先处理。

若是不想让异常处理方法和业务写在一起,我们也可以使用另外一个属性blockHandlerClass将异常处理抽取到外面去:

@SentinelResource(
    value = "message",
    blockHandlerClass = OrderMessageBlockHandler.class,
    blockHandler 
"blockHandler",
    fallback = "fallback")
public String message(String name) {
    return "message";
}

此时在外部定义OrderMessageBlockHandler类:

@Slf4j
public class OrderMessageBlockHandler {

    public static String blockHandler(String name, BlockException e) {
        log.error("BlockException...{}", e);
        return "BlockException";
    }
}

要注意此时的异常处理方法必须声明为静态的,fallback也可以如此方式进行处理。

Sentinel持久化

到这里关于Sentinel的各种规则就介绍完了,但是我们仍然有一个问题没有解决,就是每次设置的规则在应用启动后就失效了,需要重新设置,这是因为Sentinel默认将规则设置保存在了内存中,所以要是想将规则保存下来,我们就需要对其进行持久化。

编写一个类:

package com.wwj.config;

import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler;
import com.alibaba.csp.sentinel.datasource.*;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.beans.factory.annotation.Value;
import java.io.File;
import java.io.IOException;
import java.util.List;

public class FilePersistence implements InitFunc {

    @Value("spring.application.name")
    private String applicationName;

    @Override
    public void init() throws Exception {
        String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + applicationName;
        System.out.println(ruleDir);
        String flowRulePath = ruleDir + "/flow-rule.json";
        String degradeRulePath = ruleDir + "degrade-rule.json";
        String systemRulePath = ruleDir + "system-rule.json";
        String authorityRulePath = ruleDir + "authority-rule.json";
        String paramFlowRulePath = ruleDir + "param-flow-rule.json";

        this.mkdirIfNotExists(ruleDir);
        this.createFileIfNotExists(flowRulePath);
        this.createFileIfNotExists(degradeRulePath);
        this.createFileIfNotExists(systemRulePath);
        this.createFileIfNotExists(authorityRulePath);
        this.createFileIfNotExists(paramFlowRulePath);

        // 流控规则
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<List<FlowRule>>(
                flowRulePath,
                flowRuleListParser
        );
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<List<FlowRule>>(
                flowRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

        // 降级规则
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<List<DegradeRule>>(
                degradeRulePath,
                degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<List<DegradeRule>>(
                degradeRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);

        // 系统规则
        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
                systemRulePath,
                systemRuleListParser
        );
        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
        WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<List<SystemRule>>(
                systemRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);

        // 授权规则
        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<List<AuthorityRule>>(
                authorityRulePath,
                authorityRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<List<AuthorityRule>>(
                authorityRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);

        // 热点规则
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<List<ParamFlowRule>>(
                paramFlowRulePath,
                paramFlowRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<List<ParamFlowRule>>(
                paramFlowRulePath,
                this::encodeJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
    }

    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<FlowRule>>() {

            }
    );

    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<DegradeRule>>() {

            }
    );

    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<SystemRule>>() {

            }
    );

    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<AuthorityRule>>() {

            }
    );

    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<ParamFlowRule>>() {

            }
    );

    private void mkdirIfNotExists(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    private void createFileIfNotExists(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }

    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }
}

然后需要在resource目录下创建一个文件夹,名字必须为 META-INF.services ,并在该文件夹下新建一个文件,名字必须为 com.alibaba.csp.sentinel.init.initFunc ,在文件中写上持久化类的全类名:

com.wwj.config.FilePersistence

最后重新启动服务,然后设置一个流控规则,该规则就会被持久化到本地磁盘上:

img

   

1、全中国一共有多少IP地址?

2、Xcode弃用Bitcode,导致应用体积大幅增加

3、中年人,疯狂进国企

4、浏览器的最大骗局?你深夜访问的小网站,其实大家都知道

5、全网都在说一个错误的结论

点在看

浏览 79
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报