Sentinel + SpringBoot 基于本地文件模式实现规则持久化

时间:2022-07-24
本文章向大家介绍Sentinel + SpringBoot 基于本地文件模式实现规则持久化,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Sentinel基本概念在此文章就不进行介绍了,没有了解过的,可以参考我的其他文章有介绍Sentinel是解决什么问题的

首先去Sentinel官网上,将它的源码进行下载,因为在它源码上进行修改,当然如果觉得源码不方便,也可以直接下载它官网上的jar包

Sentinel官方地址

https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

Sentinel下载地址

https://github.com/alibaba/Sentinel/releases

我现在下载当前最新版本1.7.2版本

将源码下载下来后,我们向进入dashboard模块(Sentinel控制台模块),配置文件默认为properties格式,我这里自定义改成了yml格式,并自定义端口号为8888,防止8080与其他服务端口冲突

启动控制台:localhost:8888 如下界面,可以看到我们可以配置多个规则

上面我们的Sentinel Server端基本就改造完成了,接下来开始配置我们的客户端 我们先在客户端添加Sentinel依赖

<!--springboot 整合 sentinel框架-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel</artifactId>
    <version>0.9.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

接下来添加Sentinel的本地文件持久化代码,代码具体啥意思,注释都有写到了

/**
 * @Author:
 * @Date: 2020/3/26 22:00
 * @Description: klm
 */
public class DataSourceInitFunc implements InitFunc {

    @Override
    public void init() throws Exception {
        // TIPS: 持久化在本地的目录,如果你对这个路径不喜欢,可修改为你喜欢的路径
        String ruleDir = System.getProperty("user.home") + "/sentinel/order/rules";
        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.mkdirIfNotExits(ruleDir);
        this.createFileIfNotExits(flowRulePath);
        this.createFileIfNotExits(degradeRulePath);
        this.createFileIfNotExits(systemRulePath);
        this.createFileIfNotExits(authorityRulePath);
        this.createFileIfNotExits(paramFlowRulePath);

        // 流控规则
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        // 将可读数据源注册至FlowRuleManager
        // 这样当规则文件发生变化时,就会更新规则到内存
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        // 将可写数据源注册至transport模块的WritableDataSourceRegistry中
        // 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

        // 降级规则
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
                degradeRulePath,
                degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
                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<>(
                systemRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);

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

        // 热点参数规则
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
                paramFlowRulePath,
                paramFlowRuleListParser
        );
        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
                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 mkdirIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

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

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

接下我们进入resources目录

1.新建META-INF目录
META-INF

2.然后在META-INF目录新建services目录
services

3.在services目录下,新建初始化文件,文件的内容,就是配置文件持久化代码的路径地址
com.alibaba.csp.sentinel.init.InitFunc

修改application.yml配置,在配置里面我们需要连接Sentinel控制台地址,定时向控制台发送心跳,判断服务是否健康状态

spring:
  application:
    name: cluster_user_sentinel
  cloud:
    #sentinel注册地址
    sentinel:
      transport:
        dashboard: localhost:8888 #Sentinel 控制台地址
        port: 8720 #客户端监控API的端口
      eager: true #取消Sentinel控制台懒加载
      log:
        dir: ./logs # 默认值${home}/logs/csp/
        switch-pid: true # 日志带上线程id

然后在我们的在项目中,创建一个接口,并对这个接口实现降级等操作

接下来我们开始启动项目看看效果 如下图,可以看到我们的服务,已经注册Sentinel里,控制台已经能显示到

现在对刚刚创建的接口,实现流控操作

资源名:就是接口 SentinelResource注解 来源:默认default即可 阈值类型: qps:每秒钟运行N个请求进来 线程数:每秒钟N隔线程处理 阈值:自定义的值 集群:因为我们是单机的,所以就不用选集群了

请求接口,一秒请求了两次,现在就已经限流了 但是英文返回出去,非常不友好,所以我们可以自定义返回内容出去

抽出公共的返回错误码

public enum ResponseCode {
    success(200, "请求成功"),
    fail(400, "请求失败"),
    error(500, "服务端错误"),
    serviceFuse(700, "请求熔断,稍后重试"),
    serviceFlow(701, "请求限流,稍后重试"),
    serviceHotspot(702, "请求热点参数限流,稍后重试"),
    serviceSystem(703, "请求触发系统保护规则,稍后重试"),
    serviceRules(704, "请求Sentinel授权规则不通过,稍后重试"),
    unkown(999, "未知类型"),
    ;

    private int code;
    private String desc;

    ResponseCode(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public static ResponseCode getByCode(int code) {
        for (ResponseCode responseCode : ResponseCode.values()) {
            if (responseCode.code() == (code)) {
                return responseCode;
            }
        }
        return unkown;
    }

    public int code() {
        return this.code;
    }

    public String desc() {
        return this.desc;
    }

    public int getCode() {
        return code;
    }

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

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}


################################################

public class CommonResult<T> implements Serializable {

    public CommonResult() {
    }

    public CommonResult(T t) {
        this(ResponseCode.success.code(), null, t);
    }

    public CommonResult(int code, T t) {
        this(code, null, t);
    }

    public CommonResult(int code, String msg, T t) {
        this.code = code;
        this.msg = msg;
        this.data = t;
    }

    /*错误码*/
    private int code = ResponseCode.success.code();
    /*提示信息*/
    private String msg = "";
    /*具体的内容*/
    private T data = null;

    public CommonResult success() {
        return this;
    }

    public CommonResult success(T t) {
        this.msg = "请求成功";
        this.data = t;
        return this;
    }

    public CommonResult success(int code, T t) {
        this.code = code;
        this.msg = "请求成功";
        this.data = t;
        return this;
    }

    public CommonResult success(int code, String msg, T t) {
        this.code = code;
        this.msg = msg;
        this.data = t;
        return this;
    }

    public CommonResult failed() {
        this.code = ResponseCode.error.code();
        this.msg = "请求成功,未查询到数据";
        return this;
    }

    public CommonResult failed(int code, T t) {
        this.code = code;
        this.msg = "请求成功";
        return this;
    }

    public CommonResult failed(int code, String msg, T t) {
        this.code = code;
        this.msg = msg;
        this.data = t;
        return this;
    }

    public CommonResult failed(String msg) {
        this.code = ResponseCode.error.code();
        this.msg = msg;
        return this;
    }

    public CommonResult saveSuccess(int num) {
        this.msg = String.format("成功保存%s条数据", num);
        return this;
    }

    public CommonResult editSuccess(int num) {
        this.msg = String.format("成功修改%s条数据", num);
        return this;
    }

    public CommonResult deleteSuccess(int num) {
        this.msg = String.format("成功删除%s条数据", num);
        return this;
    }

    public static CommonResult error(String msg) {
        return new CommonResult(ResponseCode.error.code(), msg, null);
    }

    public static CommonResult build(ResponseCode responseCode) {
        return new CommonResult(responseCode.code(), responseCode.desc(), null);
    }

    public CommonResult buildByResponseCode(ResponseCode responseCode) {
        this.code = responseCode.getCode();
        this.msg = responseCode.getDesc();
        return this;
    }

    /**
     * 异常类返回结果处理
     *
     * @param msg
     * @param exception
     * @return
     */
    public static CommonResult error(String msg, String exception) {
        return new CommonResult(ResponseCode.error.code(), msg, exception);
    }

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

    public int getCode() {
        return code;
    }

    public void setCode(int 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;
    }
}

自定义返回内容

@Slf4j
@Component
public class SentinelUrlBlockHandler implements UrlBlockHandler {
    @Override
    public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {
        CommonResult<String> errorResponse = null;
        String serverName = "用户服务,";
        // 不同的异常返回不同的提示语
        if (e instanceof FlowException) {
            errorResponse = new CommonResult<>().failed(ResponseCode.serviceFlow.getCode(), serverName + ResponseCode.serviceFlow.getDesc(), null);
        } else if (e instanceof DegradeException) {
            errorResponse = new CommonResult<>().failed(ResponseCode.serviceFuse.getCode(), serverName + ResponseCode.serviceFuse.getDesc(), null);
        } else if (e instanceof ParamFlowException) {
            errorResponse = new CommonResult<>().failed(ResponseCode.serviceHotspot.getCode(), serverName + ResponseCode.serviceHotspot.getDesc(), null);
        } else if (e instanceof SystemBlockException) {
            errorResponse = new CommonResult<>().failed(ResponseCode.serviceSystem.getCode(), serverName + ResponseCode.serviceSystem.getDesc(), null);
        } else if (e instanceof AuthorityException) {
            errorResponse = new CommonResult<>().failed(ResponseCode.serviceRules.getCode(), serverName + ResponseCode.serviceRules.getDesc(), null);
        }

        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        new ObjectMapper().writeValue(response.getWriter(), errorResponse);
    }
}

再次请求,这次就返回自定义的内容了

当然,本文的重点是规则持久化,所以我们可以选择重启服务,看规则是否会消失

我这里选择重启是不会消失的啦,不然也不会发文章哦~

接下来看到我们指定文件存入的规则

如果有伙伴不知道路径在哪里,可如下图找即可~

以上基于本地文件模式持久化规则,到此就结束了。