微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)

目录

  • 参考文献
    • 方法一:修改FlowControllerV1,默认是操作内存,修改代码增删改查,增加对naocs的操作
    • 方法二:修改FlowControllerV2,增加nacos的支持
  • 一、修改order-service服务
    • 1、引入依赖
    • 2、配置nacos地址
  • 二、修改sentinel-dashboard源码
    • 1、下载
    • 2、解压
    • 3、修改nacos依赖
    • 4、添加nacos支持
    • 5、修改nacos地址
    • 6、配置nacos数据源
    • 7、修改前端页面
    • 8、重新编译、打包项目
      • 方法一
      • 方法二
    • 9、启动
      • 启动方式跟官方一样:
      • 如果要修改nacos地址,需要添加参数:
    • 10、测试
      • 先访问路由生成链路
    • 11、sentinel低版本FlowControllerV1支持nacos
  • 三、源代码
    • 代码仓库
    • 可执行文件下载

参考文献

持久化需要修改源码,修改支持两种方法:

方法一:修改FlowControllerV1,默认是操作内存,修改代码增删改查,增加对naocs的操作

参考:https://blog.csdn.net/qq_38723394/article/details/108991518

方法二:修改FlowControllerV2,增加nacos的支持

参考:https://blog.csdn.net/a6470831/article/details/124438593
该文档中增加了flow:限流,degrade:熔断降级,authority:授权,param-flow:参数限流四种的做法,本文档只实现了flow限流。

一、修改order-service服务

修改OrderService,让其监听Nacos中的sentinel规则配置。

具体步骤如下:

1、引入依赖

在order-service中引入sentinel监听nacos的依赖:

<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-datasource-nacosartifactId>
dependency>

2、配置nacos地址

在order-service中的application.yml文件配置nacos地址及监听的配置信息:

spring:
  cloud:
  	nacos:
      discovery: #服务注册与发现
        server-addr: ${NACOS_HOST:nacos}:${NACOS_PORT:8848} #nacos地址
        username: dev
        password: 123456
        namespace: edevp-demo #指定命名空间 可以删掉namespace不写默认public
    sentinel:
      datasource:
        flow:
          nacos:
            server-addr: ${spring.cloud.nacos.discovery.server-addr} # nacos地址
            dataId: orderservice-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow # 还可以是:degrade、authority、param-flow

flow:限流
degrade:熔断降级
authority:授权
param-flow:参数限流
对应如下几个:
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第1张图片

可以配置多个规则
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第2张图片

二、修改sentinel-dashboard源码

SentinelDashboard默认不支持nacos的持久化,需要修改源码。以下的方式我们只实现了**flow(流控)**的持久化,其他的自行参照文档实现。

以下改造采用方法二:

1、下载

进入官网https://github.com/alibaba/Sentinel/tree/1.8.4,切换tag分支1.8.4

下载Sentinel-1.8.4.zip

2、解压

解压sentinel源码包:
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第3张图片

然后并用IDEA打开这个项目,结构如下:
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第4张图片
为了简化程序,我们把其他工程删掉,只留下sentinel-dashboard.
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第5张图片

3、修改nacos依赖

在sentinel-dashboard源码的pom文件中,nacos的依赖默认的scope是test,只能在测试时使用,这里要去除:

<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-datasource-nacosartifactId>
dependency>

新引入

<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-api</artifactId>
    <version>1.4.2</version>
</dependency>

4、添加nacos支持

在sentinel-dashboard的test包下,已经编写了对nacos的支持,我们需要将其拷贝到main下。
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第6张图片
FlowRuleNacosPublisher中会报错找不到:com.alibaba.nacos.api.config.ConfigService;
需要引入:

<dependency>
    <groupId>com.alibaba.nacosgroupId>
    <artifactId>nacos-apiartifactId>
    <version>1.4.2version>
dependency>

5、修改nacos地址

然后,还需要修改测试代码中的NacosConfig类:

/**
 * @author Eric Zhao
 * @since 1.4.0
 */
@Configuration
public class NacosConfig {

    @Value("${nacos.address}")
    private String addr;   //Nacos地址



    @Value("${nacos.namespace}")
    private String namespace;   //Nacos地址


    @Bean
    public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
        return JSON::toJSONString;
    }

    @Bean
    public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
        return s -> JSON.parseArray(s, FlowRuleEntity.class);
    }

    @Bean
    public ConfigService nacosConfigService() throws Exception {
        Properties properties = new Properties();
        //nacos集群地址
        properties.put(PropertyKeyConst.SERVER_ADDR,addr);
        //namespace为空即为public
        properties.put(PropertyKeyConst.NAMESPACE,namespace);
        return ConfigFactory.createConfigService(properties);
    }
}

在sentinel-dashboard的application.properties中添加nacos地址配置:

nacos.address=localhost:8848
nacos.namespace=public

6、配置nacos数据源

另外,还需要修改sentinel-dashboard/com.alibaba.csp.sentinel.dashboard.controller.v2包下的FlowControllerV2类:
原代码

@Autowired
    @Qualifier("flowRuleDefaultProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("flowRuleDefaultPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

改为:

@Autowired
    @Qualifier("flowRuleNacosProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("flowRuleNacosPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

7、修改前端页面

接下来,还要修改前端页面,添加一个支持nacos的菜单。

修改sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/目录下的sidebar.html文件:
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第7张图片

<li ui-sref-active="active" ng-if="!entry.isGateway">
  <a ui-sref="dashboard.flow({app: entry.app})">
    <i class="glyphicon glyphicon-filter">i>  流控规则-nacosa>
li>

对应的controller
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第8张图片

8、重新编译、打包项目

方法一

运行IDEA中的maven插件,编译和打包修改好的Sentinel-Dashboard:
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第9张图片

方法二

执行 --强烈建议不要在idea里面执行 去到根目录 打包

mvn clean package -DskipTests

9、启动

启动方式跟官方一样:

java -jar sentinel-dashboard.jar

如果要修改nacos地址,需要添加参数:

java -jar -Dnacos.address=localhost:8848 -Dnacos.namespace=public sentinel-dashboard.jar

window下

@echo off

cd E:\Program Files\Java\jdk1.8.0_131\bin

java -jar -Xms256m -Xmx256m -Dnacos.addr=nacos:8848 -Dnacos.namespace=edevp-demo -Dserver.port=8858 -Dcsp.sentinel.dashboard.server=127.0.0.1:8858 E:\dev\sentinel\Sentinel-1.8.4\sentinel-dashboard\target\sentinel-dashboard.jar
pause

10、测试

先访问路由生成链路

访问http://192.168.0.55:8203/orders/1,再次刷新setinel如下:
然后在流控规则-nacos添加规则
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第10张图片
新增之后在nacos发现已经持久化
微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第11张图片

11、sentinel低版本FlowControllerV1支持nacos

  • 必须引入:
@Autowired
    @Qualifier("flowRuleNacosProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;

    @Autowired
    @Qualifier("flowRuleNacosPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
  • 查询
//改为nacos读取
            List<FlowRuleEntity> rules = ruleProvider.getRules(app);
  • 核心在于每次增删改都需要推送到nacos
private void publishRules(String appName) throws Exception {
        List<FlowRuleEntity> rules = repository.findAllByApp(appName);
        rulePublisher.publish(appName,rules);
    }

完整代码如下:

/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.dashboard.controller;

import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.util.StringUtil;

import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * Flow rule controller.
 *
 * @author leyou
 * @author Eric Zhao
 */
@RestController
@RequestMapping(value = "/v1/flow")
public class FlowControllerV1 {

    private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class);

    @Autowired
    private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;

    @Autowired
    private SentinelApiClient sentinelApiClient;

    @Autowired
    @Qualifier("flowRuleNacosProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;

    @Autowired
    @Qualifier("flowRuleNacosPublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

    @GetMapping("/rules")
    @AuthAction(PrivilegeType.READ_RULE)
    public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app,
                                                             @RequestParam String ip,
                                                             @RequestParam Integer port) {

        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isEmpty(ip)) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (port == null) {
            return Result.ofFail(-1, "port can't be null");
        }
        try {
            //注释源内存读取
            //List rules = sentinelApiClient.fetchFlowRuleOfMachine(app, ip, port);
            //改为nacos读取
            List<FlowRuleEntity> rules = ruleProvider.getRules(app);
            rules = repository.saveAll(rules);
            return Result.ofSuccess(rules);
        } catch (Throwable throwable) {
            logger.error("Error when querying flow rules", throwable);
            return Result.ofThrowable(-1, throwable);
        }
    }

    private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
        if (StringUtil.isBlank(entity.getApp())) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isBlank(entity.getIp())) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (entity.getPort() == null) {
            return Result.ofFail(-1, "port can't be null");
        }
        if (StringUtil.isBlank(entity.getLimitApp())) {
            return Result.ofFail(-1, "limitApp can't be null or empty");
        }
        if (StringUtil.isBlank(entity.getResource())) {
            return Result.ofFail(-1, "resource can't be null or empty");
        }
        if (entity.getGrade() == null) {
            return Result.ofFail(-1, "grade can't be null");
        }
        if (entity.getGrade() != 0 && entity.getGrade() != 1) {
            return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
        }
        if (entity.getCount() == null || entity.getCount() < 0) {
            return Result.ofFail(-1, "count should be at lease zero");
        }
        if (entity.getStrategy() == null) {
            return Result.ofFail(-1, "strategy can't be null");
        }
        if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
            return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
        }
        if (entity.getControlBehavior() == null) {
            return Result.ofFail(-1, "controlBehavior can't be null");
        }
        int controlBehavior = entity.getControlBehavior();
        if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
            return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
        }
        if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
            return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
        }
        if (entity.isClusterMode() && entity.getClusterConfig() == null) {
            return Result.ofFail(-1, "cluster config should be valid");
        }
        return null;
    }

    @PostMapping("/rule")
    @AuthAction(PrivilegeType.WRITE_RULE)
    public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
        Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
        if (checkResult != null) {
            return checkResult;
        }
        entity.setId(null);
        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);
        entity.setLimitApp(entity.getLimitApp().trim());
        entity.setResource(entity.getResource().trim());
        try {
            entity = repository.save(entity);
            // 推送到nacos
            publishRules(entity.getApp());

            publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS);
            return Result.ofSuccess(entity);
        } catch (Throwable t) {
            Throwable e = t instanceof ExecutionException ? t.getCause() : t;
            logger.error("Failed to add new flow rule, app={}, ip={}", entity.getApp(), entity.getIp(), e);
            return Result.ofFail(-1, e.getMessage());
        }
    }

    @PutMapping("/save.json")
    @AuthAction(PrivilegeType.WRITE_RULE)
    public Result<FlowRuleEntity> apiUpdateFlowRule(Long id, String app,
                                                  String limitApp, String resource, Integer grade,
                                                  Double count, Integer strategy, String refResource,
                                                  Integer controlBehavior, Integer warmUpPeriodSec,
                                                  Integer maxQueueingTimeMs) {
        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }
        FlowRuleEntity entity = repository.findById(id);
        if (entity == null) {
            return Result.ofFail(-1, "id " + id + " dose not exist");
        }
        if (StringUtil.isNotBlank(app)) {
            entity.setApp(app.trim());
        }
        if (StringUtil.isNotBlank(limitApp)) {
            entity.setLimitApp(limitApp.trim());
        }
        if (StringUtil.isNotBlank(resource)) {
            entity.setResource(resource.trim());
        }
        if (grade != null) {
            if (grade != 0 && grade != 1) {
                return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got");
            }
            entity.setGrade(grade);
        }
        if (count != null) {
            entity.setCount(count);
        }
        if (strategy != null) {
            if (strategy != 0 && strategy != 1 && strategy != 2) {
                return Result.ofFail(-1, "strategy must be in [0, 1, 2], but " + strategy + " got");
            }
            entity.setStrategy(strategy);
            if (strategy != 0) {
                if (StringUtil.isBlank(refResource)) {
                    return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
                }
                entity.setRefResource(refResource.trim());
            }
        }
        if (controlBehavior != null) {
            if (controlBehavior != 0 && controlBehavior != 1 && controlBehavior != 2) {
                return Result.ofFail(-1, "controlBehavior must be in [0, 1, 2], but " + controlBehavior + " got");
            }
            if (controlBehavior == 1 && warmUpPeriodSec == null) {
                return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
            }
            if (controlBehavior == 2 && maxQueueingTimeMs == null) {
                return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
            }
            entity.setControlBehavior(controlBehavior);
            if (warmUpPeriodSec != null) {
                entity.setWarmUpPeriodSec(warmUpPeriodSec);
            }
            if (maxQueueingTimeMs != null) {
                entity.setMaxQueueingTimeMs(maxQueueingTimeMs);
            }
        }
        Date date = new Date();
        entity.setGmtModified(date);
        try {
            entity = repository.save(entity);
            if (entity == null) {
                return Result.ofFail(-1, "save entity fail: null");
            }
            // 推送到nacos
            publishRules(entity.getApp());
            publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS);
            return Result.ofSuccess(entity);
        } catch (Throwable t) {
            Throwable e = t instanceof ExecutionException ? t.getCause() : t;
            logger.error("Error when updating flow rules, app={}, ip={}, ruleId={}", entity.getApp(),
                entity.getIp(), id, e);
            return Result.ofFail(-1, e.getMessage());
        }
    }

    @DeleteMapping("/delete.json")
    @AuthAction(PrivilegeType.WRITE_RULE)
    public Result<Long> apiDeleteFlowRule(Long id) {

        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }
        FlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofSuccess(null);
        }

        try {
            repository.delete(id);
        } catch (Exception e) {
            return Result.ofFail(-1, e.getMessage());
        }
        try {
            publishRules(oldEntity.getApp());
            publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get(5000, TimeUnit.MILLISECONDS);
            return Result.ofSuccess(id);
        } catch (Throwable t) {
            Throwable e = t instanceof ExecutionException ? t.getCause() : t;
            logger.error("Error when deleting flow rules, app={}, ip={}, id={}", oldEntity.getApp(),
                oldEntity.getIp(), id, e);
            return Result.ofFail(-1, e.getMessage());
        }
    }

    private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
        List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
        return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);
    }

    private void publishRules(String appName) throws Exception {
        List<FlowRuleEntity> rules = repository.findAllByApp(appName);
        rulePublisher.publish(appName,rules);
    }
}

三、源代码

代码仓库

https://gitee.com/edevp/sentinel-1.8.4-nacos.git

可执行文件下载

微服务之Sentinel-第六章-规则持久化(sentinel-1.8.4持久化)_第12张图片

sentinel-1.8.4-nacos.jar

你可能感兴趣的:(SpringCloud,微服务,java,spring,cloud)