Spring Cloud Alibaba - Sentinel

Sentinel

简介

image-20210724164148355

image-20210724164838417

image-20210724164938853

image-20210724165012638

image-20210724165415604

image-20210724165737935

image-20210724171428045

在项目配置文件中添加配置

打开控制台dashboard页面需要下载Sentinel-dashboard.jar,并启动该spring boot项目。控制页面默认为:localhost:8080 密码和用户名都为:sentinel

Sentinel控制台调用的AIP:

localhost:8719/api

Dashboard页面所有的信息都是通过这个地址进行获取的

image-20210724190343006


流量控制规则

image-20210724191227505

打开对应的服务面板,点击簇点链路 进入配置页面。选择对应的资源名称,点击流控按钮开始配置。

image-20210724191351433

在面板中直接配置即可

image-20210724191420613

配置完成后会显示配置记录信息:

image-20210724191451850

当访问请求数量超过设置的阈值时,默认返回的信息为:

image-20210724191530728

此信息可以自定义

tips:没有做持久化的Sentinel每次重启都不会保存做过的配置,所有的配置在无持久化的情况下都是保存在内存中的


实现限流自定义返回结果

自定义RestObject,返回结果部分信息存在此对象中:

@Builder  //生成构建器模式
@Data
public class RestObject {
    private int statusCode;
    private String statusMessage;
    private Object data;
}

编写一个类实现BlockExceptionHandler接口:

@Slf4j
@SuppressWarnings("all")
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        log.info("UrlBolckHandler--------------------------");
        RestObject restObject = null;

        //不同异常返回不同提示语
        if (e instanceof FlowException){
            restObject = RestObject.builder().statusCode(100).statusMessage("接口限流了").build();
        }else if (e instanceof DegradeException){
            restObject = RestObject.builder().statusCode(101).statusMessage("服务降级了").build();
        }else if (e instanceof ParamFlowException){
            restObject = RestObject.builder().statusCode(102).statusMessage("热点参数限流了").build();
        }else if (e instanceof SystemBlockException){
            restObject = RestObject.builder().statusCode(103).statusMessage("触发系统保护规则").build();
        }else if (e instanceof AuthorityException){
            restObject = RestObject.builder().statusCode(104).statusMessage("授权规则不通过 ").build();
        }

        httpServletResponse.setStatus(500);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        //Spring MVC 的一个JSON转换类(Jackson)
        new ObjectMapper().writeValue(httpServletResponse.getWriter(),restObject);
    }
}

当超过限流设定的阈值时,返回信息:

image-20210725093256980

如果需要限流以后返回一个页面则需要更改MyBlockExceptionHandler中的部分代码:

@Slf4j
@SuppressWarnings("all")
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        log.info("UrlBolckHandler--------------------------");
        RestObject restObject = null;

        //不同异常返回不同提示语
        if (e instanceof FlowException){
            restObject = RestObject.builder().statusCode(100).statusMessage("接口限流了").build();
        }else if (e instanceof DegradeException){
            restObject = RestObject.builder().statusCode(101).statusMessage("服务降级了").build();
        }else if (e instanceof ParamFlowException){
            restObject = RestObject.builder().statusCode(102).statusMessage("热点参数限流了").build();
        }else if (e instanceof SystemBlockException){
            restObject = RestObject.builder().statusCode(103).statusMessage("触发系统保护规则").build();
        }else if (e instanceof AuthorityException){
            restObject = RestObject.builder().statusCode(104).statusMessage("授权规则不通过 ").build();
        }
        
        httpServletResponse.sendRedirect("http://www.baidu.com");
//进行页面重定向,前后端分离不知道是否可行
    }
}

Sentinel配置中一些参数

image-20210725094457092

image-20210725094708603

关联设定:

image-20210725094728705

链路:

image-20210725095047185

对入口资源监听,超过设定阈值限制本资源的访问。入口资源为所属组名

image-20210725095328339

image-20210725095510794


Sentinel服务降级RT

image-20210725100823880

image-20210725101612601

image-20210725102129461


Sentinel服务降级异常比例和异常数

异常比例:范围为0.0 - 1.0,总请求数量*异常比例的请求数会进入服务降级时间为时间窗口设定的时间

image-20210725102055938

异常数:

image-20210725102203748

image-20210725102342406


Sentinel热点参数规则

image-20210725102526295

image-20210725102759889

通过@SentinelResource注解对方法加入Sentinel监控:

image-20210725105204654

@RestController
@SuppressWarnings("all")
public class SentinelController {
    @Autowired
    private RestTemplate restTemplate;
    
    @GetMapping("/app")
    @SentinelResource(value = "app",fallback = "fallback2",fallbackClass = MyFallbackClass.class)
    public String app(@RequestParam(value = "a",required = false)String a,
                      @RequestParam(value = "b",required = false)String b){
        System.out.println("/app/--->"+a+"---"+b);
        return restTemplate.getForObject("http://nacos-discovery-provider/test",String.class);
    }
}

设定的发生熔断时,调用的类:MyFallbackClass

@Component
public class MyFallbackClass {
    public static String fallback(String a,String b){
        System.out.println("Fall back--->"+a+"---"+b);
        return "Fall back";
    }
    public static String fallback2(String a,String b){
        System.out.println("Fall back2--->"+a+"---"+b);
        return "Fall back2";
    }
}

blockHandler = "block",blockHandlerClass = MyBlockHanlderClass.class处理限流

fallback = "fallback",fallbackClass = MyFallbackClass.class 处理降级

在这里,发生熔断时调用MyFallbackClass中的fallback2方法

设定规则:

image-20210725104738454

超过阈值则显示:

image-20210725104759151

如果设置如下:

image-20210725104913111

则表示,当设定的参数值为5时,它的阈值为2。超过阈值进行限流服务降级。


Sentinel系统保护规则

image-20210725105421822

image-20210725105459482


Sentinel授权规则

image-20210725105957320

白名单:可以调用;黑名单:不能调用

对于黑白名单的标识可以放在参数、header中。

对于下列设置:

image-20210725110755201

则请求为:

localhost:9000/app?origin=order

对于这里的源origin,我们需要实现一个解析类,将其中的源名返回,如果和设定的流程应用名相同,并且设定为黑名单则该请求无法访问。返回:

image-20210725111032467

解析类:

@Component
public class MyRequestOriginParser implements RequestOriginParser {
    //其中获取的字段名字需要和请求中的名字相对应
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        //从参数中获取
        String origin = httpServletRequest.getParameter("origin");
        //从header中获取
        String origin_header = httpServletRequest.getHeader("origin");
        if (StringUtils.isBlank(origin)){
            try {
                throw new IllegalAccessException("origin参数未指定");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return origin;
    }
}

请求中的origin字段也可以变为其他的,但是需要和解析类中的获取的字段一致即可(猜测)。如果使用origin_header,则请求中就不应该再带有origin=order,而是应该将该信息放入到请求header中去。


Sentinel-dashboard控制台通信原理

image-20210725112120322

微服务启动后,暴露很多http接口,Sentinel-Dashboard通过调用这些接口(localhost:8719/api)进行对微服务的操控。


tips: Sentinel的一些配置

image-20210725113020016


Sentinel三种保护应用的方式

image-20210725113629957

image-20210725113807652

image-20210725114243457

这种方式用的比较少:

@GetMapping("/protect")
public String protect(@PathVariable("/app")String app){
    System.out.println("protect: "+app);

    ContextUtil.enter("protect","protect");//origin 服务请求来源,去掉也可以。用来实现授权规则的。
    Entry entry = null;
    try {
        entry = SphU.entry("protect"); //分布式锁,开始保护下面的代码
        //业务逻辑
        int a = 10/0;
        return restTemplate.getForObject("http://nacos-discovery-provider/test",String.class);
    } catch (BlockException e) {
        e.printStackTrace();
        //手动写服务降级代码 (参考MyBlockExceptionHandler.class)
        return "熔断降级了";
    }catch (ArithmeticException e){
        Tracer.trace(e);  //对此类异常进行跟踪(trace),Sentinel才能对异常进行监控
        return "除数不能为0";
    }finally {
        if (entry!=null){
            entry.exit();
        }
        ContextUtil.exit();
    }

}

image-20210725124747950

image-20210725124756717

image-20210725124812538

image-20210725124830265

image-20210725124841222

image-20210725125153919

MyFallbackClass:

@Component
public class MyFallbackClass {
    public static String fallback(String a,String b){
        System.out.println("Fall back--->"+a+"---"+b);
        return "Fall back";
    }
    public static String fallback2(String a,String b){
        System.out.println("Fall back2--->"+a+"---"+b);
        return "Fall back2";
    }
}

image-20210725125940500

也可使用blockHandler+blockHandlerClass,规则与fallback+fallbackClass一致。需要注意的是:在MyBlockHandlerClass和MyFallbackClass中的所有被调用的方法都要定义为Static类型!!!


RestTemplate整合Sentinel

image-20210725130225128

这样Sentinel可以对RestTemplate的调用进行保护(限流,权限......)注意注解中需要的是降级还是限流。

限流-->blockHandler+blockHandlerClass

降级-->fallback+fallbackClass


Feign整合Sentinel

image-20210725132030101

第一种方法-配置fallback:

//name 远程调用服务提供者的名字  fallback 降级处理   configuration feign 配置
@FeignClient(name = "nacos-discovery-provider",
            fallback = EchoServiceFallback.class,
            configuration = FeignConfiguration.class)
public interface EchoService {
    //getMapping 中的定义需要和对应的服务提供者中的接口调用一致
    @GetMapping("/echo-feign")
    public String echo();

}

EchoServiceFallback:

@SuppressWarnings("all")
public class EchoServiceFallback implements EchoService{
    //对EchoService中的同名方法进行服务降级时的操作
    @Override
    public String echo() {
        return "has been low down";
    }
}

第二种方法-配置fallbackFactory:

@FeignClient(name = "nacos-discovery-provider",
//            fallback = EchoServiceFallback.class,
        fallbackFactory = EchoServiceFallbackFactory.class,
            configuration = FeignConfiguration.class)
public interface EchoService {
    //getMapping 中的定义需要和对应的服务提供者中的接口调用一致
    @GetMapping("/echo-feign")
    public String echo();
}

EchoServiceFallbackFactory.class:

@SuppressWarnings("all")
//FallbackFactory<EchoService>中泛型的种类为controller接口中调用的类型
public class EchoServiceFallbackFactory implements FallbackFactory<EchoService> {
    @Override
    public EchoService create(Throwable throwable) {
        return new EchoService() {
            //对其中的所有方法设置降级实现
            @Override
            public String echo() {
                return "from fallbackFactory";
            }
        };
    }

Sentinel持久化

image-20210725152809915

原始模式

image-20210725152844250

拉模式

image-20210725152649252

Pull模式

pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistry 中。以本地文件数据源为例:

public class FileDataSourceInit implements InitFunc {

    @Override
    public void init() throws Exception {
        String flowRulePath = "xxx";

        ReadableDataSource<String, List<FlowRule>> ds = new FileRefreshableDataSource<>(
            flowRulePath, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
        );
        // 将可读数据源注册至 FlowRuleManager.
        FlowRuleManager.register2Property(ds.getProperty());

        WritableDataSource<List<FlowRule>> wds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
        // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
        // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
        WritableDataSourceRegistry.registerFlowDataSource(wds);
    }

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

本地文件数据源会定时轮询文件的变更,读取规则。这样我们既可以在应用本地直接修改文件来更新规则,也可以通过 Sentinel 控制台推送规则。以本地文件数据源为例,推送过程如下图所示:

image-20210725150339652

首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。使用 pull 模式的数据源时一般不需要对 Sentinel 控制台进行改造。

这种实现方法好处是简单,不引入新的依赖,坏处是无法保证监控数据的一致性。

image-20210725153150033

实操:自定义类FileDataSourceInit:

@SuppressWarnings("all")
public class FileDataSourceInit implements InitFunc {
    @Override
    public void init() throws Exception {
        //可以根据需要指定规则文件的存放位置
        String ruleDir = "E:\\software\\JAVA\\springcloud-alibaba\\package\\Sentinel-dashboard";

        String flowRulePath = ruleDir+"/flow-rule.json";
        String degradeRulePath = ruleDir+"/degrade-rule.json";
        String paramFlowRulePath = ruleDir+"/param-flow-rule.json";
        String systemRulePath = ruleDir+"/system-rule.json";
        String authorityRulePath = ruleDir+"/authority-rule.json";

        this.mkdirIfNotExist(ruleDir);
        this.createFileIfNotExist(flowRulePath);
        this.createFileIfNotExist(degradeRulePath);
        this.createFileIfNotExist(paramFlowRulePath);
        this.createFileIfNotExist(systemRulePath);
        this.createFileIfNotExist(authorityRulePath);

        //流控规则:可读数据源
        ReadableDataSource<String, List<FlowRule>> ds = new FileRefreshableDataSource<>(
                flowRulePath, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
        );
        // 将可读数据源注册至 FlowRuleManager.
        FlowRuleManager.register2Property(ds.getProperty());

        WritableDataSource<List<FlowRule>> wds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
        // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
        // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
        WritableDataSourceRegistry.registerFlowDataSource(wds);

        //降级规则,可读数据源
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
                degradeRulePath, source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {})
        );
        // 将可读数据源注册至 FlowRuleManager.
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());

        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(degradeRulePath, this::encodeJson);
        // 将可写数据源注册至 transport 模块的 WritableDataSourceRegistry 中.
        // 这样收到控制台推送的规则时,Sentinel 会先更新到内存,然后将规则写入到文件中.
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);

        //热点参数规则:可读数据源
        ReadableDataSource<String,List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<List<ParamFlowRule>>(
                paramFlowRulePath,source-> JSON.parseObject(source,new TypeReference<List<ParamFlowRule>>(){})
        );
        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
        //热点参数规则:可写数据源
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<List<ParamFlowRule>>(
                paramFlowRulePath,
                this::encodeJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);

        //系统规则:可读数据源
        ReadableDataSource<String,List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<List<SystemRule>>(
                systemRulePath,source-> JSON.parseObject(source,new TypeReference<List<SystemRule>>(){})
        );
        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,source-> JSON.parseObject(source,new TypeReference<List<AuthorityRule>>(){})
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        //授权规则:可写数据源
        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<List<AuthorityRule>>(
                authorityRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
    }

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

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

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

然后在项目的 resources/META-INF/services 目录下创建文件,名为 com.alibaba.csp.sentinel.init.InitFunc ,内容为:

# 改成上面FileDataSourceInit的包名类名全路径即可。
#(copy reference)
com.nacos.consumer.nacosconsumer.sentinel.FileDataSourceInit

优点

  • 简单易懂
  • 没有多余依赖(比如配置中心、缓存等)

缺点

  • 由于规则是用 FileRefreshableDataSource 定时更新的,所以规则更新会有延迟。如果FileRefreshableDataSource定时时间过大,可能长时间延迟;如果FileRefreshableDataSource过小,又会影响性能;
  • 规则存储在本地文件,如果有一天需要迁移微服务,那么需要把规则文件一起迁移,否则规则会丢失。

推模式

image-20210725152708504

image-20210725162157939

我们提供了 ZooKeeper, Apollo, Nacos 等的动态数据源实现。以 ZooKeeper 为例子,如果要使用第三方的配置中心作为配置管理,您需要做下面的几件事情:

  1. 实现一个公共的 ZooKeeper 客户端用于推送规则,在 Sentinel 控制台配置项中需要指定 ZooKeeper 的地址,启动时即创建 ZooKeeper Client。
  2. 我们需要针对每个应用(appName),每种规则设置不同的 path(可随时修改);或者约定大于配置(如 path 的模式统一为 /sentinel_rules/{appName}/{ruleType},e.g. sentinel_rules/appA/flowRule)。
  3. 规则配置页需要进行相应的改造,直接针对应用维度进行规则配置;修改同个应用多个资源的规则时可以批量进行推送,也可以分别推送。Sentinel 控制台将规则缓存在内存中(如 InMemFlowRuleStore),可以对其进行改造使其支持应用维度的规则缓存(key 为 appName),每次添加/修改/删除规则都先更新内存中的规则缓存,然后需要推送的时候从规则缓存中获取全量规则,然后通过上面实现的 Client 将规则推送到 ZooKeeper 即可。
  4. 应用客户端需要注册对应的读数据源以监听变更,可以参考 相关文档

从 Sentinel 1.4.0 开始,Sentinel 控制台提供 DynamicRulePublisherDynamicRuleProvider 接口用于实现应用维度的规则推送和拉取,并提供了相关的示例。Sentinel 提供应用维度规则推送的示例页面(/v2/flow),用户改造控制台对接配置中心后可直接通过 v2 页面推送规则至配置中心。改造详情可参考 应用维度规则推送示例

部署多个控制台实例时,通常需要将规则存至 DB 中,规则变更后同步向配置中心推送规则。

实操:

image-20210725162348700

在组件中有的话就可以不用添加

image-20210725163040545

#基于Nacos配置中心进行规则持久化
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=localhost:80
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

ds1为自定义名字,需要报纸配置信息中的一致

image-20210725163111920

Data id :nacos-discovery-consumer.json 项目名称.json

Group:DEFAULT_GROUP

配置格式:JSON

配置内容:根据上图的格式,自行配置。count = QPS 配置

流程:从Nacos读取配置,缓存到本地。Sentinel 从本地读取配置完成持久化操作。


posted @ 2021-08-11 18:16  会编程的老六  阅读(475)  评论(0编辑  收藏  举报