sentinel工作过程

微服务与sentinel控制台通信原理

我们只需要在sentinel控制台中设置流控规则,微服务就可以实现流控效果,这是如何做到的呢?下面来看下控制台与微服务之间的通信原理。

注册

微服务中通过依赖sentinel-transport-simple-http向控制台进行注册,它根据配置文件中下面设置找到控制台的地址,使用这里指定的端口号8888与控制台进行交流(默认是8719):

#跟控制台交流的端口,选择一个未占用的即可
spring.cloud.sentinel.transport.port=8888
#sentinel的地址
spring.cloud.sentinel.transport.dashboard=localhost:8080

API调用

当我们在控制台设置流控规则时,它会调用微服务中对应的api将规则传入。在浏览器中打开下面地址可以看到相应的api(注意端口号要跟你设置的一致):http://localhost:8888/api

定义sentinel资源

拦截器

默认情况下sentinel会对微服务的controller进行埋点,会利用拦截器SentinelWebInterceptor对请求进行拦截,请求到达拦截器后会对流控规则进行校验从而达到流控效果。

可以通过下面参数关闭拦截器的埋点,关闭后无法在sentinel控制台中看到资源

spring.cloud.sentinel.filter.enabled=false

代码

关闭拦截器埋点后,我们可以通过代码的方式定义sentinel资源,修改BuyController的buy 方法如下:

    @GetMapping("buy")
    public String buy() {

        Entry entry = null;
        try {
            //buySentinel是资源名
            entry = SphU.entry("buySentinel");
          
            String msg = restTemplate.getForObject("http://" + payUrl + "/pay", String.class);

            return "购买成功:" + msg;
        } catch (BlockException e) {
            e.printStackTrace();
            return "block exception";
        } catch (Exception e) {
            return "exception";
        }finally {
            if (entry != null) {
                //一定要关闭entry,且保证entry与exit配对
                entry.exit();
            }
        }

设置完成之后在sentinel控制台中即可看到资源buySentinel

注解

我们可以通过@SentinelResource定义资源并提供相关的异常处理。

该注解中的部分属性:

  • value:定义资源的名称
  • blockHandler:当满足规则出现异常的时候执行的方法,默认是当前类的方法,若方法在其他类中需要通过blockHandlerClass指定
  • blockHandlerClass:指定blockHandler中调用方法所在的类
  • fallback:程序抛出异常后执行的方法,通常用于业务逻辑方面的异常处理,默认是当前类的方法,若方法在其他类中需要通过fallbackClass指定
  • fallbackClass:指定fallback中调用方法所在的类
/*
	流控后会调用MonkeyBlockHandler类中的resourceHandle方法
*/
@SentinelResource(value = "cancelResource",blockHandlerClass = MonkeyBlockHandler.class,blockHandler = "resourceHandle")
@GetMapping("buy")
public String buy() {
    //通过微服务实例对象获取其地址和端口号进行调用
    String msg = restTemplate.getForObject(payUrl+"/pay", String.class);

    return Thread.currentThread().getName() + "  购买成功:" + msg;
}

创建MonkeyBlockHandler并添加resourceHandle方法(需要是public static方法),该方法的返回值要与controller方法的返回值一致,形参与controller方法一致,最后多一个BlockException。

public class MonkeyBlockHandler {
	
    //需要是static方法
    public static String resourceHandle(BlockException e) {
        System.out.println(e);
        return "流控";
    }
}

部署上面程序并再sentinel中设置规则,当满足规则时就可以看到这些方法返回的信息了。

使用注解中的fallback,在cancel方法中来测试下fallback

@SentinelResource(value = "cancelResource",fallbackClass = MonkeyFallbackHandler.class,fallback = "fallbackHandle")
@GetMapping("cancel")
public String cancel() {
    System.out.println(10 / 0);
    return buyService.refund();
}

创建MonkeyFallbackHandler并添加public static方法fallbackHandle,该方法的返回值要与controller方法的返回值一致,形参与controller方法一致,最后多一个Throwable。

public class MonkeyFallbackHandler {
    public static String fallbackHandle(Throwable t) {
        System.out.println(t);
        return "出现异常";
    }
}

若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 中指定的方法。

sentinel规则管理

在sentinel中规则管理分为如下三种:

  • 原始模式:将规则推送到微服务的内存中。优点是简单,无需依赖,缺点是规则在内存中,重启消失。
  • pull模式:微服务主动向规则管理中心(RDBMS或文件)进行轮询拉取。优点是简单,无需依赖,缺点是不能保证实时性,拉取过于频繁可能会有性能问题。
  • push模式:规则管理中心统一推送,微服务通过监听器时刻监听变化,比如使用nacos,zookeeper,推荐生产环境下使用。优点保证一致性,缺点是要引入第三方依赖。

pull模式

Sentinel 控制台通过 API 将规则推送至微服务的内存中,然后编写的数据源代码会将规则保存到本地文件中。

需要创建InitFunc的实现类来指定规则持久化的文件路径,这里会将规则持久化到一个json文件中。

package com.monkey1024.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.block.flow.param.ParamFlowRuleManager;
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;

//sentinel规则持久化
public class RulePersistence implements InitFunc {
    @Value("spring.application:name")
    private String applicationName;

    @Override
    public void init() throws Exception {
        //持久化文件的保存路径
        String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + applicationName;
        //为不同的流控规则创建相应的持久化文件
        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.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new
                FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        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<>(
                authorityRulePath,
                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);
    }
}

在resource目录下创建 META-INF/services目录,在该目录下创建文件com.alibaba.csp.sentinel.init.InitFunc,在该文件中添加上面类的全限定名即可实现规则持久化。

push模式

push模式可以利用nacos,zookeeper,redis作为远程配置中心,配置中心将规则推送到sentinel客户端。

使用push模式的操作步骤如下:

添加依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

配置文件中添加如下配置

#nacos地址
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=127.0.0.1:8848
#dataid要与nacos中的dataid一致
spring.cloud.sentinel.datasource.ds1.nacos.data-id=${spring.application.name}.json
spring.cloud.sentinel.datasource.ds1.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow

nacos添加配置管理,配置格式使用json,内容填写流控规则,可参考pull模式中生成的json内容

[
  {
    "resource": "/buy",
    "controlBehavior": 0,
    "count": 1.0,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0
  }
]