|
|
@@ -0,0 +1,330 @@
|
|
|
+package com.usthe.warehouse.store;
|
|
|
+
|
|
|
+import com.usthe.collector.dispatch.export.MetricsDataExporter;
|
|
|
+import com.usthe.common.entity.dto.Value;
|
|
|
+import com.usthe.common.entity.message.CollectRep;
|
|
|
+import com.usthe.common.util.CommonConstants;
|
|
|
+import com.usthe.warehouse.WarehouseProperties;
|
|
|
+import com.usthe.warehouse.WarehouseWorkerPool;
|
|
|
+import com.zaxxer.hikari.HikariConfig;
|
|
|
+import com.zaxxer.hikari.HikariDataSource;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.DisposableBean;
|
|
|
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
|
|
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
|
+import org.springframework.context.annotation.Configuration;
|
|
|
+
|
|
|
+import java.sql.Connection;
|
|
|
+import java.sql.ResultSet;
|
|
|
+import java.sql.Statement;
|
|
|
+import java.sql.Timestamp;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.LinkedList;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * influxdb存储采集数据
|
|
|
+ * @author tom
|
|
|
+ * @date 2021/11/24 18:23
|
|
|
+ */
|
|
|
+@Configuration
|
|
|
+@AutoConfigureAfter(value = {WarehouseProperties.class})
|
|
|
+@ConditionalOnProperty(prefix = "warehouse.store.td-engine",
|
|
|
+ name = "enabled", havingValue = "true", matchIfMissing = true)
|
|
|
+@Slf4j
|
|
|
+public class TdEngineDataStorage implements DisposableBean {
|
|
|
+
|
|
|
+ private HikariDataSource hikariDataSource;
|
|
|
+ private WarehouseWorkerPool workerPool;
|
|
|
+ private MetricsDataExporter dataExporter;
|
|
|
+ private static final String INSERT_TABLE_DATA_SQL = "INSERT INTO %s USING %s TAGS (%s) VALUES %s";
|
|
|
+ private static final String CREATE_SUPER_TABLE_SQL = "CREATE STABLE IF NOT EXISTS %s %s TAGS (monitor BIGINT)";
|
|
|
+ private static final String NO_SUPER_TABLE_ERROR = "Table does not exist";
|
|
|
+ private static final String QUERY_HISTORY_WITH_INSTANCE_SQL
|
|
|
+ = "SELECT ts, instance, %s FROM %s WHERE instance = %s AND ts >= now - %s order by ts desc";
|
|
|
+ private static final String QUERY_HISTORY_SQL
|
|
|
+ = "SELECT ts, instance, %s FROM %s WHERE ts >= now - %s order by ts desc";
|
|
|
+ private static final String QUERY_HISTORY_INTERVAL_WITH_INSTANCE_SQL
|
|
|
+ = "SELECT first(%s), avg(%s), min(%s), max(%s) FROM %s WHERE instance = %s AND ts >= now - %s interval(4h)";
|
|
|
+ private static final String QUERY_INSTANCE_SQL
|
|
|
+ = "SELECT DISTINCT instance FROM %s WHERE ts >= now - 1w";
|
|
|
+
|
|
|
+ public TdEngineDataStorage(WarehouseWorkerPool workerPool, WarehouseProperties properties,
|
|
|
+ MetricsDataExporter dataExporter) {
|
|
|
+ this.workerPool = workerPool;
|
|
|
+ this.dataExporter = dataExporter;
|
|
|
+ if (properties == null || properties.getStore() == null || properties.getStore().getTdEngine() == null) {
|
|
|
+ log.error("init error, please config Warehouse influxdb props in application.yml");
|
|
|
+ throw new IllegalArgumentException("please config Warehouse influxdb props");
|
|
|
+ }
|
|
|
+ initTdEngineDatasource(properties.getStore().getTdEngine());
|
|
|
+ startStorageData();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void initTdEngineDatasource(WarehouseProperties.StoreProperties.TdEngineProperties tdEngineProperties) {
|
|
|
+ HikariConfig config = new HikariConfig();
|
|
|
+ // jdbc properties
|
|
|
+ config.setJdbcUrl(tdEngineProperties.getUrl());
|
|
|
+ config.setUsername(tdEngineProperties.getUsername());
|
|
|
+ config.setPassword(tdEngineProperties.getPassword());
|
|
|
+ config.setDriverClassName(tdEngineProperties.getDriverClassName());
|
|
|
+ //minimum number of idle connection
|
|
|
+ config.setMinimumIdle(10);
|
|
|
+ //maximum number of connection in the pool
|
|
|
+ config.setMaximumPoolSize(10);
|
|
|
+ //maximum wait milliseconds for get connection from pool
|
|
|
+ config.setConnectionTimeout(30000);
|
|
|
+ // maximum lifetime for each connection
|
|
|
+ config.setMaxLifetime(0);
|
|
|
+ // max idle time for recycle idle connection
|
|
|
+ config.setIdleTimeout(0);
|
|
|
+ //validation query
|
|
|
+ config.setConnectionTestQuery("select server_status()");
|
|
|
+ this.hikariDataSource = new HikariDataSource(config);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void startStorageData() {
|
|
|
+ Runnable runnable = () -> {
|
|
|
+ Thread.currentThread().setName("warehouse-tdEngine-data-storage");
|
|
|
+ while (!Thread.currentThread().isInterrupted()) {
|
|
|
+ try {
|
|
|
+ CollectRep.MetricsData metricsData = dataExporter.pollPersistentStorageMetricsData();
|
|
|
+ if (metricsData != null) {
|
|
|
+ saveData(metricsData);
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ workerPool.executeJob(runnable);
|
|
|
+ workerPool.executeJob(runnable);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public void saveData(CollectRep.MetricsData metricsData) {
|
|
|
+ if (metricsData == null || metricsData.getValuesList().isEmpty() || metricsData.getFieldsList().isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String monitorId = String.valueOf(metricsData.getId());
|
|
|
+ String superTable = metricsData.getApp() + "_" + metricsData.getMetrics() + "_super";
|
|
|
+ String table = metricsData.getApp() + "_" + metricsData.getMetrics() + "_" + monitorId;
|
|
|
+ //组建DATA SQL
|
|
|
+ List<CollectRep.Field> fields = metricsData.getFieldsList();
|
|
|
+ StringBuilder sqlBuffer = new StringBuilder();
|
|
|
+ for (CollectRep.ValueRow valueRow : metricsData.getValuesList()) {
|
|
|
+ StringBuilder sqlRowBuffer = new StringBuilder("(");
|
|
|
+ sqlRowBuffer.append(metricsData.getTime()).append(", ");
|
|
|
+ sqlRowBuffer.append("'").append(valueRow.getInstance()).append("', ");
|
|
|
+ for (int index = 0; index < fields.size(); index++) {
|
|
|
+ CollectRep.Field field = fields.get(index);
|
|
|
+ String value = valueRow.getColumns(index);
|
|
|
+ if (field.getType() == CommonConstants.TYPE_NUMBER) {
|
|
|
+ // number data
|
|
|
+ if (CommonConstants.NULL_VALUE.equals(value)) {
|
|
|
+ sqlRowBuffer.append("NULL");
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ double number = Double.parseDouble(value);
|
|
|
+ sqlRowBuffer.append(number);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn(e.getMessage());
|
|
|
+ sqlRowBuffer.append("NULL");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // string
|
|
|
+ if (CommonConstants.NULL_VALUE.equals(value)) {
|
|
|
+ sqlRowBuffer.append("NULL");
|
|
|
+ } else {
|
|
|
+ sqlRowBuffer.append("'").append(value).append("'");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (index != fields.size() - 1) {
|
|
|
+ sqlRowBuffer.append(", ");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sqlRowBuffer.append(")");
|
|
|
+ sqlBuffer.append(" ").append(sqlRowBuffer);
|
|
|
+ }
|
|
|
+ String insertDataSql = String.format(INSERT_TABLE_DATA_SQL, table, superTable, monitorId, sqlBuffer);
|
|
|
+ log.info(insertDataSql);
|
|
|
+ Connection connection = null;
|
|
|
+ Statement statement = null;
|
|
|
+ try {
|
|
|
+ connection = hikariDataSource.getConnection();
|
|
|
+ statement = connection.createStatement();
|
|
|
+ statement.execute(insertDataSql);
|
|
|
+ connection.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+ if (e.getMessage().contains(NO_SUPER_TABLE_ERROR)) {
|
|
|
+ // 超级表未创建 创建对应超级表
|
|
|
+ StringBuilder fieldSqlBuilder = new StringBuilder("(");
|
|
|
+ fieldSqlBuilder.append("ts TIMESTAMP, ");
|
|
|
+ fieldSqlBuilder.append("instance NCHAR(100), ");
|
|
|
+ for (int index = 0; index < fields.size(); index++) {
|
|
|
+ CollectRep.Field field = fields.get(index);
|
|
|
+ String fieldName = field.getName();
|
|
|
+ if (field.getType() == CommonConstants.TYPE_NUMBER) {
|
|
|
+ fieldSqlBuilder.append(fieldName).append(" ").append("DOUBLE");
|
|
|
+ } else {
|
|
|
+ fieldSqlBuilder.append(fieldName).append(" ").append("NCHAR(100)");
|
|
|
+ }
|
|
|
+ if (index != fields.size() - 1) {
|
|
|
+ fieldSqlBuilder.append(", ");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ fieldSqlBuilder.append(")");
|
|
|
+ String createTableSql = String.format(CREATE_SUPER_TABLE_SQL, superTable, fieldSqlBuilder);
|
|
|
+ try {
|
|
|
+ assert statement != null;
|
|
|
+ statement.execute(createTableSql);
|
|
|
+ statement.execute(insertDataSql);
|
|
|
+ } catch (Exception createTableException) {
|
|
|
+ log.error(e.getMessage(), createTableException);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ assert connection != null;
|
|
|
+ connection.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void destroy() throws Exception {
|
|
|
+ if (hikariDataSource != null) {
|
|
|
+ hikariDataSource.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从TD ENGINE时序数据库获取指标历史数据
|
|
|
+ *
|
|
|
+ * @param monitorId 监控ID
|
|
|
+ * @param app 监控类型
|
|
|
+ * @param metrics 指标集合名
|
|
|
+ * @param metric 指标名
|
|
|
+ * @param instance 实例
|
|
|
+ * @param history 历史范围
|
|
|
+ * @return 指标历史数据列表
|
|
|
+ */
|
|
|
+ public Map<String, List<Value>> getHistoryMetricData(Long monitorId, String app, String metrics, String metric, String instance, String history) {
|
|
|
+ String table = app + "_" + metrics + "_" + monitorId;
|
|
|
+ String selectSql = instance == null ? String.format(QUERY_HISTORY_SQL, metric, table, history) :
|
|
|
+ String.format(QUERY_HISTORY_WITH_INSTANCE_SQL, metric, table, instance, history);
|
|
|
+ log.info(selectSql);
|
|
|
+ Connection connection = null;
|
|
|
+ Map<String, List<Value>> instanceValuesMap = new HashMap<>(8);
|
|
|
+ try {
|
|
|
+ connection = hikariDataSource.getConnection();
|
|
|
+ Statement statement = connection.createStatement();
|
|
|
+ ResultSet resultSet = statement.executeQuery(selectSql);
|
|
|
+ while (resultSet.next()) {
|
|
|
+ Timestamp ts = resultSet.getTimestamp(1);
|
|
|
+ String instanceValue = resultSet.getString(2);
|
|
|
+ if (instanceValue == null || "".equals(instanceValue)) {
|
|
|
+ instanceValue = "NULL";
|
|
|
+ }
|
|
|
+ double value = resultSet.getDouble(3);
|
|
|
+ List<Value> valueList = instanceValuesMap.computeIfAbsent(instanceValue, k -> new LinkedList<>());
|
|
|
+ valueList.add(new Value(String.valueOf(value), ts.getTime()));
|
|
|
+ }
|
|
|
+ resultSet.close();
|
|
|
+ return instanceValuesMap;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ assert connection != null;
|
|
|
+ connection.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return instanceValuesMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, List<Value>> getHistoryIntervalMetricData(Long monitorId, String app, String metrics,
|
|
|
+ String metric, String instance, String history) {
|
|
|
+ String table = app + "_" + metrics + "_" + monitorId;
|
|
|
+ List<String> instances = new LinkedList<>();
|
|
|
+ if (instance != null) {
|
|
|
+ instances.add(instance);
|
|
|
+ }
|
|
|
+ if (instances.isEmpty()) {
|
|
|
+ // 若未指定instance,需查询当前指标数据前1周有多少个instance
|
|
|
+ String queryInstanceSql = String.format(QUERY_INSTANCE_SQL, table);
|
|
|
+ Connection connection = null;
|
|
|
+ try {
|
|
|
+ connection = hikariDataSource.getConnection();
|
|
|
+ Statement statement = connection.createStatement();
|
|
|
+ ResultSet resultSet = statement.executeQuery(queryInstanceSql);
|
|
|
+ while (resultSet.next()) {
|
|
|
+ String instanceValue = resultSet.getString(1);
|
|
|
+ if (instanceValue == null || "".equals(instanceValue)) {
|
|
|
+ instances.add("''");
|
|
|
+ } else {
|
|
|
+ instances.add(instanceValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ assert connection != null;
|
|
|
+ connection.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Map<String, List<Value>> instanceValuesMap = new HashMap<>(instances.size());
|
|
|
+ for (String instanceValue : instances) {
|
|
|
+ String selectSql = String.format(QUERY_HISTORY_INTERVAL_WITH_INSTANCE_SQL,
|
|
|
+ metric, metric, metric, metric, table, instanceValue, history);
|
|
|
+ log.info(selectSql);
|
|
|
+ if ("''".equals(instanceValue)) {
|
|
|
+ instanceValue = "NULL";
|
|
|
+ }
|
|
|
+ List<Value> values = instanceValuesMap.computeIfAbsent(instanceValue, k -> new LinkedList<>());
|
|
|
+ Connection connection = null;
|
|
|
+ try {
|
|
|
+ connection = hikariDataSource.getConnection();
|
|
|
+ Statement statement = connection.createStatement();
|
|
|
+ ResultSet resultSet = statement.executeQuery(selectSql);
|
|
|
+ while (resultSet.next()) {
|
|
|
+ Timestamp ts = resultSet.getTimestamp(1);
|
|
|
+ double origin = resultSet.getDouble(2);
|
|
|
+ double avg = resultSet.getDouble(3);
|
|
|
+ double min = resultSet.getDouble(4);
|
|
|
+ double max = resultSet.getDouble(5);
|
|
|
+ Value value = Value.builder()
|
|
|
+ .origin(String.valueOf(origin)).mean(String.valueOf(avg))
|
|
|
+ .min(String.valueOf(min)).max(String.valueOf(max))
|
|
|
+ .time(ts.getTime())
|
|
|
+ .build();
|
|
|
+ values.add(value);
|
|
|
+ }
|
|
|
+ resultSet.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error(e.getMessage(), e);
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ assert connection != null;
|
|
|
+ connection.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return instanceValuesMap;
|
|
|
+ }
|
|
|
+}
|