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

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

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

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

本文使用时序数据库InfluxDB来进行持久化,从下载开始,一步步编写一个基于InfluxDB的存储实现。

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

InfluxDB官网:https://www.influxdata.com

关键词:

高性能时序数据库

go语言编写没有外部依赖

支持HTTP API读写

支持类SQL查询语法

通过数据保留策略(Retention Policies)支持自动清理历史数据

通过连续查询(Continuous Queries)支持数据归档

最新版本:1.6.4

下载

windows:wget https://dl.influxdata.com/influxdb/releases/influxdb-1.6.4_windows_amd64.zip

linux:wget https://dl.influxdata.com/influxdb/releases/influxdb-1.6.4_linux_amd64.tar.gz

注:windows下载安装wget  https://eternallybored.org/misc/wget/

在windows环境,解压zip文件至D:\influxdb\influxdb-1.6.4-1目录:

打开cmd命令行窗口,在D:\influxdb\influxdb-1.6.4-1执行命令启动influxdb服务端:influxd

再打开一个cmd窗口,在目录下输入influx启动客户端: // 后面可以带上参数:-precision rfc3339 指定时间格式显示

show databases发现只有系统的2个数据库,这里我们新建一个sentinel_db,输入命令:create database sentinel_db

use sentinel_db  使用sentinel_db数据库

show measurements  查看数据库中的数据表(measurement)

可以看到,这几个InfluxDB命令跟MySQL很相似。

==============================================================

InfluxDB名词概念:

database:数据库 // 关系数据库的database

measurement:数据库中的表 // 关系数据库中的table

point:表里的一行数据 // 关系数据库中的row

point由3部分组成:

time:每条数据记录的时间,也是数据库自动生成的主索引;// 类似主键

fields:各种记录的值;// 没有索引的字段

tags:各种有索引的属性 // 有索引的字段

==============================================================

在官方github上,有一个java的客户端库:

https://github.com/influxdata/influxdb-java

在sentinel-dashboard的pom.xml中,加入maven依赖:

<dependency>
<groupId>org.influxdb</groupId>
<artifactId>influxdb-java</artifactId>
<version>2.14</version>
</dependency>

封装一个工具类:存储InfluxDB连接信息以及方便调用

/**
* @author cdfive
* @date 2018-10-19
*/
@Component
public class InfluxDBUtils { private static Logger logger = LoggerFactory.getLogger(InfluxDBUtils.class); private static String url; private static String username; private static String password; private static InfluxDBResultMapper resultMapper = new InfluxDBResultMapper(); @Value("${influxdb.url}")
public void setUrl(String url) {
InfluxDBUtils.url = url;
} @Value("${influxdb.username}")
public void setUsername(String username) {
InfluxDBUtils.username = username;
} @Value("${influxdb.password}")
public void setPassword(String password) {
InfluxDBUtils.password = password;
} public static void init(String url, String username, String password) {
InfluxDBUtils.url = url;
InfluxDBUtils.username = username;
InfluxDBUtils.password = password;
} public static <T> T process(String database, InfluxDBCallback callback) {
InfluxDB influxDB = null;
T t = null;
try {
influxDB = InfluxDBFactory.connect(url, username, password);
influxDB.setDatabase(database); t = callback.doCallBack(database, influxDB);
} catch (Exception e) {
logger.error("[process exception]", e);
} finally {
if (influxDB != null) {
try {
influxDB.close();
} catch (Exception e) {
logger.error("[influxDB.close exception]", e);
}
}
} return t;
} public static void insert(String database, InfluxDBInsertCallback influxDBInsertCallback) {
process(database, new InfluxDBCallback() {
@Override
public <T> T doCallBack(String database, InfluxDB influxDB) {
influxDBInsertCallback.doCallBack(database, influxDB);
return null;
}
}); } public static QueryResult query(String database, InfluxDBQueryCallback influxDBQueryCallback) {
return process(database, new InfluxDBCallback() {
@Override
public <T> T doCallBack(String database, InfluxDB influxDB) {
QueryResult queryResult = influxDBQueryCallback.doCallBack(database, influxDB);
return (T) queryResult;
}
});
} public static <T> List<T> queryList(String database, String sql, Map<String, Object> paramMap, Class<T> clasz) {
QueryResult queryResult = query(database, new InfluxDBQueryCallback() {
@Override
public QueryResult doCallBack(String database, InfluxDB influxDB) {
BoundParameterQuery.QueryBuilder queryBuilder = BoundParameterQuery.QueryBuilder.newQuery(sql);
queryBuilder.forDatabase(database); if (paramMap != null && paramMap.size() > 0) {
Set<Map.Entry<String, Object>> entries = paramMap.entrySet();
for (Map.Entry<String, Object> entry : entries) {
queryBuilder.bind(entry.getKey(), entry.getValue());
}
} return influxDB.query(queryBuilder.create());
}
}); return resultMapper.toPOJO(queryResult, clasz);
} public interface InfluxDBCallback {
<T> T doCallBack(String database, InfluxDB influxDB);
} public interface InfluxDBInsertCallback {
void doCallBack(String database, InfluxDB influxDB);
} public interface InfluxDBQueryCallback {
QueryResult doCallBack(String database, InfluxDB influxDB);
}
}

其中:

url、username、password用于存储InfluxDB的连接、用户名、密码信息,定义为static属性,因此在set方法上使用@Value注解从配置文件读取属性值;

resultMapper用于查询结果到实体类的映射;

init方法用于初始化url、username、password;

process为通用的处理方法,负责打开关闭连接,并且调用InfluxDBCallback回调方法;

insert为插入数据方法,配合InfluxDBInsertCallback回调使用;

query为通用的查询方法,配合InfluxDBQueryCallback回调方法使用,返回QueryResult对象;

queryList为查询列表方法,调用query得到QueryResult,再通过resultMapper转换为List<实体类>;

在resources目录下的application.properties文件中,增加InfluxDB的配置:

influxdb.url=${influxdb.url}
influxdb.username=${influxdb.username}
influxdb.password=${influxdb.password}

用${xxx}占位符,这样可以通过maven的pom.xml添加profile配置不同环境(开发、测试、生产) 或 从配置中心读取参数。

在datasource.entity包下,新建influxdb包,下面新建sentinel_metric数据表(measurement)对应的实体类MetricPO:

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

import org.influxdb.annotation.Column;
import org.influxdb.annotation.Measurement; import java.time.Instant; /**
* @author cdfive
* @date 2018-10-19
*/
@Measurement(name = "sentinel_metric")
public class MetricPO { @Column(name = "time")
private Instant time; @Column(name = "id")
private Long id; @Column(name = "gmtCreate")
private Long gmtCreate; @Column(name = "gmtModified")
private Long gmtModified; @Column(name = "app", tag = true)
private String app; @Column(name = "resource", tag = true)
private String resource; @Column(name = "passQps")
private Long passQps; @Column(name = "successQps")
private Long successQps; @Column(name = "blockQps")
private Long blockQps; @Column(name = "exceptionQps")
private Long exceptionQps; @Column(name = "rt")
private double rt; @Column(name = "count")
private int count; @Column(name = "resourceCode")
private int resourceCode; // getter setter省略
}

该类参考MetricEntity创建,加上influxdb-java包提供的注解,通过@Measurement(name = "sentinel_metric")指定数据表(measurement)名称,

time作为时序数据库的时间列;

app、resource设置为tag列,通过注解标识为tag=true;

其它字段为filed列;

接着在InMemoryMetricsRepository所在的repository.metric包下新建InfluxDBMetricsRepository类,实现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.influxdb.MetricPO;
import com.taobao.csp.sentinel.dashboard.util.InfluxDBUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;
import org.influxdb.InfluxDB;
import org.influxdb.dto.Point;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils; import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; /**
* metrics数据InfluxDB存储实现
* @author cdfive
* @date 2018-10-19
*/
@Repository("influxDBMetricsRepository")
public class InfluxDBMetricsRepository implements MetricsRepository<MetricEntity> { /**时间格式*/
private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; /**数据库名称*/
private static final String SENTINEL_DATABASE = "sentinel_db"; /**数据表名称*/
private static final String METRIC_MEASUREMENT = "sentinel_metric"; /**北京时间领先UTC时间8小时 UTC: Universal Time Coordinated,世界统一时间*/
private static final Integer UTC_8 = 8; @Override
public void save(MetricEntity metric) {
if (metric == null || StringUtil.isBlank(metric.getApp())) {
return;
} InfluxDBUtils.insert(SENTINEL_DATABASE, new InfluxDBUtils.InfluxDBInsertCallback() {
@Override
public void doCallBack(String database, InfluxDB influxDB) {
if (metric.getId() == null) {
metric.setId(System.currentTimeMillis());
}
doSave(influxDB, metric);
}
});
} @Override
public void saveAll(Iterable<MetricEntity> metrics) {
if (metrics == null) {
return;
} Iterator<MetricEntity> iterator = metrics.iterator();
boolean next = iterator.hasNext();
if (!next) {
return;
} InfluxDBUtils.insert(SENTINEL_DATABASE, new InfluxDBUtils.InfluxDBInsertCallback() {
@Override
public void doCallBack(String database, InfluxDB influxDB) {
while (iterator.hasNext()) {
MetricEntity metric = iterator.next();
if (metric.getId() == null) {
metric.setId(System.currentTimeMillis());
}
doSave(influxDB, metric);
}
}
});
} @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 sql = new StringBuilder();
sql.append("SELECT * FROM " + METRIC_MEASUREMENT);
sql.append(" WHERE app=$app");
sql.append(" AND resource=$resource");
sql.append(" AND time>=$startTime");
sql.append(" AND time<=$endTime"); Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("app", app);
paramMap.put("resource", resource);
paramMap.put("startTime", DateFormatUtils.format(new Date(startTime), DATE_FORMAT_PATTERN));
paramMap.put("endTime", DateFormatUtils.format(new Date(endTime), DATE_FORMAT_PATTERN)); List<MetricPO> metricPOS = InfluxDBUtils.queryList(SENTINEL_DATABASE, sql.toString(), paramMap, MetricPO.class); if (CollectionUtils.isEmpty(metricPOS)) {
return results;
} for (MetricPO metricPO : metricPOS) {
results.add(convertToMetricEntity(metricPO));
} return results;
} @Override
public List<String> listResourcesOfApp(String app) {
List<String> results = new ArrayList<>();
if (StringUtil.isBlank(app)) {
return results;
} StringBuilder sql = new StringBuilder();
sql.append("SELECT * FROM " + METRIC_MEASUREMENT);
sql.append(" WHERE app=$app");
sql.append(" AND time>=$startTime"); Map<String, Object> paramMap = new HashMap<String, Object>();
long startTime = System.currentTimeMillis() - 1000 * 60;
paramMap.put("app", app);
paramMap.put("startTime", DateFormatUtils.format(new Date(startTime), DATE_FORMAT_PATTERN)); List<MetricPO> metricPOS = InfluxDBUtils.queryList(SENTINEL_DATABASE, sql.toString(), paramMap, MetricPO.class); if (CollectionUtils.isEmpty(metricPOS)) {
return results;
} List<MetricEntity> metricEntities = new ArrayList<MetricEntity>();
for (MetricPO metricPO : metricPOS) {
metricEntities.add(convertToMetricEntity(metricPO));
} 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());
} private MetricEntity convertToMetricEntity(MetricPO metricPO) {
MetricEntity metricEntity = new MetricEntity(); metricEntity.setId(metricPO.getId());
metricEntity.setGmtCreate(new Date(metricPO.getGmtCreate()));
metricEntity.setGmtModified(new Date(metricPO.getGmtModified()));
metricEntity.setApp(metricPO.getApp());
metricEntity.setTimestamp(Date.from(metricPO.getTime().minusMillis(TimeUnit.HOURS.toMillis(UTC_8))));// 查询数据减8小时
metricEntity.setResource(metricPO.getResource());
metricEntity.setPassQps(metricPO.getPassQps());
metricEntity.setSuccessQps(metricPO.getSuccessQps());
metricEntity.setBlockQps(metricPO.getBlockQps());
metricEntity.setExceptionQps(metricPO.getExceptionQps());
metricEntity.setRt(metricPO.getRt());
metricEntity.setCount(metricPO.getCount()); return metricEntity;
} private void doSave(InfluxDB influxDB, MetricEntity metric) {
influxDB.write(Point.measurement(METRIC_MEASUREMENT)
.time(DateUtils.addHours(metric.getTimestamp(), UTC_8).getTime(), TimeUnit.MILLISECONDS)// 因InfluxDB默认UTC时间,按北京时间算写入数据加8小时
.tag("app", metric.getApp())
.tag("resource", metric.getResource())
.addField("id", metric.getId())
.addField("gmtCreate", metric.getGmtCreate().getTime())
.addField("gmtModified", metric.getGmtModified().getTime())
.addField("passQps", metric.getPassQps())
.addField("successQps", metric.getSuccessQps())
.addField("blockQps", metric.getBlockQps())
.addField("exceptionQps", metric.getExceptionQps())
.addField("rt", metric.getRt())
.addField("count", metric.getCount())
.addField("resourceCode", metric.getResourceCode())
.build());
}
}

其中:

save、saveAll方法通过调用InfluxDBUtils.insert和InfluxDBInsertCallback回调方法,往sentinel_db库的sentinel_metric数据表写数据;

saveAll方法不是循环调用save方法,而是在回调内部循环Iterable<MetricEntity> metrics处理,这样InfluxDBFactory.connect连接只打开关闭一次;

doSave方法中,.time(DateUtils.addHours(metric.getTimestamp(), 8).getTime(), TimeUnit.MILLISECONDS)

因InfluxDB的UTC时间暂时没找到修改方法,所以这里time时间列加了8个小时时差;

queryByAppAndResourceBetween、listResourcesOfApp里面的查询方法,使用InfluxDB提供的类sql语法,编写查询语句即可。

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

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

来验证下成果:

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

启动工程,打开http://localhost:8080,查看各页面均显示正常,

在命令行通过InfluxDB客户端命令,show measurements,可以看到已经生成了sentinel_metric数据表(measurement);

查询总数:select count(id) from sentinel_metric

查询最新5行数据:select * from sentinel_metric order by time desc limit 5

注:命令行语句结束不用加分号

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

代码参考:https://github.com/cdfive/Sentinel/tree/winxuan_develop/sentinel-dashboard

扩展:

1.考虑以什么时间维度归档历史数据;

2.结合grafana将监控数据进行多维度的统计和呈现。

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

参考:

Sentinel官方文档:

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

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

InfluxDB官网文档 https://docs.influxdata.com/influxdb/v1.6/introduction/getting-started/

InfluxDB简明手册 https://xtutu.gitbooks.io/influxdb-handbook/content/

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

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

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

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

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

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

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

  4. 通过Python将监控数据由influxdb写入到MySQL

    一.项目背景 我们知道InfluxDB是最受欢迎的时序数据库(TSDB).InfluxDB具有 持续高并发写入.无更新:数据压缩存储:低查询延时 的特点.从下面这个权威的统计图中,就可以看出Influ ...

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

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

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

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

  7. nagios+influxdb+grafana的监控数据可视化流程

    nagios介绍 nagios是一款开源监控的应用,可用于监控本地和远程主机的日志.资源.死活等等诸多功能.通过snmp协议和nrpe协议. nagios的配置文件是由nconf上进行配置,然后点击生 ...

  8. 乐视云监控数据存放到influxdb中

    3.9     监控.告警系统 监控报警我们分PaaS平台和业务应用两大类. PaaS平台主要聚焦在基础设施和LeEngine的各个服务组件的监控报警(比如主机CPU,内存,IO,磁盘空间,LeEng ...

  9. 阿里Sentinel控制台源码修改-对接Apollo规则持久化

    改造背景 前面我们讲解了如何对接Apollo来持久化限流的规则,对接后可以直接通过Apollo的后台进行规则的修改,推送到各个客户端实时生效. 但还有一个问题就是Sentinel控制台没有对接Apol ...

随机推荐

  1. EEPROM IIC

    1. 数据位的有效性规定 I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化 2. 起始和终止信号 S ...

  2. 【1】Git基础

    一.Git概念 1.1.Git定义   Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目.Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发 ...

  3. 微信小程序开发(八)获取手机ip地址

    // succ.wxml <view>手机IP:{{motto.query}}</view> // succ.js var app = getApp() Page({ data ...

  4. javascript弹出带文字信息的提示框效果

    // position of the tooltip relative to the mouse in pixel // <html><head><meta charse ...

  5. u-boot器件驱动模型(Device&Drivers)之uclass (转)

    一.剧情回顾 在上一篇链接器的秘密里面我们讲到我们用一些特殊的宏让链接器帮我们把一些初始化好的结构体列好队并安排在程序的某一个段里面,这里我例举出了三个和我们主题相关段的分布情况,它们大概如下图所示: ...

  6. 【洛谷P4430】小猴打架

    题目大意:求带标号 N 个点的生成树个数,两棵生成树相同当且仅当两棵树结构相同且边的生成顺序相同. 题解:学会了 prufer 序列. prufer 序列是用来表示带标号的无根树的序列. 每种不同类型 ...

  7. Error resolving template “pages”, template might not exist or might not be accessible by any of the configured Template Resolver 或者 springboot使用thymeleaf时报html没有结束标签

    application.properties配置文件 spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.ht ...

  8. sheet.getRow(rowIndex);为null_POI导出excel

    第一次使用POI,出现这个问题,看到有其他猿也遇到过,不知道怎么处理,所以记录一下~ sheet.getRow(rowIndex);通过sheet获取指定行,rowIndex代表第几行 用rowInd ...

  9. P2709 小B的询问——普通莫队&&模板

    普通莫队概念 莫队:莫涛队长发明的算法,尊称莫队.其实就是优化的暴力. 普通莫队只兹磁询问不支持修改,是离线的. 莫队的基本思想:就是假定我得到了一个询问区间[l,r]的答案,那么我可以在极短(通常是 ...

  10. $ python manage.py makemigrations You are trying to add a non-nullable field 'name' to course without a default; we can't do that (the database needs something to populate existing rows). Please selec

    问题: $ python manage.py makemigrationsYou are trying to add a non-nullable field 'name' to course wit ...