influxdb java_将Sentinel监控数据持久化到外部InfluxDB时间序列数据库

一、实现介绍

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

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

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

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

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


二、InfluxDB介绍及安装使用

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

关键词:

  1. 高性能时序数据库
  2. go语言编写没有外部依赖
  3. 支持HTTP API读写
  4. 支持类SQL查询语法
  5. 通过数据保留策略(Retention Policies)支持自动清理历史数据
  6. 通过连续查询(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:influxdbinfluxdb-1.6.4-1目录:

1a2c0ab1a3acbd321804cca7d41e92c5.png

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

ef7a892bb783938301dc818844be457a.png

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

3b4089d51d63f99e43d8eb922075c193.png

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

62f81b1dc86484e174d1a6e5c384ab56.png

use sentinel_db 使用sentinel_db数据库

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

ec26e3b3ec7caf8ccb62d9c2c824a104.png

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


InfluxDB名词概念:

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

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

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

point由3部分组成:

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

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

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


三、修改Sentinel Dashboard

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

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

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

org.influxdb    influxdb-java    2.17

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

/** * @author cdfive * @date 2018-10-19 */@Componentpublic 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 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 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 doCallBack(String database, InfluxDB influxDB) {                QueryResult queryResult = influxDBQueryCallback.doCallBack(database, influxDB);                return (T) queryResult;            }        });    }    public static  List queryList(String database, String sql, Map paramMap, Class 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> entries = paramMap.entrySet();                    for (Map.Entry entry : entries) {                        queryBuilder.bind(entry.getKey(), entry.getValue());                    }                }                return influxDB.query(queryBuilder.create());            }        });        return resultMapper.toPOJO(queryResult, clasz);    }    public interface InfluxDBCallback {         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.alibaba.csp.sentinel.dashboard.datasource.entity;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接口:

package com.alibaba.csp.sentinel.dashboard.repository.metric;import java.util.ArrayList;import java.util.Date;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.concurrent.TimeUnit;import java.util.stream.Collectors;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 com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricPO;import com.alibaba.csp.sentinel.dashboard.util.InfluxDBUtils;import com.alibaba.csp.sentinel.util.StringUtil;/** * metrics数据InfluxDB存储实现 * @author cdfive * @date 2018-10-19 */@Repository("influxDBMetricsRepository")public class InfluxDBMetricsRepository implements MetricsRepository {    /**时间格式*/    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 metrics) {        if (metrics == null) {            return;        }        Iterator 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 queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime) {        List results = new ArrayList();        if (StringUtil.isBlank(app)) {            return results;        }        if (StringUtil.isBlank(resource)) {            return results;        }                // 将查询的开始时间和结束减去8小时,因为influxdb使用的是UTC时间,北京时间比UTC时间慢8个小时        endTime = endTime - UTC_8 * 60 * 60 *1000;        startTime = startTime - UTC_8 * 60 * 60 *1000;        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 paramMap = new HashMap();        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 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 listResourcesOfApp(String app) {        List 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 paramMap = new HashMap();        long startTime = System.currentTimeMillis() - 1000 * 60;                // 将查询的开始时间减去8小时,因为influxdb使用的是UTC时间,北京时间比UTC时间慢8个小时        startTime = startTime - UTC_8 * 60 * 60 *1000;                paramMap.put("app", app);        paramMap.put("startTime", DateFormatUtils.format(new Date(startTime), DATE_FORMAT_PATTERN));        List metricPOS = InfluxDBUtils.queryList(SENTINEL_DATABASE, sql.toString(), paramMap, MetricPO.class);        if (CollectionUtils.isEmpty(metricPOS)) {            return results;        }        List metricEntities = new ArrayList();        for (MetricPO metricPO : metricPOS) {            metricEntities.add(convertToMetricEntity(metricPO));        }        Map 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()));        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(metric.getTimestamp().getTime(), TimeUnit.MILLISECONDS)                .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 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")@Autowiredprivate MetricsRepository 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/


原文链接:https://www.cnblogs.com/cdfive2018/p/9914838.html

注:在原文的链接基础之上修正了Sentinal控制台可以显示监控数据,但是通过Grafana因为时区的问题不能够正常展示的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Sentinel是一款开源的流量控制和熔断降级框架,而Nacos是一个动态服务发现、配置管理和服务管理平台。在Sentinel中,可以通过实现NacosRepository接口将数据持久化到Nacos。 具体实现步骤如下: 1. 首先,需要引入Sentinel和Nacos的相关依赖包。 2. 创建一个实现NacosRepository接口的类,该类负责将Sentinel的规则数据持久化到Nacos中。可以参考以下代码示例: ```java import com.alibaba.csp.sentinel.datasource.Converter;import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSourceBuilder; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSourceConfiguration; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSourceListener; import com.alibaba.csp.sentinel.init.InitFunc; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import com.alibaba.nacos.api.PropertyKeyConst; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingFactory; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import java.util.List; import java.util.Properties; public class NacosRepository implements InitFunc { private static final String GROUP_ID = "SENTINEL_GROUP"; private static final String FLOW_DATA_ID = "FLOW_RULES"; @Override public void init() throws Exception { // Nacos配置 Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, "localhost:8848"); properties.put(PropertyKeyConst.NAMESPACE, "public"); // 创建Nacos数据源 NacosDataSource<List<FlowRule>> dataSource = NacosDataSourceBuilder .createDataSourceBuilder() .groupId(GROUP_ID) .dataId(FLOW_DATA_ID) .configService(getConfigService(properties)) .objectDeserializer(new Converter<String, List<FlowRule>>() { @Override public List<FlowRule> convert(String source) { return JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}); } }) .build(); // 注册Nacos数据源监听器 dataSource.addListener(new NacosDataSourceListener<List<FlowRule>>() { @Override public void receiveConfigInfo(List<FlowRule> config) { // 处理配置更新事件 // 将配置更新到Sentinel中 } @Override public void handleException(Throwable throwable) { // 处理异常事件 } }); // 初始化Nacos数据源 dataSource.init(); } private ConfigService getConfigService(Properties properties) throws NacosException { return NamingFactory.createConfigService(properties); } } ``` 在上述代码中,我们创建了一个NacosDataSource对象,并通过NacosDataSourceBuilder进行配置。其中,groupId和dataId用于指定Nacos中的配置分组和数据ID。objectDeserializer用于将Nacos中的配置反序列化为Sentinel的规则对象。 然后,我们通过addListener方法注册了一个NacosDataSourceListener,用于监听Nacos中配置的变化。在receiveConfigInfo方法中,可以处理配置更新事件,将更新后的配置更新到Sentinel中。 最后,在init方法中,我们初始化了NacosDataSource对象。 3. 在Sentinel的配置文件中,配置NacosRepository类的初始化: ``` -Dcsp.sentinel.init-block-args=NacosRepository ``` 这样,当Sentinel启动时,会自动初始化NacosRepository类,并将规则数据持久化到Nacos中。 以上就是将Sentinel的规则数据持久化到Nacos的实现方式。通过实现NacosRepository接口,可以将Sentinel的规则数据与Nacos进行动态同步,实现规则的持久化和动态更新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值