Spring Cloud系列—Alibaba Sentinel授权与规则管理及推送

上篇文章:

Spring Cloud系列---Alibaba Sentinel熔断降级https://blog.csdn.net/sniper_fandc/article/details/149945571?fromshare=blogdetail&sharetype=blogdetail&sharerId=149945571&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目录

[1 授权](#1 授权)

[1.1 服务端(被调用方)获取来源](#1.1 服务端(被调用方)获取来源)

[1.2 客户端(调用方)设置来源](#1.2 客户端(调用方)设置来源)

[1.3 配置授权规则](#1.3 配置授权规则)

[2 自定义异常返回结果](#2 自定义异常返回结果)

[3 Sentinel规则管理及推送](#3 Sentinel规则管理及推送)

[3.1 原始模式](#3.1 原始模式)

[3.2 Pull模式](#3.2 Pull模式)

[3.3 Push模式](#3.3 Push模式)

[3.3.1 基于Nacos实现Push模式](#3.3.1 基于Nacos实现Push模式)

[3.3.2 Sentinel集成Nacos持久化](#3.3.2 Sentinel集成Nacos持久化)


1 授权

目前的微服务架构中,许多服务是可以通过ip+端口号直接访问的,这样就会导致系统的不安全,比如订单服务可能包含用户的个人隐私,如果可以随便访问订单服务就会导致数据泄露。

因此可以通过Sentinel添加授权规则,调用方设置请求来源,被调用方获取请求来源,然后通过Sentinel判断授权类型:黑名单和白名单,根据授权规则决定是否允许请求通过。

****白名单:****请求来源位于白名单内的调用者才允许调用被调用者的接口。

****黑名单:****请求来源位于黑名单内的调用者不允许调用被调用者的接口,其它允许。

现有Gateway调用order-service这样的调用链,Gateway网关相当于调用方,order-service相当于被调用方,实现授权就需要网关来设置请求头的来源,order-service获取请求头来源来判断是否在黑白名单内:

1.1 服务端(被调用方)获取来源

Sentinel通过RequestOriginParser的parseOrigin()方法获取请求头,并获取请求的来源,因此调用方可以获取约定好的请求头来判断来源:

java 复制代码
@Component

public class HeaderOriginParser implements RequestOriginParser {

    @Override

    public String parseOrigin(HttpServletRequest httpServletRequest) {

        // 1.获取请求头

        String origin = httpServletRequest.getHeader("origin");

        // 2.非空判断

        if (!StringUtils.hasLength(origin)) {

            origin = "default";

        }

        return origin;

    }

}

1.2 客户端(调用方)设置来源

调用方是网关,设置来源可以通过过滤器,在请求头添加key:value=origin:gateway:

bash 复制代码
spring:

  cloud:

    gateway:

      default-filters:

        - AddRequestHeader=origin,gateway

1.3 配置授权规则

资源名是指要进行权限判断的接口,即服务端(被调用方)。流控应用可以填写多个(用英文,分割),指客户端(调用方)。授权类型就是把调用方添加到白名单或者黑名单:

如果直接访问8080端口的order-service服务,就会失败:

如果通过gateway访问,由于gateway是白名单,因此权限判断通过,可以成功访问:

2 自定义异常返回结果

上面发现,无论是限流还是授权未通过,返回结果都是Blocked by Sentinel。这是由于Sentinel默认实现了BlockExceptionHandle接口:

java 复制代码
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {

    public DefaultBlockExceptionHandler() {

    }

    public void handle(HttpServletRequest request, HttpServletResponse

        response, BlockException e) throws Exception {

        response.setStatus(429);

        PrintWriter out = response.getWriter();

        out.print("Blocked by Sentinel (flow limiting)");

        out.flush();

        out.close();

    }

}

BlockException有5种实现子类,分别对应授权异常、服务降级异常、限流异常、热点参数限流异常、系统阻塞异常:

DefaultBlockExceptionHandler接口将所有类型的异常都定义返回消息Blocked by Sentinel (flow limiting),这不利于我们观察到异常的发生原因,因此需要自己实现BlockExceptionHandle接口来自定义异常返回结果:

java 复制代码
@Component

public class SentinelExceptionHandler implements BlockExceptionHandler {

    @Override

    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {

        response.setContentType("text/html; charset=utf-8");

        String msg = "Blocked by Sentinel (flow limiting)";

        int status = 429;

        PrintWriter out = response.getWriter();

        if (e instanceof FlowException) { //限流异常

            msg = "触发限流, 请联系服务方进行调整";

        } else if (e instanceof DegradeException) { //降级异常

            msg = "触发降级异常";

        } else if (e instanceof AuthorityException) { //授权异常

            status = 401;

            msg = "没有权限访问, 请联系服务方进行调整";

        }

        response.setStatus(status);

        out.print(msg);

        out.flush();

        out.close();

    }

}

分别设置流控规则、授权规则、熔断规则,观察异常返回结果:

3 Sentinel 规则管理及推送

Sentinel想要在生产环境中使用,还缺少一些重要的特性。目前Sentinel仅提供规则存储在内存中,一旦服务重启就得重新配置规则,因此需要对规则进行持久化,这就是规则管理及推送。

共分为3种模式,Sentinel默认采用原始模式:

3.1 原始模式

如果不做任何修改,Dashboard的推送规则方式是通过API将规则推送至客户端并直接更新到内存中,这里的客户端即微服务系统:

3.2 Pull模式

Pull模式是将规则持久化到一种数据源上(如本地文件、RDBMS、配置中心(Consul、Eureka)等),该数据源要求是可读/写的,然后客户端定期轮询从数据源拉取规则。使用时需要在客户端(微服务)注册数据源:将对应的读数据源注册至对应的RuleManager,将写数据源注册至transport的WritableDataSourceRegistry中。以本地文件数据源为例:

首先需要注册数据源:

java 复制代码
public class FileDataSourceInit implements InitFunc {

    @Override

    public void init() throws Exception {

        String ruleDir = "D:/javaee_study/springcloud_sentinel/sentinel/rules/orderService";

        String flowRulePath = ruleDir + "/flow-rule.json";

        mkdirIfNotExist(ruleDir);

        createFileIfNotExist(flowRulePath);

        // 注册一个可读数据源,用来定时读取本地的json文件,更新到规则内存中

        // 流控规则

        ReadableDataSource<String, List<FlowRule>> ds = new

                FileRefreshableDataSource<>(

                flowRulePath, source -> JSON.parseObject(

                source, new TypeReference<List<FlowRule>>() {

                })

        );

        // 将可读数据源注册至FlowRuleManager

        // 这样当规则文件发生变化时,就会更新规则到内存

        FlowRuleManager.register2Property(ds.getProperty());

        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(

                flowRulePath,

                this::encodeJson

        );

        // 将可写数据源注册至transport模块的WritableDataSourceRegistry中

        // 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中

        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

    }

    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,文件内容是注册数据源的类,如下:

配置完成后重启服务,设置流控规则,流控规则会被持久化到配置的本地数据源:

文件内容如下:

{"clusterConfig":{"acquireRefuseStrategy":0,"clientOfflineTime":2000,"fallbackToLocalWhenFail":true,"resourceTimeout":2000,"resourceTimeoutStrategy":0,"sampleCount":10,"strategy":0,"thresholdType":0,"windowIntervalMs":1000},"clusterMode":false,"controlBehavior":0,"count":5.0,"grade":1,"limitApp":"default","maxQueueingTimeMs":500,"resource":"/order/{orderId}","strategy":0,"warmUpPeriodSec":10}

重启服务,流控规则不会消失说明成功持久化到本地。

3.3 Push模式

Pull模式是Sentinel客户端(微服务)从数据源拉取规则;而Push模式是数据源推送规则给Sentinel客户端(客户端监听数据源)。此时这里的数据源不再是本地文件等方式,因为本地文件并不能推送给服务。

注意:规则管理及推送的Pull模式或Push模式实现本质还是看数据源支持的是推模式还是拉模式,如果数据源支持推模式,比如Nacos配置中心、ZooKeeper,那么基于这些配置中心实现的就是Push模式。如果数据源支持拉模式,比如Eureka或本地文件(只能是服务拉取本地文件,本地文件系统压根无权访问服务端),那实现的就是Pull模式。

而Push模式的数据源通常是基于远程配置中心的,比如Nacos、ZooKeeper等等。配置中心的数据源将变化的规则及时推送给客户端,即推送规则的路径变为Sentinel Dashboard/Config Center Dashboard => Config Center => Sentinel数据源 => Sentinel客户端:

3.3.1 基于Nacos实现Push模式

首先需要引入Sentinel关于Nacos的依赖:

XML 复制代码
        <dependency>

            <groupId>com.alibaba.csp</groupId>

            <artifactId>sentinel-datasource-nacos</artifactId>

        </dependency>

然后把Nacos作为数据源:

java 复制代码
public class NacosDataSourceInit implements InitFunc {

    // nacos server ip

    private static final String remoteAddress = "192.168.141.150:8848";

    // nacos group

    private static final String groupId = "SENTINEL_GROUP";

    // nacos dataId

    private static final String dataId = "orderservice-flow-rules";

    @Override

    public void init() throws Exception {

        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new

                NacosDataSource<>(remoteAddress, groupId, dataId,

                source -> JSON.parseObject(source, new

                        TypeReference<List<FlowRule>>() {

                        }));

        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());

    }

}

接着类似Pull模式,也需要配置数据源的类的读取路径:

最后,在Nacos Dashboard也就是Nacos的控制台页面配置规则,比如流控规则(JSON格式可以从Pull模式的本地数据源文件中复制):

重启服务,观察Sentinel Dashboard:

这说明基于Nacos的数据源配置成功,Nacos配置中心可以把规则推送给Sentinel客户端(微服务)和Sentinel Dashboard。

但是反之,如果在Sentinel Dashboard修改流控规则,Nacos配置中心的规则无法同步,这需要开启Sentinel集成Nacos持久化,从而让Sentinel Dashboard和Nacos的通信是双向的。

3.3.2 Sentinel集成Nacos持久化

Sentinel想要集成Nacos实现持久化(Sentinel Dashboard和Nacos双向通信)需要修改Sentinel的源码。具体流程如下:

首先先从官网https://github.com/alibaba/Sentinel/releases下载Sentinel的源码:

然后用IDEA打开源码项目,修改sentinel-dashboard的pom文件,将sentinel-datasource-nacos依赖的<scope>test</scope>删除或注释:

接着将sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/rule/nacos目录完整的复制到sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/rule目录下面:

在配置文件application.properties中添加自定义配置项sentinel.nacos.addr=192.168.141.150:8848,然后修改nacos包下面的NacosConfig类读取该配置项:

修改com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2类中注入的对象为nacos包下面的对象:

修改前端页面src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html,这段代码原先是注释状态,取消注释即可:

修改前端js部分src/main/webapp/resources/app/scripts/controllers/identity.js,把第四行FlowControllerV1修改为FlowControllerV2:

还是该js文件,98行左右,修改/dashboard/flow/为/dashboard/v2/flow/(该js逻辑就是将Sentinel Dashboard的流控规则同步给Nacos):

修改完成后,重新编译打包部署Sentinel Dashboard,需要跳过单元测试:

上图包名是防止重名修改后的包。关闭原Sentinel Dashboard,启动新的Dashboard。

Sentinel客户端(order-service服务)也需要修改流控规则的数据源的配置项:

bash 复制代码
spring:

  application:

    name: order-service

  cloud:

    nacos:

      discovery:

        server-addr: 192.168.141.150:8848

    sentinel:

      transport:

        dashboard: 127.0.0.1:8100 #sentinel控制台地址

        client-ip: 127.0.0.1 #连接本地Sentinel可以不配置,连接服务器Sentinel需要配置client-ip

      web-context-unify: false #关闭context整合

      datasource:

        flow-rules: #流控规则

          nacos:

            server-addr: ${spring.cloud.nacos.discovery.server-addr}

            dataId: ${spring.application.name}-flow-rules

            groupId: SENTINEL_GROUP

            rule-type: flow

然后重启服务,观察效果,记得注释resources目录下com.alibaba.csp.sentinel.init.InitFunc文件中的内容(这是Push模式基于Nacos的实现,但是只能Nacos同步Sentinel Dashboard,不能Sentinel Dashboard同步Nacos):

Sentinel Dashboard页面,多了选项流控规则V1,该选项修改的流控规则就会同步给Nacos,选项流控规则还是原来的选项,只能接受Nacos往Sentinel Dashboard单向同步。

可以发现,规则正确同步过去了。

下篇文章:

Spring Cloud系列---Alibaba Seata分布式事务https://blog.csdn.net/sniper_fandc/article/details/149946716?fromshare=blogdetail&sharetype=blogdetail&sharerId=149946716&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

相关推荐
xiao-xiang21 小时前
redis-sentinel基础概念及部署
数据库·redis·sentinel
泉城老铁1 天前
Spring Boot中实现多线程6种方式,提高架构性能
spring boot·后端·spring cloud
一颗星的征途2 天前
java循环分页查询数据,任何把查询到的数据,分批处理,多线程提交到数据库清洗数据
java·数据库·mysql·spring cloud
Hello World呀2 天前
springcloud负载均衡测试类
spring·spring cloud·负载均衡
麦兜*3 天前
Spring Boot调用优化版AI推理微服务 集成 NVIDIA NIM指南
java·人工智能·spring boot·后端·spring cloud·微服务·ai编程
tanxiaomi5 天前
学习分库分表的前置知识:高可用系统架构理论与实践
java·mysql·spring cloud·系统架构·springboot
蓝眸少年CY5 天前
(第三篇)spring cloud之Zookeeper注册中心
spring·spring cloud·zookeeper
weixin_429326095 天前
Spring Cloud-面试题(49)
后端·spring·spring cloud
麦兜*6 天前
内存杀手机器:TensorFlow Lite + Spring Boot移动端模型服务深度优化方案
java·人工智能·spring boot·spring cloud·ai·tensorflow·ai编程