前言

根据官方提供的wiki文档,sentinel控制台的实时监控数据,默认提供的存储数据时间为 5 分钟以内的数据。如果我们的需求是需要持久化的,那就需要我们自己定制实现相关的接口。
https://github.com/alibaba/Sentinel/wiki/%E5%AE%9E%E6%97%B6%E7%9B%91%E6%8E%A7


本文只做一个持久化基于MySQL的方式,对于监控数据,可能用MySQL关系数据库存储不太合适,虽然MySQL也可以通过事件或者任务定期清理或者通过代码定时的清理。
数据定期清理、历史归档的需求,用时序数据库比如InfluxDB可能更适合。
其实有些人说,如果我对历史记录不需要查询的需求,我想说,你没有要求,你搞这个是干啥呢?难道就为了个配置注册中心?历史记录,如同日志,方便排查问题。
基于InfluxDB后续补上,我们也学习一波时序数据库。

一:实时监控

1:阅读官方wiki文档之后,我们理了下面如下步骤:

1.1:需自己扩展实现 MetricsRepository 接口;

1.2:需要注册成 Spring Bean ,并在相应位置通过 @Qualifier 注解指定对应的 bean name 即可。

我们先打开源代码看看这个MetricsRepository接口是如何定义的

com.alibaba.csp.sentinel.dashboard.repository.metric包下的MetricsRepository接口

1.3 接口方法

我们仔细阅读,会发现当前接口定义了4个方法,分别用于查询和保存sentinel的metric数据。方法的注释写的很清楚了,这里还是需要简单过一下:

1.3.1 方法简述

save:保存单个metric
saveAll:保存多个metric
queryByAppAndResourceBetween:通过应用名名称、资源名称、开始时间、结束时间查询metric列表
listResourcesOfApp:通过应用名称查询资源列表

了解过以及实践过Spring data jpa的,估计看这接口的方法定义,和Spring data jpa 很像,即某个实体类xxx对应一个xxxRepository,方法的命令也很规范,save、queryBy…。
本文就基于Spring data jpa来实现基于MySQL持久化

2:控制台

2.1 查看控制台

我们先启动我们自定义的sentinel-dashboard的项目,启动之后,我们访问下控制台

我们现在需要跟控制台结合,看看《实时监控》菜单的界面,大概的可以猜到列表页面的查询流程是怎样的。

菜单属于某一个应用,这里应用名称是sentinel-dashboard;
我们先通过应用名称查询应用下所有的资源,图中看到有2个,资源名称分别是/resource/machineResource.json、/flow/rules.json;
// listResourcesOfApp方法
再通过应用名称、资源名称、时间等查询metric列表用于呈现统计图表;
// queryByAppAndResourceBetween方法

2.2 阅读源码的实现类

我们回到代码,我们用Ctrl+H 看看当前接口的实现类

我们看的默认有一个用内存存储的实现类:InMemoryMetricsRepository

2.3 调用关系

我们再看看MetricsRepository的方法调用
在各个方法上,通过Ctrl+Alt+H 查看方法调用关系:

2.3.1 save

2.3.2 saveAll

2.3.3 queryByAppAndResourceBetween

2.3.4 listResourcesOfApp


查看完4个方法的调用关系,可以看到,MetricsRepository接口的方法
          保存:save方法被它的实现类InMemoryMetricsRepository的saveAll调用,再往上走被MetricFetcher调用,用于保存metric数据;
          查询:queryByAppAndResourceBetween、listResourcesOfApp被MetricController调用,用于查询metric数据;

如上,就是基本梳理了MetricsRepository接口的4个方法和调用流程,现在接下来我们就要使用MySQL数据库持久化,实现一个MetricsRepository接口。

二:创建数据库表

我们先看看源码MetricsRepository,既然是泛型

我们看看实现类

我们进入当前实体类,模仿着MetricEntity类搞一个表

表DDL如下:
CREATE TABLE `sentinel_metric` (`id` INT NOT NULL AUTO_INCREMENT COMMENT 'id,主键',`gmt_create` DATETIME COMMENT '创建时间',`gmt_modified` DATETIME COMMENT '修改时间',`app` VARCHAR(100) COMMENT '应用名称',`timestamp` DATETIME COMMENT '统计时间',`resource` VARCHAR(500) COMMENT '资源名称',`pass_qps` INT COMMENT '通过qps',`success_qps` INT COMMENT '成功qps',`block_qps` INT COMMENT '限流qps',`exception_qps` INT COMMENT '发送异常的次数',`rt` DOUBLE COMMENT '所有successQps的rt的和',`_count` INT COMMENT '本次聚合的总条数',`resource_code` INT COMMENT '资源的hashCode',INDEX app_idx(`app`) USING BTREE,INDEX resource_idx(`resource`) USING BTREE,INDEX timestamp_idx(`timestamp`) USING BTREE,PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;


提示:细心查看DDL,发现我们给app(应用名称),resource(资源名称),timestamp加上了索引,因为这3个条件在where 条件中使用到,添加索引增加查询速度;再仔细阅读,为什么count加上了_前缀符号,因为count是mysql关键字,记住,字段名不要瞎JB取。

三:编写接口

首先我们引入spring data jpa 依赖,pom文件添加如下依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId><version>${spring.boot.version}</version>
</dependency>

3.1 新建实体类

我是把实体类新建到与实现类的实体类对应的目录

import org.springframework.data.annotation.Id;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import java.io.Serializable;
import java.util.Date;/*** @author Gaci* @className MetricPO* @description 我没有加lombok插件,所以我自己提供了getter和setter方法* @date 2021/5/8 11:03**/
@Entity
@Table(name = "sentinel_metric")
public class MetricPO implements Serializable {private static final long serialVersionUID = 6900023615444171359L;/** id,主键 */@Id@GeneratedValue@Column(name = "id")private Long id;/** 创建时间 */@Column(name = "gmt_create")private Date gmtCreate;/** 修改时间 */@Column(name = "gmt_modified")private Date gmtModified;/** 应用名称 */@Column(name = "app")private String app;/** 统计时间 */@Column(name = "timestamp")private Date timestamp;/** 资源名称 */@Column(name = "resource")private String resource;/** 通过QPS */@Column(name = "pass_qps")private Long passQps;/** 成功QPS */@Column(name = "success_qps")private Long successQps;/** 限流QPS */@Column(name = "block_qps")private Long blockQps;/** 发送异常的次数 */@Column(name = "exception_qps")private Long exceptionQps;/** 全部successQPS的rt的和 */@Column(name = "rt")private Double rt;/** 本次聚合的总条数 */@Column(name = "_count")private Integer count;/** 资源的hashCode值 */@Column(name = "resource_code")private Integer resourceCode;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;}public Long getPassQps() {return passQps;}public void setPassQps(Long passQps) {this.passQps = passQps;}public Long getSuccessQps() {return successQps;}public void setSuccessQps(Long successQps) {this.successQps = successQps;}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 Integer getCount() {return count;}public void setCount(Integer count) {this.count = count;}public Integer getResourceCode() {return resourceCode;}public void setResourceCode(Integer resourceCode) {this.resourceCode = resourceCode;}

上述的属性都是参考默认实现类InMemoryMetricsRepository的实体类MetricEntity定义的

这里简述一下注解的作用
自定义的实体类加上了JPA的注解,
@Table指定表名,@Entity标识为实体,@Id、@GeneratedValue设置id字段为自增主键,Column列名的映射;

3.2 配置文件配置数据库属性


增加数据源和JPA(hibernate)的配置:

提示:如提示cannot reslove Driver xxxxx信息

则需要引入依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.38</version>
</dependency>

3.3 编写实现类

在InMemoryMetricsRepository所在的com.alibaba.csp.sentinel.dashboard.repository.metric包下新建一个JpaCustomeMetricsRepository类,实现MetricsRepository接口:

然后重写接口的4个方法

实际实现方法后,归根究底就是把存在map的,改成jpa,存储在MySQL数据库。
我们先模仿实现类重新4个方法

3.3.1 方法

先添加两个注解和引入entityManager

简述:

@Transactional 事务注解
@Repository       @Repository、@Service、@Controller 和 @Component 将类标识为Bean
@PersistenceContext
这个是JPA中的注解,PersistenceContext,称为持久化上下文,它一般包含有当前事务范围内的,被管理的实体对象(Entity)的数据。每个EntityManager,都会跟一个PersistenceContext相关联。PersistenceContext中存储的是实体对象的数据,而关系数据库中存储的是记录。
@PersistenceContext与@Resource区别
@PersistenceContext
注入的是实体管理器,执行持久化操作的,需要配置文件persistence.xml。
注入一堆保存实体类状态的数据结构,针对实体类的不同状态(四种,managedh或detached等)可以做出不同的反应(merge,persist等等),其实就是把数据从数据库里提出,然后在内存里处理的,再返回数据库的法则。
@Resource
是注入容器提供的资源对象,比如SessionContext MessageDrivenContext。或者你那个name指定的JNDI对象,可以理解为资源->数据源->也就是数据连接,基本上就是告诉程序数据库在哪里

save方法:将MetricEntity转换为MetricPO类,调用EntityManager类的persist方法即可;

@Override
public void save(MetricEntity metric) {// 判断是否为空,或者应用名称为空if (metric == null || StringUtil.isBlank(metric.getApp())) {return;}// 不为空直接赋值,这里为了快速集成,不考虑性能优化// 赋值优化,见文章:十万个为什么不推荐使用BeanUtils属性转换工具// CSDN博客链接:https://blog.csdn.net/BugRoot/article/details/116270539MetricPO metricPO = new MetricPO();BeanUtils.copyProperties(metric, metricPO);// 持久化实体persist():往数据表中插入数据。entityManager.persist(metricPO);
}saveAll方法:循环调用save就好了;
@Override
public void saveAll(Iterable<MetricEntity> metrics) {// 判断是否为空if(metrics == null){return;}// forEach方法就是Java8新增的,它接受一个Consumer对象,是一个消费者类型的函数式接口。// 根据注释我们已经大概清楚了,这边简述下// forEach(this:save);// this代表我们saveAll方法重点metrics,save代表我们forEach源码循环体当前需要调用的方法// 进入foeEach源码// 根据源码我们可以清晰看出,Consumer<? super T>action// this就是metrics,它在for循环,然后action对应就是save方法,// 相当于for循环metrics,然后循环体执行save方法,方法参数体都传入每一个实体类。metrics.forEach(this::save);
}


这里面我们使用了JDK1.8的函数式接口。
我们既然用到了,也讲讲这个函数式接口,直接上源码。
根据注释我们已经大概清楚了,这边简述下
forEach(this:save);
this代表我们saveAll方法重点metrics,save代表我们forEach源码循环体当前需要调用的方法

根据源码我们可以清晰看出,Consumer<? super T>action
this就是metrics,它在for循环,然后action对应就是save方法,
相当于for循环metrics,然后循环体执行save方法,方法参数体都传入每一个实体类。

queryByAppAndResourceBetween、listResourcesOfApp编写查询即可。

queryByAppAndResourceBetween

@Override
public List<MetricEntity> queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime) {// 定义实时监控数据列表,以便于封装后数据存储List<MetricEntity> results = new ArrayList<MetricEntity>();// 如果应用名称为空,则返回空集合if (StringUtil.isBlank(app)) {return results;}// 如果资源名称为空,则返回空集合if (StringUtil.isBlank(resource)) {return results;}// 这里使用StringBuilder作为拼接,String,StringBuffer,StringBuilder区别以及性能,请百度。// 这里留一个东西,Java8中的StringJoiner,嘻嘻// 每一行代码都是质量,当你写出高质量的代码,你就可以和大牛平齐了。// 拼接SQLStringBuilder hql = new StringBuilder();hql.append("FROM MetricPO");hql.append(" WHERE app=:app");hql.append(" AND resource=:resource");hql.append(" AND timestamp>=:startTime");hql.append(" AND timestamp<=:endTime");// 执行查询语句封装Query query = entityManager.createQuery(hql.toString());query.setParameter("app", app);// 应用名称query.setParameter("resource", resource);// 资源名称query.setParameter("startTime", Date.from(Instant.ofEpochMilli(startTime)));// 查询时间起始query.setParameter("endTime", Date.from(Instant.ofEpochMilli(endTime)));// 查询时间终止List<MetricPO> metricPOs = query.getResultList();// 查出的数据如果为空,则返回空集合。if (CollectionUtils.isEmpty(metricPOs)) {return results;}// 遍历赋值for (MetricPO metricPO : metricPOs) {MetricEntity metricEntity = new MetricEntity();BeanUtils.copyProperties(metricPO, metricEntity);results.add(metricEntity);}return results;
}

listResourcesOfApp

@Override
public List<String> listResourcesOfApp(String app) {List<String> results = new ArrayList<>();// 应用名称为空,则返回空集合if (StringUtil.isBlank(app)) {return results;}// 这里使用StringBuilder作为拼接,String,StringBuffer,StringBuilder区别以及性能,请百度。// 这里留一个东西,Java8中的StringJoiner,嘻嘻// 每一行代码都是质量,当你写出高质量的代码,你就可以和大牛平齐了。// 拼接SQLStringBuilder hql = new StringBuilder();hql.append("FROM MetricPO");hql.append(" WHERE app=:app");hql.append(" AND timestamp>=:startTime");// 执行查询语句// Instant.ofEpochMilli(long xxx)// 参数:此方法接受一个参数epochMilli是从1970-01-01T00:00:00Z开始的毫秒值。// 返回值:此方法返回从纪元开始的时间(以毫秒为单位)。final long startTime = System.currentTimeMillis() - 1000 * 60;Query query = entityManager.createQuery(hql.toString());query.setParameter("app", app);query.setParameter("startTime", Date.from(Instant.ofEpochMilli(startTime)));List<MetricPO> metricPOs = query.getResultList();// 如果返回空,则返回空集合if (CollectionUtils.isEmpty(metricPOs)) {return results;}// 定义资源数据列表,以便于封装后数据存储List<MetricEntity> metricEntities = new ArrayList<MetricEntity>();// 遍历赋值for (MetricPO metricPO : metricPOs) {MetricEntity metricEntity = new MetricEntity();BeanUtils.copyProperties(metricPO, metricEntity);metricEntities.add(metricEntity);}// 如下代码重写MetricsRepository接口,模仿InMemoryMetricsRepository实现编写Map<String, MetricEntity> resourceCount = new HashMap<>(32);for (MetricEntity metricEntity : metricEntities) {String resource = metricEntity.getResource();if (resourceCount.containsKey(resource)) {MetricEntity oldEntity = resourceCount.get(resource);oldEntity.addPassQps(metricEntity.getPassQps());oldEntity.addRtAndSuccessQps(metricEntity.getRt(), metricEntity.getSuccessQps());oldEntity.addBlockQps(metricEntity.getBlockQps());oldEntity.addExceptionQps(metricEntity.getExceptionQps());oldEntity.addCount(1);} else {resourceCount.put(resource, MetricEntity.copyOf(metricEntity));}}// Order by last minute b_qps DESC.return resourceCount.entrySet().stream().sorted((o1, o2) -> {MetricEntity e1 = o1.getValue();MetricEntity e2 = o2.getValue();int t = e2.getBlockQps().compareTo(e1.getBlockQps());if (t != 0) {return t;}return e2.getPassQps().compareTo(e1.getPassQps());}).map(Map.Entry::getKey).collect(Collectors.toList());
}


见:JDK1.8特性之StringJoiner
4个方法目前已经实现完了。
4个方法都是参考MetricsRepository接口的实现类InMemoryMetricsRepository进行编写
大致讲下:
save方法存储的时候转储为我们自己定义的MetricPo实体类,调用EntityManager类的persist方法进行插入数据
saveAll方法就是循环插入
queryByAppAndResourceBetween、listResourcesOfApp这两个方法,主要模仿InMemoryMetricsRepository对应的接口进行编写就好了。

接口写完了我们还得找到源头,重写了接口,相当于一个接口对应两个实现类,那这个接口,它要找哪个实现类好呢,所以我们要去controller源头,给注入的接口指定实现类。
这个就是spring的基础了,需要指定实现类。

3.4 controller

com.alibaba.csp.sentinel.dashboard.controller包下的MetricController
一打开,我们就看到如下的错误提示。


需要添加如下的注解,对应的bean名称就是我们自己定义的实现类

3.5 MetricFetcher

如下也注入了

我们也要在这里注入添加注解,指定我们的实现类

3.6 启动

打包启动
进入我们打包好的目录

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject=peng-sentinel -jar sentinel-dashboard.jar

既然报错了
import org.springframework.data.annotation.Id;
仔细看,实体类。我既然引错包了,
把这个注解删掉就好了。
再次打包,启动

好了,我们先去页面看看
由于我为了方便展示,我直接代码先修改下


瞬间10分钟内的数据也展示了,如果我们需要根据时间,各种条件来搜索,那我们需要修改源码代码的页面和controller,那个自行实现了。

我们还得去看看数据库有没有值,然后我们重启sentinel控制台,看数据是否存在

有数据,说明正常集成

告一段落了,集成InfluxDB时序数据库后续加上

Sentinel(第三篇)_Springboot2.x+Sentinel监控信息基于MySQL持久化相关推荐

  1. python调用libvirt_通过python获取kvm虚拟机的监控信息(基于libvirt API)

    通常在我们的云环境中,为了保证云平台中虚拟机的正常运行,基本都需要这样一个功能,就是收集虚拟机的监控数据,比如cpu的使用率.内存的使用率.磁盘io.网络io等基本信息.可以利用这些信息及时调整云平台 ...

  2. 编程速记(39):Matlab篇-提取图像高频/低频信息-基于DCT

    一.简述 希望通过离散余弦变换获取某张图片的高频/低频信息 二.Demo 转灰图处理方式 RGB = imread('autumn.tif'); I = rgb2gray(RGB); J = dct2 ...

  3. nagios mysql入库_使用NDOUtils将Nagios监控信息存入Mysql

    官方的一些简介: NDOUtilsest un addon pour. Il permet de stocker dans une base de donnéesou dans un fichier  ...

  4. mysql 热切换_热璞HotDB学习篇—如何基于MySQL进行数据高可用

    数据高可用之所以是老生常谈的话题,是因它对企业数据安全起到了至关重要的保障作用,数据高可用核心功能在于如何保证在发生故障时数据不丢失.本文作者热璞数据库HotDB首席架构师,精通数据库原理和MySQL ...

  5. Sentinel(三)之如何使用

    转载自  Sentinel如何使用 简介 Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard.核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好 ...

  6. 【转】Redis 分布式——可用性保证之 Sentinel(实战篇)

    前言 在上个篇章我们阐述了Sentinel的原理,可能大家还是云里雾里,需要来点实战性的东西,那这个篇章我们来个实战篇吧-话不多说,我们开始今天的吹牛皮- 正文 Sentinel 实战 Sentine ...

  7. 第三篇——第二部分——第六文 监控SQL Server镜像

    原文: 第三篇--第二部分--第六文 监控SQL Server镜像 原文出处:http://blog.csdn.net/dba_huangzj/article/details/26846203 要优化 ...

  8. Sentinel(十二)之实时监控

    转载自  实时监控 Sentinel 提供对所有资源的实时监控.如果需要实时监控,客户端需引入以下依赖(以 Maven 为例): <dependency><groupId>co ...

  9. hadoop作业初始化过程详解(源码分析第三篇)

    (一)概述 我们在上一篇blog已经详细的分析了一个作业从用户输入提交命令到到达JobTracker之前的各个过程.在作业到达JobTracker之后初始化之前,JobTracker会通过submit ...

最新文章

  1. 为什么会需要HTTPS?
  2. mysql常用语句整理
  3. 使用js实现放大镜效果
  4. 【转】Magento2 数据库操作
  5. 我的世界服务器自定义怪物怎么用,我的世界 教你自定义怪物属性
  6. netsh 禁用端口
  7. POJ3159-Candies
  8. hive udf开发超详细手把手教程
  9. 一个很难的sql面试题
  10. caj安装,win10,错误1372。无效驱动器:D:\ (解决方案大全总结)
  11. Linux消息队列及函数详解(含示例)
  12. 为什么mysql填不了数据库_求助,为何我的数据不能写入数据库
  13. 联通光纤猫虚拟服务器设置,联通光猫连接无线路由器怎么设置?
  14. FFCreator制作视频(合成视频)
  15. 【资讯】2017年乌镇互联网大会,互联网大佬们齐聚首
  16. main主函数参数解析
  17. 基于多种群机制的PSO算法Python实现
  18. Android头像上传实战模拟
  19. TP5.1 +LayuiAdmin 富文本使用教程
  20. 「图片版」ps常用快捷键大全,设计师进阶之路

热门文章

  1. EasyNVR 使用记录
  2. CVE-2022-39227jwt伪造
  3. HTML 关于hr标签,虚线
  4. [数值分析]不动点迭代法
  5. linux 配置 MP3 RMVB 解码器_Andy_Issta_新浪博客
  6. VS Code 这么牛,再次印证了一句名言
  7. 中本聪在2009年挖到了100万枚BTC?
  8. android将代码提交到github,[Android Studio使用][将AS上的代码提交到github]
  9. 设计模式之适配器模式 :外部系统接口的使用
  10. 【已测】仿bing搜索蓝色海风清爽导航网站源码,单页