Spring Cloud Alibaba Sentinel(续)

微服务架构➖SpringCloud➖Sentinel(续)

关于作者

  • 作者介绍

🍓 博客主页:作者主页

🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉 🏆、阿里云专家博主51CTO专家博主

🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻


Spring Cloud Alibaba Sentinel

@SentinelResource的使用

在定义了资源点之后,可以通过Dashboard来设置限流和降级策略来对资源点进行保护。同时还能 通过@SentinelResource来指定出现异常时的处理策略。 @SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。其主要参数如下:

value 资源名称
entryType entry类型,标记流量的方向,取值IN/OUT,默认是OUT
blockHandler 处理BlockException的函数名称,函数要求: 1. 必须是 public 2.返回类型 参数与原方法一致 3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。
blockHandlerClass 存放blockHandler的类,对应的处理函数必须static修饰。
fallback 用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所 有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进 行处理。函数要求: 1. 返回类型与原方法一致 2. 参数类型需要和原方法相匹配 3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定fallbackClass里面的方法。
fallbackClass 存放fallback的类。对应的处理函数必须static修饰。
defaultFallback 用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进 行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函 数要求: 1. 返回类型与原方法一致 2. 方法参数列表为空,或者有一个 Throwable 类型的参数。 3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定 fallbackClass 里面的方法。
exceptionsToIgnore 指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入 fallback逻辑,而是原样抛出。
exceptionsToTrace 需要trace的异常

@SentinelResource有value属性,定义资源名,有blockHandler,后面跟函数名,处理sentinel的异常。fallback后面跟函数名。处理所有异常。实例代码:

定义限流和降级后的处理方法

方式一:直接将限流和降级方法定义在方法中

java 复制代码
public class OrderServiceImpl3 {

    int i = 0;

    //定义一个资源
    //定义当资源内部发生异常的时候的处理逻辑
    //blockHandler  定义当资源内部发生了BlockException应该进入的方法[捕获的是Sentinel定义的异常]
    //fallback      定义当资源内部发生了Throwable应该进入的方法
    @SentinelResource(
            value = "message",
            blockHandlerClass = OrderServiceImpl3BlockHandler.class,
            blockHandler = "blockHandler",
            fallbackClass = OrderServiceImpl3Fallback.class,
            fallback = "fallback"
    )
    public String message(String name) {
        i++;
        if (i % 3 == 0) {
            throw new RuntimeException();
        }
        return "message";
    }
    
    //BlockException时进入的方法
    public String blockHandler(BlockException ex) {
        log.error("{}", ex);
        return "接口被限流或者降级了...";
    }
    
    //Throwable时进入的方法
    public String fallback(Throwable throwable) {
        log.error("{}", throwable);
        return "接口发生异常了...";
    }
}

方式二: 将限流和降级方法外置到单独的类中

java 复制代码
@Service
@Slf4j
public class OrderServiceImpl3 {

    int i = 0;

    //定义一个资源
    //定义当资源内部发生异常的时候的处理逻辑
    //blockHandler  定义当资源内部发生了BlockException应该进入的方法[捕获的是Sentinel定义的异常]
    //fallback      定义当资源内部发生了Throwable应该进入的方法
    @SentinelResource(
            value = "message",
            blockHandlerClass = OrderServiceImpl3BlockHandler.class,
            blockHandler = "blockHandler",
            fallbackClass = OrderServiceImpl3Fallback.class,
            fallback = "fallback"
    )
    public String message(String name) {
        i++;
        if (i % 3 == 0) {
            throw new RuntimeException();
        }
        return "message";
    }
}



//OrderServiceImpl3对应的BlockException处理的类
@Slf4j
public class OrderServiceImpl3BlockHandler {
    //blockHandler
    //要求:
    //1 当前方法的返回值和参数要跟原方法一致
    //2 但是允许在参数列表的最后加入一个参数BlockException, 用来接收原方法中发生的异常
    public static String blockHandler(String name, BlockException e) {
        //自定义异常处理逻辑
        log.error("触发了BlockException,内容为{}", e);
        return "BlockException";
    }

}

@Slf4j
public class OrderServiceImpl3Fallback {

    //fallback
    //要求:
    //1 当前方法的返回值和参数要跟原方法一致
    //2 但是允许在参数列表的最后加入一个参数BlockException, 用来接收原方法中发生的异常
    public static String fallback(String name, Throwable e) {
        //自定义异常处理逻辑
        log.error("触发了Throwable,内容为{}", e);
        return "Throwable";
    }
}

blockHandlerClass可以指定一个类,与blockHandler拼接就是哪一个类的哪一个函数。函数必须是static,参数必须是和原请求参数一致,在最后可以加一个参数,Exception。fallBack同理也有个fallBackClass......

Sentinel规则持久化

通过前面的讲解,我们已经知道,可以通过Dashboard来为每个Sentinel客户端设置各种各样的规 则,但是这里有一个问题,就是这些规则默认是存放在内存中,极不稳定,所以需要将其持久化。

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

首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的 规则保存到本地的文件中。

1.编写处理类FilePersistence.java

java 复制代码
package com.itheima.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;

public class FilePersistence implements InitFunc {

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

    @Override
    public void init() throws Exception {
        String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + appcationName;
        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);
    }
}

2.添加配置 在resources下创建配置目录 META-INF/services ,然后添加文件 com.alibaba.csp.sentinel.init.InitFunc 在文件中添加配置类的全路径 com.itheima.config.FilePersistence

Feign整合Sentinel

fallBack容错类

①在配置文件中开启Feign对Sentinel的支持

yml 复制代码
feign:
	sentinel:
		enabled: true

②创建容错类,容错类,需要实现feign所在接口,并实现接口中所有方法,一旦远程调用出错,即进入容错类的同名方法。实例代码:

java 复制代码
//容错类,需要实现feign所在接口,并实现接口中所有方法
//一旦远程调用出错,即进入容错类的同名方法
@Service
public class ProductServiceFallback implements ProductService {<!-- -->
    @Override
    public Product findByPid(Integer pid) {<!-- -->
        //容错逻辑
        Product p = new Product();
        p.setPid(-100);
        return p;
    }

    @Override
    public String reduceInventory(Integer pid, Integer number) {<!-- -->
        return "error";
    }
}

③为被容器的接口指定容错类,@FeignClient注解的value值是调用的服务名,fallBack值是容错类的类名.class。 实例:

java 复制代码
@FeignClient(value = "service-product", fallback = ProductServiceFallBack.class)

fallBack有个缺陷,就是远程调用失败转容错类的时候不会把远程调用的异常信息抛出来。

fallBackFactory容错类

容错类实现FallbackFactory<>接口范型里面是feign的接口类,需要重写方法create,返回值就是feign的接口类。用匿名内部类,或者Lambda表达式的方式创建新的接口类,并实现。实例代码如下:

java 复制代码
@Slf4j
@Service
public class ProductServiceFallbackFactory implements FallbackFactory&lt;ProductService&gt; {<!-- -->

    @Override
    public ProductService create(Throwable throwable) {<!-- -->

        return new ProductService() {<!-- -->
            @Override
            public Product findByPid(Integer pid) {<!-- -->
                log.error("{}",throwable);
                //容错逻辑
                Product p = new Product();
                p.setPid(-100);
                return p;
            }

            @Override
            public String reduceInventory(Integer pid, Integer number) {<!-- -->
                return "error";
            }
        };
    }
}

@FeignClient注解中fallBackFactory值指定上面创建容错类的类名.class

相关推荐
一只叫煤球的猫4 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9655 小时前
tcp/ip 中的多路复用
后端
bobz9655 小时前
tls ingress 简单记录
后端
皮皮林5516 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友6 小时前
什么是OpenSSL
后端·安全·程序员
bobz9656 小时前
mcp 直接操作浏览器
后端
前端小张同学9 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook9 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康9 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在10 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net