Sentinel + SpringBoot 基于本地文件模式實(shí)現(xiàn)規(guī)則持久化

Sentinel基本概念在此文章就不進(jìn)行介紹了,沒有了解過(guò)的,可以參考我的其他文章有介紹Sentinel是解決什么問(wèn)題的

首先去Sentinel官網(wǎng)上,將它的源碼進(jìn)行下載,因?yàn)樵谒创a上進(jìn)行修改,當(dāng)然如果覺得源碼不方便,也可以直接下載它官網(wǎng)上的jar包

Sentinel官方地址

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

Sentinel下載地址

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

我現(xiàn)在下載當(dāng)前最新版本1.7.2版本


將源碼下載下來(lái)后,我們向進(jìn)入dashboard模塊(Sentinel控制臺(tái)模塊),配置文件默認(rèn)為properties格式,我這里自定義改成了yml格式,并自定義端口號(hào)為8888,防止8080與其他服務(wù)端口沖突

啟動(dòng)控制臺(tái):localhost:8888
如下界面,可以看到我們可以配置多個(gè)規(guī)則

上面我們的Sentinel Server端基本就改造完成了,接下來(lái)開始配置我們的客戶端
我們先在客戶端添加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>

接下來(lái)添加Sentinel的本地文件持久化代碼,代碼具體啥意思,注釋都有寫到了

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

    @Override
    public void init() throws Exception {
        // TIPS: 持久化在本地的目錄,如果你對(duì)這個(gè)路徑不喜歡,可修改為你喜歡的路徑
        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);

        // 流控規(guī)則
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        // 將可讀數(shù)據(jù)源注冊(cè)至FlowRuleManager
        // 這樣當(dāng)規(guī)則文件發(fā)生變化時(shí),就會(huì)更新規(guī)則到內(nèi)存
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        // 將可寫數(shù)據(jù)源注冊(cè)至transport模塊的WritableDataSourceRegistry中
        // 這樣收到控制臺(tái)推送的規(guī)則時(shí),Sentinel會(huì)先更新到內(nèi)存,然后將規(guī)則寫入到文件中
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

        // 降級(jí)規(guī)則
        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);

        // 系統(tǒng)規(guī)則
        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);

        // 授權(quán)規(guī)則
        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);

        // 熱點(diǎn)參數(shù)規(guī)則
        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);
    }
}

接下我們進(jìn)入resources目錄

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

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

3.在services目錄下,新建初始化文件,文件的內(nèi)容,就是配置文件持久化代碼的路徑地址
com.alibaba.csp.sentinel.init.InitFunc

修改application.yml配置,在配置里面我們需要連接Sentinel控制臺(tái)地址,定時(shí)向控制臺(tái)發(fā)送心跳,判斷服務(wù)是否健康狀態(tài)

spring:
  application:
    name: cluster_user_sentinel
  cloud:
    #sentinel注冊(cè)地址
    sentinel:
      transport:
        dashboard: localhost:8888 #Sentinel 控制臺(tái)地址
        port: 8720 #客戶端監(jiān)控API的端口
      eager: true #取消Sentinel控制臺(tái)懶加載
      log:
        dir: ./logs # 默認(rèn)值${home}/logs/csp/
        switch-pid: true # 日志帶上線程id

然后在我們的在項(xiàng)目中,創(chuàng)建一個(gè)接口,并對(duì)這個(gè)接口實(shí)現(xiàn)降級(jí)等操作

接下來(lái)我們開始啟動(dòng)項(xiàng)目看看效果
如下圖,可以看到我們的服務(wù),已經(jīng)注冊(cè)Sentinel里,控制臺(tái)已經(jīng)能顯示到

現(xiàn)在對(duì)剛剛創(chuàng)建的接口,實(shí)現(xiàn)流控操作

資源名:就是接口 SentinelResource注解
來(lái)源:默認(rèn)default即可
閾值類型:
qps:每秒鐘運(yùn)行N個(gè)請(qǐng)求進(jìn)來(lái)
線程數(shù):每秒鐘N隔線程處理
閾值:自定義的值
集群:因?yàn)槲覀兪菃螜C(jī)的,所以就不用選集群了

請(qǐng)求接口,一秒請(qǐng)求了兩次,現(xiàn)在就已經(jīng)限流了
但是英文返回出去,非常不友好,所以我們可以自定義返回內(nèi)容出去

抽出公共的返回錯(cuò)誤碼

public enum ResponseCode {
    success(200, "請(qǐng)求成功"),
    fail(400, "請(qǐng)求失敗"),
    error(500, "服務(wù)端錯(cuò)誤"),
    serviceFuse(700, "請(qǐng)求熔斷,稍后重試"),
    serviceFlow(701, "請(qǐng)求限流,稍后重試"),
    serviceHotspot(702, "請(qǐng)求熱點(diǎn)參數(shù)限流,稍后重試"),
    serviceSystem(703, "請(qǐng)求觸發(fā)系統(tǒng)保護(hù)規(guī)則,稍后重試"),
    serviceRules(704, "請(qǐng)求Sentinel授權(quán)規(guī)則不通過(guò),稍后重試"),
    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;
    }

    /*錯(cuò)誤碼*/
    private int code = ResponseCode.success.code();
    /*提示信息*/
    private String msg = "";
    /*具體的內(nèi)容*/
    private T data = null;

    public CommonResult success() {
        return this;
    }

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

    public CommonResult success(int code, T t) {
        this.code = code;
        this.msg = "請(qǐng)求成功";
        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 = "請(qǐng)求成功,未查詢到數(shù)據(jù)";
        return this;
    }

    public CommonResult failed(int code, T t) {
        this.code = code;
        this.msg = "請(qǐng)求成功";
        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條數(shù)據(jù)", num);
        return this;
    }

    public CommonResult editSuccess(int num) {
        this.msg = String.format("成功修改%s條數(shù)據(jù)", num);
        return this;
    }

    public CommonResult deleteSuccess(int num) {
        this.msg = String.format("成功刪除%s條數(shù)據(jù)", 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;
    }

    /**
     * 異常類返回結(jié)果處理
     *
     * @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;
    }
}

自定義返回內(nèi)容

@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 = "用戶服務(wù),";
        // 不同的異常返回不同的提示語(yǔ)
        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);
    }
}

再次請(qǐng)求,這次就返回自定義的內(nèi)容了


當(dāng)然,本文的重點(diǎn)是規(guī)則持久化,所以我們可以選擇重啟服務(wù),看規(guī)則是否會(huì)消失

我這里選擇重啟是不會(huì)消失的啦,不然也不會(huì)發(fā)文章哦~

接下來(lái)看到我們指定文件存入的規(guī)則


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


以上基于本地文件模式持久化規(guī)則,到此就結(jié)束了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容