工作纪实_25-SpringCloud整合sentinel问题汇总

sentinel使用汇总

        • 1.纠结于`sentinel` 包的引入
        • 2.关于reactive客户端和web客户端方面包引入的区别
        • 3.包版本不匹配的问题
        • 4.代码
          • 1.启动类
          • 2.http接口
          • 3.sentinel控制台
        • 5.sentinel应用于生产需要考虑的问题
          • 1.规则管理及推送【必做】
          • 2. 监控【必做】
          • 3. 权限控制【其实没必要,看需求】
        • FAQ
          • 1.关于`sentinel`的异常统一处理
          • 2.改造`sentinel-dashboard`,持久化监控数据
            • 1.`sentinel-dashboard`版本过新
            • 2.找不到包
          • 3.生产改造:持久化监控数据数据
            • 1.打开项目,找到`sentinel-dashboard`模块的`pom`文件
            • 2. 配置`application.yaml`文件
            • 3.创建监控数据存储表、repo 、entity等
            • 4. 改造监控数据repo调用
            • 5.sentinel客户端【应用程序】
            • 6.启动上述的两个项目
          • 4.生产改造:持久化sentinel-dashboard规则【push模式】
            • 1. 介绍
            • 2.重写推送规则
            • 3.修改规则的存储方式为redis

sentinel官方文档
生产环境完整版改造demo:https://download.csdn.net/download/u013553309/15533552?spm=1001.2014.3001.5503

1.纠结于sentinel 包的引入

很多文章、资料都贴着下面的包

<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-annotation-aspectjartifactId>
    <version>1.6.3version>
dependency>
<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-coreartifactId>
    <version>1.6.3version>
dependency>
<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-transport-simple-httpartifactId>
    <version>1.6.3version>
dependency>

官网给出的包确是

<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    <version>2.1.2.RELEASEversion>
dependency>

第一种方法没试过,但个人觉得不要与标准去做对,出现问题连参照都没有,启动、编译报错一顿NoClassFound是最伤的,包反而是会越补越乱

2.关于reactive客户端和web客户端方面包引入的区别

如果是cloud gateway网关类型对sentinel做整合,需要严格上面的官方文档地址去做包的引入

3.包版本不匹配的问题

我在使用com.alibaba.cloud包引入sentinel时,没有注意到与springcloud包版本的匹配【我cloud项目使用的是cloud的原生包,除了sentinel包使用阿里巴巴的cloud包组件】,启动报错出现错误工作纪实_25-SpringCloud整合sentinel问题汇总_第1张图片
版本比对
工作纪实_25-SpringCloud整合sentinel问题汇总_第2张图片

4.代码

1.启动类
@Bean
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
     
    return new RestTemplate();
}
public static void main(String[] args) {
     
    SpringApplication.run(App.class, args);
    log.info("{} is started!", App.class.getSimpleName());
}
@Slf4j
public class ExceptionUtil {
     

    /**
     * 普通阻塞错误
     */
    public static ClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) {
     
        log.info("feign调用错误发生: " + exception.getMessage());
        return new ClientHttpResponse() {
     
            @Override
            public HttpStatus getStatusCode() throws IOException {
     
                return HttpStatus.TOO_MANY_REQUESTS;
            }

            @Override
            public int getRawStatusCode() throws IOException {
     
                return 0;
            }

            @Override
            public String getStatusText() throws IOException {
     
                return null;
            }

            @Override
            public void close() {
     

            }

            @Override
            public InputStream getBody() throws IOException {
     
                return null;
            }

            @Override
            public HttpHeaders getHeaders() {
     
                return null;
            }
        };
    }
}
2.http接口
@RestController
@RequestMapping("/test")
public class TestController {
     

 	@GetMapping("sentinel")
 	@SentinelResource(value = "test.sentinel", fallback = "demotion")
 	public JsonResult<String> test(@RequestParam String param) {
     
    	log.info("测试sentinel:{}", param);
     	return JsonResult.ok(param);
 	}
 
 	/**
 	* sentinel接口熔断后的降级处理
 	*/
	public String demotion(String param, Throwable e) {
     
    	return String.format("sentinel服务降级,入参:%s,异常:%s", param, e.getCause());
	}
} 
3.sentinel控制台

使用postman或者其他rest请求工具模拟发送请求
工作纪实_25-SpringCloud整合sentinel问题汇总_第3张图片
由于sentinel客户端的监控数据,我并没有做持久化,只显示5分钟之内的监控数据,但是我们可以去查看对应的sentinel日志来回顾历史数据
工作纪实_25-SpringCloud整合sentinel问题汇总_第4张图片
对应windows的我的电脑目录是: C:\Users\liuleiba\logs\csp
工作纪实_25-SpringCloud整合sentinel问题汇总_第5张图片
参照官网的可以去寻找需要的数据 :实时监控

5.sentinel应用于生产需要考虑的问题

官网原话:生产环境的 Sentinel Dashboard 需要具备下面几个特性:

1.规则管理及推送【必做】
集中管理和推送规则。sentinel-core 提供 API 和扩展接口来接收信息。开发者需要根据自己的环境,选取一个可靠的推送规则方式;同时,规则最好在控制台中集中管理。
  • 原始模式
    如果不做任何修改,Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中:

工作纪实_25-SpringCloud整合sentinel问题汇总_第6张图片
这种做法的好处是简单,无依赖;坏处是重启应用之后。在sentinel-dashboard上配置好的规则就会消失,仅用于简单测试,不能用于生产环境。

  • 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);
    }
}

工作纪实_25-SpringCloud整合sentinel问题汇总_第7张图片
首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。使用 pull 模式的数据源时一般不需要对 Sentinel 控制台进行改造。
这种实现方法好处是简单,不引入新的依赖,坏处是无法保证监控数据的一致性

  • push模式
    生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非常清晰了:
    工作纪实_25-SpringCloud整合sentinel问题汇总_第8张图片
    毫无疑问,生产环境,只能选择推Push模式了!
2. 监控【必做】

支持可靠、快速的实时监控和历史监控数据查询。sentinel-core 记录秒级的资源运行情况,并且提供 API 来拉取资源运行信息。当机器大于一台以上的时候,可以通过 Dashboard 来拉取,聚合,并且存储这些信息。这个时候,Dashboard 需要有一个存储媒介,来存储历史运行情况。
工作纪实_25-SpringCloud整合sentinel问题汇总_第9张图片

3. 权限控制【其实没必要,看需求】

区分用户角色,来进行操作。生产环境下的权限控制是非常重要的,理论上只有管理员等高级用户才有权限去修改应用的规则。

由于开发者有各自不一样的环境和需求,我们会对“规则管理和推送”,“监控”这两个方面给出建议以及最佳实践;对于权限控制,由于每个开发者的环境都不一样,我们在最佳实践中仅仅使用了简单的认证。开发者可以依循自己的需求,结合实际生产环境,选择最适合自己的方式。

对于上述的操作,我们没有考虑以上的3个问题,但是实际情况而言,规则管理及推送和监控数据的持久化是我们不得不面对的问题,因为默认情况下的sentinel-dashboard只存储5分钟以内的数据存在内存当中

至此,关于概念上的sentinel的说明和介绍就到此为止了,下面重点关注一下在生产环境中的使用需要准备去做的事情,以及我在使用途中遇到的问题汇总一下

FAQ

1.关于sentinel的异常统一处理

有很多文章说的是在@SentinelResource的注解属性中配置,个人觉得比较麻烦,我是在异常中统一处理的

@ExceptionHandler(UndeclaredThrowableException.class)
public JsonResult flowException(UndeclaredThrowableException ex) {
     
    JsonResult jsonResult = new JsonResult();
    if (ex.getUndeclaredThrowable() instanceof FlowException) {
     
        jsonResult.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
        jsonResult.setMessage("请求服务器过于频繁,请稍侯再试");
    } else if (ex.getUndeclaredThrowable() instanceof BlockException) {
     
        jsonResult.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
        jsonResult.setMessage("目标服务未启用,请稍候再试");
    } else {
     
        jsonResult.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        jsonResult.setMessage("系统未知异常,请联系管理员");
    }
    return jsonResult;
}
2.改造sentinel-dashboard,持久化监控数据
1.sentinel-dashboard版本过新

这个会直接导致我们引入其他包,像我引入了mybatisplus想做监控数据的持久化,但是很惨,版本包的问题困扰我一天,需要人为的对sentinel-dashboard模块的包,尤其是对spring-boot基础依赖版本有要求,如果出现spring包的问题,比如@Configuration注解报找不到proxyBeanMethods,则可以升级看看,我当时下载的时候没注意sentinel-dashboard版本,结果发现所依赖的spring-boot基础版本还挺高的,算是自己给自己挖坑了,不过好在解决了!
工作纪实_25-SpringCloud整合sentinel问题汇总_第10张图片

2.找不到包

工作纪实_25-SpringCloud整合sentinel问题汇总_第11张图片
上述问题的话,可以引入包即刻解决

<dependency>
	<groupId>com.fasterxml.jackson.coregroupId>
	<artifactId>jackson-databindartifactId>
	<version>2.10.2version>
dependency>

如果缺少其他的包,缺一个补一个就行!

下载sentinel-dashboard源码: https://github.com/alibaba/Sentinel

3.生产改造:持久化监控数据数据

关于生产要注意的问题,和规则的推送模式上面已经提到过,先改造监控数据的持久化!

监控数据由于sentinel-dashboard本身就是使用的内存做的存储,所以只能够提供5分钟范围内的数据存储,这一点在生产上是极其不能接受的,至少也是需要一段时间内的监控数据,我们才可以监测到系统的运行情况,虽然不是每个程序都是百万级别的访问量,但是该做的改造还是要去做。
为了解析和操作方便,此处我使用的是mysql作为存储介质来存放监控数据,其实换成mongodb可能要更好一点。

1.打开项目,找到sentinel-dashboard模块的pom文件
<properties>
	
	<spring.boot.version>2.2.0.RELEASEspring.boot.version>
	<curator.version>4.0.1curator.version>
properties>

<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <scope>runtimescope>
    <version>8.0.16version>
dependency>
<dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>3.3.0version>
dependency>

<dependency>
    <groupId>com.fasterxml.jackson.coregroupId>
    <artifactId>jackson-databindartifactId>
    <version>2.10.2version>
dependency>
2. 配置application.yaml文件

配置redisdatasource数据源

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    password: roo1t
    url: jdbc:mysql://47.115.158.78:3306/sentinel?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
    username: root1
    redis:
    host: 47.115.158.78
    port: 6399
    timeout: 20s
    # 数据库索引
    database: 0
    jedis:
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        max-active: 300
        #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1s
        #连接池中的最大空闲连接
        max-idle: 100
        #连接池中的最小空闲连接
        min-idle: 20
mybatis-plus:
  configuration:
    default-statement-timeout: 30000
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
  global-config:
    banner: false
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0
    refresh: true
  mapper-locations: classpath*:mapper/*Mapper.xml
  typeAliasesPackage: com.alibaba.csp.sentinel.dashboard.datasource.entity
3.创建监控数据存储表、repo 、entity等

工作纪实_25-SpringCloud整合sentinel问题汇总_第12张图片
metric包是存储监控数据的工具repo层,使用的是默认的内存模式存储,所以我们需要创建一个DbMetricsRepository来替换它,但是存储的数据格式属性,是一模一样的

package com.alibaba.csp.sentinel.dashboard.repository.metric;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelMetricEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.mappers.SentinelMetricsMapper;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author liulei
 * @version 1.0
 */
@Component
public class DbMetricsRepository implements MetricsRepository<MetricEntity> {
     

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

    private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    @Autowired
    private SentinelMetricsMapper metricsMapper;

    @Override
    public void save(MetricEntity entity) {
     
        if (entity == null || StringUtil.isBlank(entity.getApp())) {
     
            return;
        }
        readWriteLock.writeLock().lock();
        try {
     
            SentinelMetricEntity metricEntity = new SentinelMetricEntity();
            BeanUtils.copyProperties(entity, metricEntity);
            metricsMapper.insert(metricEntity);
        } finally {
     
            readWriteLock.writeLock().unlock();
        }
        logger.info("存储监控信息:{}", entity);
    }

    @Override
    public void saveAll(Iterable<MetricEntity> metrics) {
     
        Iterator<MetricEntity> iterator = metrics.iterator();
        while (iterator.hasNext()) {
     
            MetricEntity metricEntity = iterator.next();
            this.save(metricEntity);
        }
        logger.info("批量存储监控信息:{}", metrics);
    }

    @Override
    public List<MetricEntity> queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime) {
     
        List<SentinelMetricEntity> metricEntities = this.metricsMapper.queryByAppAndResourceBetween(app, resource,
                dateFormat.format(new Date(startTime)), dateFormat.format(new Date(endTime)));
        List<MetricEntity> entities = new ArrayList<>();
        for (SentinelMetricEntity entity : metricEntities) {
     
            MetricEntity metricEntity = new MetricEntity();
            BeanUtils.copyProperties(entity, metricEntity);
            entities.add(metricEntity);
        }
        logger.info("查询一段时间内的监控数据:{}", entities);
        return entities;
    }

    @Override
    public List<String> listResourcesOfApp(String app) {
     
        List<String> strings = this.metricsMapper.listResourcesOfApp(app);
        logger.info("根据app查询监控资源:{}", strings);
        return strings;
    }

}
package com.alibaba.csp.sentinel.dashboard.datasource.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.util.Date;

/**
 * 复制框架原生自带的MetricEntity对象,改成和数据库中映射的字段即可
 *
 * @author liulei
 * @version 1.0
 */
@TableName("sentinel_metric")
public class SentinelMetricEntity {
     

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @TableId(value = "gmt_create")
    private Date gmtCreate;
    @TableId(value = "gmt_modified")
    private Date gmtModified;
    @TableId(value = "app")
    private String app;
    /**
     * 监控信息的时间戳
     */
    @TableId(value = "timestamp")
    private Date timestamp;
    @TableId(value = "resource")
    private String resource;
    @TableId(value = "pass_qps")
    private Long passQps;
    @TableId(value = "success_qps")
    private Long successQps;
    @TableId(value = "block_qps")
    private Long blockQps;
    @TableId(value = "exception_qps")
    private Long exceptionQps;

    /**
     * summary rt of all success exit qps.
     */
    @TableId(value = "rt")
    private double rt;

    /**
     * 本次聚合的总条数
     */
    @TableId(value = "count")
    private int count;
    @TableId(value = "resource_code")
    private int resourceCode;

    public static SentinelMetricEntity copyOf(MetricEntity oldEntity) {
     
        SentinelMetricEntity entity = new SentinelMetricEntity();
        entity.setId(oldEntity.getId());
        entity.setGmtCreate(oldEntity.getGmtCreate());
        entity.setGmtModified(oldEntity.getGmtModified());
        entity.setApp(oldEntity.getApp());
        entity.setTimestamp(oldEntity.getTimestamp());
        entity.setResource(oldEntity.getResource());
        entity.setPassQps(oldEntity.getPassQps());
        entity.setBlockQps(oldEntity.getBlockQps());
        entity.setSuccessQps(oldEntity.getSuccessQps());
        entity.setExceptionQps(oldEntity.getExceptionQps());
        entity.setRt(oldEntity.getRt());
        entity.setCount(oldEntity.getCount());
        return entity;
    }

    public synchronized void addPassQps(Long passQps) {
     
        this.passQps += passQps;
    }

    public synchronized void addBlockQps(Long blockQps) {
     
        this.blockQps += blockQps;
    }

    public synchronized void addExceptionQps(Long exceptionQps) {
     
        this.exceptionQps += exceptionQps;
    }

    public synchronized void addCount(int count) {
     
        this.count += count;
    }

    public synchronized void addRtAndSuccessQps(double avgRt, Long successQps) {
     
        this.rt += avgRt * successQps;
        this.successQps += successQps;
    }

    /**
     * {@link #rt} = {@code avgRt * successQps}
     *
     * @param avgRt      average rt of {@code successQps}
     * @param successQps
     */
    public synchronized void setRtAndSuccessQps(double avgRt, Long successQps) {
     
        this.rt = avgRt * successQps;
        this.successQps = successQps;
    }

    public Long getId() {
     
        return id;
    }

    public void setId(Long id) {
     
        this.id = id;
    }

    public Date getGmtCreate() {
     
        return gmtCreate;
    }

    public void setGmtCreate(Date gmtCreate) {
     
        this.gmtCreate = gmtCreate;
    }

    public Date getGmtModified() {
     
        return gmtModified;
    }

    public void setGmtModified(Date gmtModified) {
     
        this.gmtModified = gmtModified;
    }

    public String getApp() {
     
        return app;
    }

    public void setApp(String app) {
     
        this.app = app;
    }

    public Date getTimestamp() {
     
        return timestamp;
    }

    public void setTimestamp(Date timestamp) {
     
        this.timestamp = timestamp;
    }

    public String getResource() {
     
        return resource;
    }

    public void setResource(String resource) {
     
        this.resource = resource;
        this.resourceCode = resource.hashCode();
    }

    public Long getPassQps() {
     
        return passQps;
    }

    public void setPassQps(Long passQps) {
     
        this.passQps = passQps;
    }

    public Long getBlockQps() {
     
        return blockQps;
    }

    public void setBlockQps(Long blockQps) {
     
        this.blockQps = blockQps;
    }

    public Long getExceptionQps() {
     
        return exceptionQps;
    }

    public void setExceptionQps(Long exceptionQps) {
     
        this.exceptionQps = exceptionQps;
    }

    public double getRt() {
     
        return rt;
    }

    public void setRt(double rt) {
     
        this.rt = rt;
    }

    public int getCount() {
     
        return count;
    }

    public void setCount(int count) {
     
        this.count = count;
    }

    public int getResourceCode() {
     
        return resourceCode;
    }

    public Long getSuccessQps() {
     
        return successQps;
    }

    public void setSuccessQps(Long successQps) {
     
        this.successQps = successQps;
    }

    @Override
    public String toString() {
     
        return "DBMetricEntity{" +
                "id=" + id +
                ", gmtCreate=" + gmtCreate +
                ", gmtModified=" + gmtModified +
                ", app='" + app + '\'' +
                ", timestamp=" + timestamp +
                ", resource='" + resource + '\'' +
                ", passQps=" + passQps +
                ", blockQps=" + blockQps +
                ", successQps=" + successQps +
                ", exceptionQps=" + exceptionQps +
                ", rt=" + rt +
                ", count=" + count +
                ", resourceCode=" + resourceCode +
                '}';
    }
}
package com.alibaba.csp.sentinel.dashboard.datasource.mappers;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelMetricEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @author liulei
 * @version 1.0
 */
public interface SentinelMetricsMapper extends BaseMapper<SentinelMetricEntity> {
     

    /**
     * 监控数据条件查询1
     */
    List<SentinelMetricEntity> queryByAppAndResourceBetween(@Param("app") String app, @Param("resource") String resource,
                                                            @Param("start") String start, @Param("end") String end);

    /**
     * 监控税局条件查询2
     */
    List<String> listResourcesOfApp(String app);
}


<mapper namespace="com.alibaba.csp.sentinel.dashboard.datasource.mappers.SentinelMetricsMapper">

    <select id="queryByAppAndResourceBetween"
            resultType="com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelMetricEntity">
        select * from sentinel_metric
        where app = #{app}
        <if test="start != null and start != ''">
            and `timestamp` >= #{start}
        if>
        <if test="end != null and end != ''">
            and `timestamp` <= #{end}
        if>
    select>

    <select id="listResourcesOfApp" resultType="java.lang.String">
        select resource from sentinel_metric
        where app = #{app}
    select>
mapper>

为此,我们可以根据MetricEntity反推出监控的数据表sql

CREATE TABLE `sentinel_metric` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id,主键',
  `gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
  `gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
  `app` varchar(100) DEFAULT NULL COMMENT '应用名称',
  `timestamp` datetime DEFAULT NULL COMMENT '统计时间',
  `resource` varchar(500) DEFAULT NULL COMMENT '资源名称',
  `pass_qps` int(11) DEFAULT NULL COMMENT '通过qps',
  `success_qps` int(11) DEFAULT NULL COMMENT '成功qps',
  `block_qps` int(11) DEFAULT NULL COMMENT '限流qps',
  `exception_qps` int(11) DEFAULT NULL COMMENT '发送异常的次数',
  `rt` double DEFAULT NULL COMMENT '所有successQps的rt的和',
  `count` int(11) DEFAULT NULL COMMENT '本次聚合的总条数',
  `resource_code` int(11) DEFAULT NULL COMMENT '资源的hashCode',
  PRIMARY KEY (`id`),
  KEY `app_idx` (`app`) USING BTREE,
  KEY `resource_idx` (`resource`) USING BTREE,
  KEY `timestamp_idx` (`timestamp`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=229 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
4. 改造监控数据repo调用
/*
 * 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.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository;
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.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.alibaba.csp.sentinel.dashboard.domain.vo.MetricVo;

/**
 * @author leyou
 */
@Controller
@RequestMapping(value = "/metric", produces = MediaType.APPLICATION_JSON_VALUE)
public class MetricController {
     

    private static Logger logger = LoggerFactory.getLogger(MetricController.class);

    // 查询最近24小时的数据
    private static final long dayQueryIntervalMs = 1000 * 60 * 60 * 24 * 1;

    @Autowired
    @Qualifier("dbMetricsRepository")
    private MetricsRepository<MetricEntity> metricStore;

    @ResponseBody
    @RequestMapping("/queryTopResourceMetric.json")
    public Result<?> queryTopResourceMetric(final String app,
                                            Integer pageIndex,
                                            Integer pageSize,
                                            Boolean desc,
                                            Long startTime, Long endTime, String searchKey) {
     
        if (StringUtil.isEmpty(app)) {
     
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (pageIndex == null || pageIndex <= 0) {
     
            pageIndex = 1;
        }
        if (pageSize == null) {
     
            pageSize = 6;
        }
        if (pageSize >= 20) {
     
            pageSize = 20;
        }
        if (desc == null) {
     
            desc = true;
        }
        // 人为修改成统计最近24小时内的
        if (endTime == null) {
     
            endTime = System.currentTimeMillis();
        }
        if (startTime == null) {
     
            startTime = endTime - dayQueryIntervalMs;
        }
        // 注释掉下面的代码,因为1个小时之内的数据无太大意义
        /*if (endTime - startTime > maxQueryIntervalMs) {
            return Result.ofFail(-1, "time intervalMs is too big, must <= 1h");
        }*/
        List<String> resources = metricStore.listResourcesOfApp(app);
        logger.debug("queryTopResourceMetric(), resources.size()={}", resources.size());

        if (resources == null || resources.isEmpty()) {
     
            return Result.ofSuccess(null);
        }
        if (!desc) {
     
            Collections.reverse(resources);
        }
        if (StringUtil.isNotEmpty(searchKey)) {
     
            List<String> searched = new ArrayList<>();
            for (String resource : resources) {
     
                if (resource.contains(searchKey)) {
     
                    searched.add(resource);
                }
            }
            resources = searched;
        }
        int totalPage = (resources.size() + pageSize - 1) / pageSize;
        List<String> topResource = new ArrayList<>();
        if (pageIndex <= totalPage) {
     
            topResource = resources.subList((pageIndex - 1) * pageSize,
                Math.min(pageIndex * pageSize, resources.size()));
        }
        final Map<String, Iterable<MetricVo>> map = new ConcurrentHashMap<>();
        logger.debug("topResource={}", topResource);
        long time = System.currentTimeMillis();
        for (final String resource : topResource) {
     
            List<MetricEntity> entities = metricStore.queryByAppAndResourceBetween(
                app, resource, startTime, endTime);
            logger.debug("resource={}, entities.size()={}", resource, entities == null ? "null" : entities.size());
            List<MetricVo> vos = MetricVo.fromMetricEntities(entities, resource);
            Iterable<MetricVo> vosSorted = sortMetricVoAndDistinct(vos);
            map.put(resource, vosSorted);
        }
        logger.debug("queryTopResourceMetric() total query time={} ms", System.currentTimeMillis() - time);
        Map<String, Object> resultMap = new HashMap<>(16);
        resultMap.put("totalCount", resources.size());
        resultMap.put("totalPage", totalPage);
        resultMap.put("pageIndex", pageIndex);
        resultMap.put("pageSize", pageSize);

        Map<String, Iterable<MetricVo>> map2 = new LinkedHashMap<>();
        // order matters.
        for (String identity : topResource) {
     
            map2.put(identity, map.get(identity));
        }
        resultMap.put("metric", map2);
        return Result.ofSuccess(resultMap);
    }

    @ResponseBody
    @RequestMapping("/queryByAppAndResource.json")
    public Result<?> queryByAppAndResource(String app, String identity, Long startTime, Long endTime) {
     
        if (StringUtil.isEmpty(app)) {
     
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isEmpty(identity)) {
     
            return Result.ofFail(-1, "identity can't be null or empty");
        }
        if (endTime == null) {
     
            endTime = System.currentTimeMillis();
        }
        if (endTime == null) {
     
            endTime = System.currentTimeMillis();
        }
        if (startTime == null) {
     
            startTime = endTime - dayQueryIntervalMs;
        }
        List<MetricEntity> entities = metricStore.queryByAppAndResourceBetween(
            app, identity, startTime, endTime);
        List<MetricVo> vos = MetricVo.fromMetricEntities(entities, identity);
        return Result.ofSuccess(sortMetricVoAndDistinct(vos));
    }

    private Iterable<MetricVo> sortMetricVoAndDistinct(List<MetricVo> vos) {
     
        if (vos == null) {
     
            return null;
        }
        Map<Long, MetricVo> map = new TreeMap<>();
        for (MetricVo vo : vos) {
     
            MetricVo oldVo = map.get(vo.getTimestamp());
            if (oldVo == null || vo.getGmtCreate() > oldVo.getGmtCreate()) {
     
                map.put(vo.getTimestamp(), vo);
            }
        }
        return map.values();
    }
}

对了,别忘记在DashboardApplication类中加上mybatis-plus包扫描注解
在这里插入图片描述

5.sentinel客户端【应用程序】

咱单独开个项目,配置一下与sentinel-dashboard的连接即可

spring:
	cloud:
	    sentinel:
	      transport:
	        port: 8719 #与sentinel服务端交互的端口,默认就是8719
	        dashboard: localhost:8080
<dependency>
	<groupId>com.alibaba.cloudgroupId>
	<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>

再加个控制器
工作纪实_25-SpringCloud整合sentinel问题汇总_第13张图片
配置个异常处理
工作纪实_25-SpringCloud整合sentinel问题汇总_第14张图片

6.启动上述的两个项目
  • 1.新增流控规则
    工作纪实_25-SpringCloud整合sentinel问题汇总_第15张图片
  • F5 连续刷新两次[qps=1,刷新2次必然触发流控管理规则]
    在这里插入图片描述
  • 查看监控数据
    工作纪实_25-SpringCloud整合sentinel问题汇总_第16张图片
    至此,监控数据的持久化基本完成,当然了,实际的数据存储还是以数据库为准,可以去看看数据库的表中是否有监控数据!如果说需要对监控数据做一些个性化的统计和归档的话,完全可以重新开发出几个模块来供自己使用,原理都差不多!
4.生产改造:持久化sentinel-dashboard规则【push模式】

此处我这里的实践主要是:管理规则及模式使用的是push模式,且将流控规则持久化到redis当中
官方文档中提到生产的实践时,反复提及push模式,只有这一种模式才适合生产的使用,并且可以保证数据的一致性与安全!

1. 介绍

工作纪实_25-SpringCloud整合sentinel问题汇总_第17张图片
push模式下的改造主要是依靠两个类

  • DynamicRuleProvider
  • DynamicRulePublisher
    工作纪实_25-SpringCloud整合sentinel问题汇总_第18张图片
2.重写推送规则
@Slf4j
@Component("redisRuleProvider")
public class RedisRuleProviderImpl implements DynamicRuleProvider<List<FlowRuleEntity>> {
     

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<FlowRuleEntity> getRules(String appName) throws Exception {
     
        if (StringUtil.isBlank(appName)) {
     
            return new ArrayList<>();
        }
        String rules = (String) redisTemplate.opsForValue().get(RedisConfig.ruleKey + appName);
        log.info("订阅规则:{}-{}", appName, rules);
        return JSONObject.parseArray(rules, FlowRuleEntity.class);
    }
}
package com.alibaba.csp.sentinel.dashboard.rule.impl;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.dashboard.rule.config.RedisConfig;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 发布规则
 *
 * @author liulei
 * @version 1.0
 */
@Slf4j
@Component("redisRulePublisher")
public class RedisRulePublisherImpl implements DynamicRulePublisher<List<FlowRuleEntity>> {
     

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
     
        log.info("发布规则:{}-{}", app, rules);
        redisTemplate.opsForValue().set(RedisConfig.ruleKey + app, JSON.toJSONString(rules));
    }
}

再搭配一个redis乱码修正的bean,注入进去

@Configuration
public class RedisConfig {
     

    public static final String ruleKey = "sentinel.rules.flow.ruleKey";

    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
     
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
    }

}
3.修改规则的存储方式为redis
/*
 * 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 com.alibaba.fastjson.JSON;
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);

    // 内存存储repo,改成redis存储规则后,它的作用主要是协助redis做规则的存储
    @Autowired
    private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;

    // =====改造新增代码开始=====
    @Autowired
    @Qualifier("redisRuleProvider")
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("redisRulePublisher")
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
    // =====改造新增代码结束=====

    @Autowired
    private SentinelApiClient sentinelApiClient;

    @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);
            // =====改造新增代码开始=====[如果非得和机器挂钩,可以使用组合key来区分]
            List<FlowRuleEntity> rules = ruleProvider.getRules(app);
            if (rules != null && !rules.isEmpty()) {
     
                for (FlowRuleEntity entity : rules) {
     
                    entity.setApp(app);
                    if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
     
                        entity.setId(entity.getClusterConfig().getFlowId());
                    }
                }
            }
            // =====改造新增代码结束=====
            // 存储到内存
            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);

            boolean publishRules = publishRules(entity.getApp(), entity.getIp(), entity.getPort());
            if (!publishRules) {
     
                logger.info("publish flow rules fail after rule query");
            }
            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");
            }

            boolean publishRules = publishRules(entity.getApp(), entity.getIp(), entity.getPort());
            if (!publishRules) {
     
                logger.info("publish flow rules fail after rule save.json");
            }
            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 {
     
            boolean publishRules = publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort());
            if (!publishRules) {
     
                logger.info("publish flow rules fail after rule delete");
            }
            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 publishRules(String app, String ip, Integer port) {
     
//        List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
//        return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);
//    }
    // 新增代码
    private boolean publishRules(String app, String ip, Integer port) {
     
        //核心代码,sentinel-dashboard通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中
        List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
        try {
     
            // 再推送到redis中
            rulePublisher.publish(app, rules);
            logger.info("添加限流规则成功{}", JSON.toJSONString(rules));
        } catch (Exception e) {
     
            e.printStackTrace();
            logger.warn("publishRules failed", e);
            return false;
        }
        return sentinelApiClient.setFlowRuleOfMachine(app, ip, port, rules);
    }
}

启动项目,重新测试,即可!
降级、热点、授权,除了降级还稍微有点改造的必要以外,其他的暂时先不改造了,授权的话,无关人员没必要让他登录,降级的话,原理和规则的改造类似,DegradeController找到这个,咔咔按照监控的存储一样类似的改造即可!如果连这个都自己处理不了的话,那么我的文章说明你也没有看懂,那就多看两次,哈哈哈

你可能感兴趣的:(工作纪实)