根据官方wiki文档,sentinel控制台的实时监控数据,默认仅存储 5 分钟以内的数据。如需持久化,需要定制实现相关接口。

https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel-控制台 也给出了指导步骤:

1.自行扩展实现 MetricsRepository 接口;

2.注册成 Spring Bean 并在相应位置通过 @Qualifier 注解指定对应的 bean name 即可。

本文先学习官方提供的接口梳理思路,然后使用Spring Data JPA编写一个MySQL存储实现。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

首先看接口定义:

repository.metric包下的MetricsRepository<T>接口

该接口定义了4个方法,分别用于保存和查询sentinel的metric数据。注释其实很清楚了,这里简单过一下:

save:保存单个metric

saveAll:保存多个metric

queryByAppAndResourceBetween:通过应用名名称、资源名称、开始时间、结束时间查询metric列表

listResourcesOfApp:通过应用名称查询资源列表

注:发现跟接口定义跟Spring Data JPA用法很像,即某个实体类Xxx对应一个XxxRepository,方法的命令也很规范,save、queryBy...

结合控制台【实时监控】菜单的界面,大概能猜到列表页面的查询流程:

菜单属于某一个应用,这里应用名称是sentinel-dashborad;

先通过应用名称查询应用下所有的资源,图中看到有2个,资源名称分别是/resource/machineResource.json、/flow/rules.json;// listResourcesOfApp方法

再通过应用名称、资源名称、时间等查询metric列表用于呈现统计图表;// queryByAppAndResourceBetween方法

在MetricsRepository类名上Ctrl+H查看类继承关系(Type Hiberarchy):

默认提供了一个用内存存储的实现类:InMemoryMetricsRepository

在MetricsRepository类的各个方法上,通过Ctrl+Alt+H 查看方法调用关系(Call Hierarchy) :

可以看到,MetricsRepository接口的

save方法被它的实现类InMemoryMetricsRepository的saveAll调用,再往上走被MetricFetcher调用,用于保存metric数据;

queryByAppAndResourceBetween、listResourcesOfApp被MetricController调用,用于查询metric数据;

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

OK,以上初步梳理了MetricsRepository接口的方法和流程,接下来我们使用MySQL数据库,实现一个MetricsRepository接口。

首先,参考MetricEntity类设计一张表sentinel_metric来存储监控的metric数据,表ddl如下:

-- 创建监控数据表
CREATE TABLE `sentinel_metric1` (
`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;

注:app、resource、timestamp在查询语句的where条件中用到,因此给它们建立索引提高查询速度;

count是MySQL的关键字,因此加上_前缀。

持久层选用Spring Data JPA框架,在pom中引入starter依赖:

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

在datasource.entity包下,新建jpa包,下面新建sentinel_metric表对应的实体类MetricPO:

package com.taobao.csp.sentinel.dashboard.datasource.entity.jpa;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date; /**
* @author cdfive
* @date 2018-09-14
*/
@Entity
@Table(name = "sentinel_metric")
public class MetricPO implements Serializable { private static final long serialVersionUID = 7200023615444172715L; /**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; // getter setter省略
}

该类也是参考MetricEntity创建,加上了JPA的注解,比如@Table指定表名,@Entity标识为实体,@Id、@GeneratedValue设置id字段为自增主键等;

在resources目录下的application.properties文件中,增加数据源和JPA(hibernate)的配置:

# datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=${spring.datasource.url}
spring.datasource.username=${spring.datasource.username}
spring.datasource.password=${spring.datasource.password} # spring data jpa
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.use-new-id-generator-mappings=false
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=false

这里数据库连接(url)、用户名(username)、密码(password)用${xxx}占位符,这样可以通过maven的pom.xml添加profile配置不同环境(开发、测试、生产) 或 从配置中心读取参数。

接着在InMemoryMetricsRepository所在的repository.metric包下新建JpaMetricsRepository类,实现MetricsRepository<MetricEntity>接口:

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

import com.alibaba.csp.sentinel.util.StringUtil;
import com.taobao.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.jpa.MetricPO;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors; /**
* @author cdfive
* @date 2018-09-17
*/
@Transactional
@Repository("jpaMetricsRepository")
public class JpaMetricsRepository implements MetricsRepository<MetricEntity> { @PersistenceContext
private EntityManager em; @Override
public void save(MetricEntity metric) {
if (metric == null || StringUtil.isBlank(metric.getApp())) {
return;
} MetricPO metricPO = new MetricPO();
BeanUtils.copyProperties(metric, metricPO);
em.persist(metricPO);
} @Override
public void saveAll(Iterable<MetricEntity> metrics) {
if (metrics == null) {
return;
} metrics.forEach(this::save);
} @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 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 = em.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;
} @Override
public List<String> listResourcesOfApp(String app) {
List<String> results = new ArrayList<>();
if (StringUtil.isBlank(app)) {
return results;
} StringBuilder hql = new StringBuilder();
hql.append("FROM MetricPO");
hql.append(" WHERE app=:app");
hql.append(" AND timestamp>=:startTime"); long startTime = System.currentTimeMillis() - 1000 * 60;
Query query = em.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);
} 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());
}
}

参考InMemoryMetricsRepository类来实现,将其中用map存储和查询的部分改为用JPA实现:

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

saveAll方法,循环调用save;

queryByAppAndResourceBetween、listResourcesOfApp编写查询即可。

最后一步,在MetricController、MetricFetcher两个类,找到metricStore属性,在@Autowired注解上面加上@Qualifier("jpaMetricsRepository")注解:

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

至此,监控数据MySQL持久化就完成了,得益于sentinel良好的Repository接口设计,是不是很简单:)

来验证下成果:

设置sentinel-dashboard工程启动参数:-Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard

启动工程,打开http://localhost:8080,查看不同的页面均显示正常,执行sql查询sentinel_metric表已有数据。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

总结:

个人感觉sentinel控制台默认的实现类InMemoryMetricsRepository挺赞的,虽然内存存储重启会清空数据,如果没有对历史数据查询的需求应用于生产环境是没问题的,

其中如何用内存存储,包括保存、查询以及排序等代码都值得学习;

对于监控数据,可能用MySQL关系数据库存储不太合适,虽然MySQL也可以通过事件或者任务定期清理;

数据定期清理、历史归档的需求,用时序数据库比如InfluxDB可能更适合。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

参考:

https://github.com/alibaba/Sentinel/wiki/控制台

https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel-控制台

sentinel控制台监控数据持久化【MySQL】的更多相关文章

  1. sentinel控制台监控数据持久化【InfluxDB】

    根据官方wiki文档,sentinel控制台的实时监控数据,默认仅存储 5 分钟以内的数据.如需持久化,需要定制实现相关接口. https://github.com/alibaba/Sentinel/ ...

  2. Sentinel上生产环境只差一步,监控数据持久化

    之前介绍了Sentinel相关的文章,小伙伴在生产实践中不知道有没有这个疑问?我们的Sentinel控制台监控的数据只能看最近5分钟的,如图 那么就导致历史数据是查看不了的,那肯定是不行的,在生产环境 ...

  3. SpringBoot 2.0 + InfluxDB+ Sentinel 实时监控数据存储

    前言 阿里巴巴提供的控制台只是用于演示 Sentinel 的基本能力和工作流程,并没有依赖生产环境中所必需的组件,比如持久化的后端数据库.可靠的配置中心等.目前 Sentinel 采用内存态的方式存储 ...

  4. 大家久等了,改造版阿里巴巴 sentinel 控制台终于开源了

    前言 最近几天,好几个小伙伴在后台询问,改造后的 sentinel-dashboard 什么时候开源.讲真,不是不想给大家放出来,是因为一些地方还没有完善好,怕误导了大家,在经过了一个星期业余时间的努 ...

  5. Sentinel: 接入控制台实时查看监控数据

    Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理.监控(单机和集群),规则管理和推送的功能. 比如我们之前是直接在代码中初始限流的值,接入控制台后可以直接通过控制台进行限流 ...

  6. Docker数据持久化及实战(Nginx+Spring Boot项目+MySQL)

    Docker数据持久化: Volume: (1)创建mysql数据库的container docker run -d --name mysql01 -e MYSQL_ROOT_PASSWORD= my ...

  7. Named Volume 在 MySQL 数据持久化上的基本应用

    原文作者:春哥 初识春哥时,春哥是美术设计大咖.后不久,创业并致力于游戏开发,已有3年.从Unity3D到IOS(Swift)开发,从前端开发到后端以及容器技术,从设计开发测试到产品经理以及CEO,已 ...

  8. dcoker 安装mysql和数据持久化

    # mysql 镜像下载 docker pull mysql ( 版本自己选择)   # run 服务 docker run --name mysql.5.5 -p 3306:3306 -e MYSQ ...

  9. docker mysql 数据持久化到本地、设置不区别表名大小写-清风柳絮-51CTO博客

    原文:docker mysql 数据持久化到本地.设置不区别表名大小写-清风柳絮-51CTO博客 Docker MySQL 把数据存储在本地目录,很简单,只需要映射本地目录到容器即可 1.加上-v参数 ...

随机推荐

  1. django inclusion用法

    概述: inclusion主要的是生成html标签, 返回的是一个字典,大分部跟simple_tag类似, simple_tag可返回任意类型的值 定义inclusion from django im ...

  2. Flutter 38: 图解 Flutter 基本动画 (二)

    小菜前两天学习了以下 Animation 的基本动画,接下来小菜学习以下稍微进阶版的 Animation 动画. 复合动画 小菜前两天学习的主要是基本的单一动画,当然多个动画效果集一身也是毫无问题的, ...

  3. echo打印换行

    shell环境中,echo是常用的数据命令,但有的时候,想通过“\n”使输出换行却换不了,这个时候需要增加-e选项: $ echo "Hellow.\nHey man~" Hell ...

  4. ThinkPHP模板继承和修改title

    先说下模板继承: 假定:在View文件夹中 -> Public  公共模块 —>base/header/top/footer 4个html文件 这下面base文件使用include引入其他 ...

  5. 看一下我学习linux的过程

    学习Linux的最佳方法是将它用于日常工作. 阅读Linux书籍,观看Linux视频不仅仅是足够的. 学习Linux没有捷径可走. 你不可能在一夜之间在Linux中掌握. 这需要时间和持久性. 刚刚潜 ...

  6. DA_01_linux_物理机局域网工作机制

    一:物理机局域网工作机制: 二:域名服务的工作流程: 首先通过域名映射到IP地址,如果没有找到域名,向外部服务器DNS查询,然后通过IP地址访问服务器: 三:Vmware--NAT虚拟网络配置:

  7. Loadrunner12-思考时间设置

    1.什么是思考时间 简单来说就是可以在不同的操作之间做停顿,最大程度的模拟用户最真实的操作. 2.如何设置思考时间 函数:lr_think_time(4) 进入Runtime Settings页面,快 ...

  8. Xftp6 和 Xshell 6 下载与安装使用

    官网地址:https://www.netsarang.com/zh/all-downloads/ 然后进行安装吧,基本没有什么配置. 打开 xftp 连接 Linux,进行文件操作. 然后 名称:随意 ...

  9. python之抽象类&abc模块+虚拟子类&register

    抽象类和接口: java 我们先从java讲起,没有java基础的可以略过. (挖坑) python 在python并没有抽象类之说,或者说抽象类=接口类(区别于接口) 继承有两种用途: 一:继承基类 ...

  10. curl查看http请求消息的时长

    1. -X 指定请求方式GET请求curl -X GET http://www.jackyops.com/search?data=123  # -X GET是可选的 POST请求curl -X POS ...