influxdb的安装和语法这边不加以说明,本文主要讲解influxdb在java大数据量存储的应用。

该项目的场景是存储设备的历史数据。(针对时序数据库influxdb是个比较好的选择)

一。流程图

项目启动流程:

java每秒在数据库中顺序读取一条 java使用时序数据库_java

存储流程如下:

java每秒在数据库中顺序读取一条 java使用时序数据库_数据库_02

二,数据库及表设计

数据库:

rt_necp_history 短期数据库

key_necp_history 长期数据库

表:

AI_RESULT 存储数字类型的表(double,int),方面以后统计计算

MIX_RESULT 混合表(存储字符串类型)

字段:tid 数据的唯一标识(对应平台的设备的测点) value 值 ,time 创建时间

总共三个字段即将设备的每个测点当做一条数据存储,该种方案可能会造成influxdb的series数量庞大。

实际压测过:内存8g,能存储的数据量在5000万条左右,还是可以满足一定的需求。

若对数据量要求较大时建议用一个设备作为一条数据,表的字段将会比较多。

实际压测过:内存8g,表字段在500个左右,能存储数据量在1000万左右。

 

存储策略:


LONG_YEAR 长期数据


String commandLong = String.format("CREATE RETENTION POLICY \"%s\" ON \"%s\" DURATION %s REPLICATION %s DEFAULT",
                keyPolicy, keyDataBase, "1300w", 1); //25年  一年有52周,52*25 =1300w
        this.query(commandLong,keyDataBase);

 


ONE_MONTH 短期数据


String command = String.format("CREATE RETENTION POLICY \"%s\" ON \"%s\" DURATION %s REPLICATION %s DEFAULT",
                rtPolicy, rtDataBase, "30d", 1); //30天
        this.query(command,rtDataBase);

分为两种策略分别设置为长期数据库和短期数据库的默认存储策略。

三,实践。

1.在influxdb配置文件中打开这个两个参数并设置为0(批量存储无限)。

java每秒在数据库中顺序读取一条 java使用时序数据库_influxdb_03

2.pom.xml中导入jar包依赖

java每秒在数据库中顺序读取一条 java使用时序数据库_springboot_04

3.influxdb工具类

@Component
public class InfluxBaseDao implements InitializingBean {
    private static InfluxDB influxDB;
    private static final int maxActiveNum = 1000;//最大初始化时连接数
    private static BlockingQueue<InfluxDB> connPool;
    @Value("${spring.influx.user}")
    private String username;// 用户名
    @Value("${spring.influx.password}")
    private String password;// 密码
    @Value("${spring.influx.url}")
    private String openurl;// 连接地址
    @Value("${spring.influx.dataBase}")
    private String database;// 数据库
    @Value("${spring.influx.table.analog}")
    private String analogtable;//数值表
    @Value("${spring.influx.table.mix}")
    private String mixtable;//混合表(存字符串等数据)

    void initPool() {
        connPool = new LinkedBlockingQueue<>(maxActiveNum);
        while (connPool.size()<maxActiveNum) {
            if (influxDbBuild() != null) {
                try {
                    connPool.put(influxDbBuild());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                break;
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        initPool();
        influxDbBuild();
        //createRetentionPolicy();
    }

    /**
     * 连接时序数据库;获得InfluxDB
     **/
    @SuppressWarnings("deprecation")
    public InfluxDB influxDbBuild() {
        try {
            if (influxDB == null) {
                if (openurl != null) {
                    influxDB = InfluxDBFactory.connect(openurl, username, password);
                    if (influxDB != null) {
                        boolean flag = influxDB.databaseExists(database);
                        if (!flag) {
                            influxDB.createDatabase(database);
                            createRetentionPolicy(); //初始化创建保存策略
                        }
                    }
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return influxDB;
    }

    public InfluxDB getInfluxDB() {
        if (connPool.size() > 0) {
            InfluxDB conn = null;
            try {
                conn = connPool.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (conn == null) {
                initPool();
               // System.out.println(openurl + "--" + database);
            }
            return conn;
        } else {
            return influxDbBuild();
        }
    }

    public static void setInfluxDB(InfluxDB influxDB) {
        InfluxBaseDao.influxDB = influxDB;
    }

    /**
     * 设置数据保存策略
     * defalut 策略名 /database 数据库名/ 1095d 数据保存时限3年/ 1  副本个数为1/ 结尾DEFAULT 表示 设为默认的策略
     */
    public void createRetentionPolicy() {
        String command = String.format("CREATE RETENTION POLICY \"%s\" ON \"%s\" DURATION %s REPLICATION %s DEFAULT",
                "defalut", database, "1095d", 1); //3年
        this.query(command);
    }

    public void createRetentionPolicyDefault() {
        String command = String.format("CREATE RETENTION POLICY \"%s\" ON \"%s\" DURATION %s REPLICATION %s DEFAULT",
                "defalut", database, "0s", 1); //无限制
        this.query(command);
    }

    //删除策略
    public void dropRetentionPolicyDefault() {
        String command = String.format("DROP RETENTION POLICY \"%s\" ON \"%s\"",
                "defalut", database);
        this.query(command);
    }

    /**
     * 查询
     *
     * @param command 查询语句
     * @return
     */
    public QueryResult query(String command) {
        return getInfluxDB().query(new Query(command, database));
    }

    public QueryResult query(String command, String database) {
        return getInfluxDB().query(new Query(command, database));
    }

    /**
     * 插入
     *
     * @param measurement 表
     * @param tags        标签
     * @param fields      字段
     */
    public void insert(String measurement, Map<String, String> tags, Map<String, Object> fields) {
        Point.Builder builder = Point.measurement(measurement);
        builder.tag(tags);
        builder.fields(fields);

        getInfluxDB().write(database, "", builder.build());
    }

    public void insert(String measurement, Map<String, String> tags, Map<String, Object> fields, long time) {
        Point.Builder builder = Point.measurement(measurement);
        builder.tag(tags);
        builder.fields(fields);
        builder.time(time, TimeUnit.MILLISECONDS);
        getInfluxDB().write(database, "", builder.build());
    }

    public void insert(String measurement, Map<String, Object> fields) {
        Point.Builder builder = Point.measurement(measurement);
        builder.fields(fields);

        getInfluxDB().write(database, "", builder.build());
    }

    //where查询语句拼接
    public String joinWhere(List<String> tids, Date startTime, Date endTime) {
        String startTimeStr = IfdbDateUtils.formatDateByNum(startTime, -8); //时区转换
        String endTimeStr = IfdbDateUtils.formatDateByNum(endTime, -8);
        String command = "where 1=1 ";
        if (tids != null && tids.size() > 0) {
            command = command + " and (";
            for (String tid : tids) {
                command = command + "tid = '" + tid + "' or ";
            }
            command = command.substring(0, command.length() - 3);
            command = command + " ) ";
        }

        if (StringUtils.isNotBlank(startTimeStr)) {
            command = command + "and  time >='" + startTimeStr + "' ";
        }
        if (StringUtils.isNotBlank(endTimeStr)) {
            command = command + "and time <= '" + endTimeStr + "' ";
        }

        return command;
    }

    //批量插入历史数据
    public void batchInstertHistoryData(List<HistoryData> rtDList) {
        try {
            //声明influxdb批量插入数据对象
            BatchPoints batchPoints = BatchPoints.database(database)
                    .consistency(InfluxDB.ConsistencyLevel.ALL).build();
            for (int i = 0; i < rtDList.size(); i++) {
                HistoryData rtd = rtDList.get(i);
                Calendar cld = Calendar.getInstance();
                if (IfdbDateUtils.isDouble(rtd.getValue())) {//判断是否为数值(由于业务层之前设计没有分数值和字符串的区分,导致这边无法判断value是什么类型来存不同的表)
                    //创建单条数据对象——表名
                    Point point = Point
                            .measurement(analogtable)
                            //tag属性——只能存储String类型
                            .tag("tid", rtd.getDevicePointCode())
                            //field存储数据
                            .addField("value", Double.valueOf(rtd.getValue()))
                            //  .time(cld.getTimeInMillis(), TimeUnit.MILLISECONDS)
                            .build();
                    //将单条数据存储到集合中
                    batchPoints.point(point);
                } else {
                    //创建单条数据对象——表名
                    Point point = Point
                            .measurement(mixtable)
                            //tag属性——只能存储String类型
                            .tag("tid", rtd.getDevicePointCode())
                            //field存储数据
                            .addField("value", rtd.getValue())
                            //  .time(cld.getTimeInMillis(), TimeUnit.MILLISECONDS)
                            .build();
                    //将单条数据存储到集合中
                    batchPoints.point(point);
                }
            }
            getInfluxDB().write(batchPoints);
            getInfluxDB().close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除
     *
     * @param command 删除语句
     * @return 返回错误信息
     */
    public String deleteMeasurementData(String command) {
        QueryResult result = getInfluxDB().query(new Query(command, database));
        return result.getError();
    }

    //获取指定表的分页数据
    public Map<String, Object> queryDataDataByOffset(String table, String command, int pageSize, int pageNum) {
        Map<String, Object> rsMap = new HashMap<String, Object>();
        command = "select * from " + table + " " + command;
        String sumSQL = " select count(value) as value,time as t from ( " + command + " ) ";
        List<CommonData> sumData = queryRailTransitData(sumSQL);
        int total = 0;
        if (sumData != null && sumData.size() > 0) {
            CommonData rd = sumData.get(0);
            total = Integer.parseInt(new java.text.DecimalFormat("0").format(Double.valueOf(rd.getValue()))); //Double转Internet
            rsMap.put("total", total);
        } else {
            rsMap.put("total", 0);
        }
        String offsetSql = command + "ORDER BY time  desc  limit " + pageSize + " OFFSET " + (pageNum - 1) * pageSize;
        List<CommonData> offsetData = queryRailTransitData(offsetSql);
        rsMap.put("rows", offsetData);
        return rsMap;
    }

    //获取历史数据
    public List<CommonData> queryHistoryData(String table, String command) {
        command = "select * from " + table + " " + command;
        String offsetSql = command + " ORDER BY time  desc ";
        List<CommonData> offsetData = queryRailTransitData(offsetSql);
        return offsetData;
    }

    //根据时间、tid获取数值表每日的对应值,
    public List<CommonData> queryDayData(String command, String type) {
        java.text.DecimalFormat df = new java.text.DecimalFormat("#0.00");
        String statics = "mean"; //统计方式,默认取平均
        String[] staticMap = {"max", "min", "mean", "last"};
        statics = staticMap[Integer.valueOf(type) - 1];//表格驱动大法。。。。

        command = "select " + statics + "(value) as value from " + analogtable + " " + command;
        String offsetSql = command + " group by time(8h),* fill(0) ";//由于influxdb存入的时间与现在差8个小时,如果用1天来求平均会导致求出每天数据有误(出现偏移),这边用8小时作为求平均
        List<CommonData> offsetData = queryRailTransitData(offsetSql);
        //将每3个数据累计成一个数据,即为一天的数据(一天3个8小时)
        List<CommonData> offsetDataResult = new ArrayList<>();
        Double sum = 0.0;
        Integer flag = 0;

        Double[] ereryGroup = {0.0, 0.0, 0.0};  //取最大,最小值的数组
        for (int i = 0; i < offsetData.size(); i++) {
            if (!"0.0".equals(offsetData.get(i).getValue()) && !"0".equals(offsetData.get(i).getValue())) {
                sum += Double.valueOf(offsetData.get(i).getValue());
                ereryGroup[flag] = Double.valueOf(offsetData.get(i).getValue());
                flag++;
            }

            if ((i + 1) % 3 == 0) { //根据一天3个8小时,取一天的对应值(平均、最大、最小、瞬时)
                CommonData temp = new CommonData();
                temp.setTid(offsetData.get(i).getTid());
                temp.setTime(offsetData.get(i).getTime());

                switch (ReportStatisticsOptionEnum.getInstance(type)) {
                    case MAX:
                        Double tempmax = Collections.max(Arrays.asList(ereryGroup));
                        temp.setValue(flag == 0 ? (sum + "") : df.format(new BigDecimal(tempmax)));
                        break;
                    case MIN:
                        Double tempmin = Collections.min(Arrays.asList(ereryGroup));
                        temp.setValue(flag == 0 ? (sum + "") : df.format(new BigDecimal(tempmin)));
                        break;
                    case NOW:
                        temp.setValue(flag == 0 ? (sum + "") : df.format(new BigDecimal(offsetData.get(i).getValue())));
                        break;
                    default:
                        temp.setValue(flag == 0 ? (sum + "") : df.format(new BigDecimal(sum / 3)));
                        break;
                }
                offsetDataResult.add(temp);
                sum = 0.0;
                flag = 0;
                Double[] ereryGroupTemp = {0.0, 0.0, 0.0};  //清空
                ereryGroup = ereryGroupTemp;
            }
        }
        return offsetDataResult;
    }

    //根据时间、tid获取数值表每小时的对应值,
    public List<CommonData> queryHourData(String command, String type) {
        String statics = "mean"; //统计方式,默认取平均
        String[] staticMap = {"max", "min", "mean", "last"};
        statics = staticMap[Integer.valueOf(type) - 1];//
        command = "select " + statics + "(value) as value from " + analogtable + " " + command;
        String offsetSql = command + " group by time(1h),* fill(0) ";
        List<CommonData> offsetData = queryRailTransitData(offsetSql);
        return offsetData;
    }

    public List<CommonData> queryRailTransitData(String command) {
        // System.out.println("sql:"+command);
        QueryResult rs = getInfluxDB().query(new Query(command, database));
        List<CommonData> lists = new ArrayList<CommonData>();
        lists = dealResult(rs);
        return lists;
    }

    public List<CommonData> dealResult(QueryResult rs) {
        List<CommonData> lists = new ArrayList<CommonData>();
        if (rs != null && rs.getResults() != null && rs.getResults().get(0) != null && rs.getResults().get(0).getSeries() != null) {
            for (QueryResult.Result result : rs.getResults()) {

                List<QueryResult.Series> series = result.getSeries();
                for (QueryResult.Series serie : series) {
//					Map<String, String> tags = serie.getTags();
                    List<List<Object>> values = serie.getValues();
                    List<String> columns = serie.getColumns();
                    Map<String, String> tags = serie.getTags();
                    lists.addAll(getQueryData(columns, values, tags));
                }
            }
        }

        return lists;
    }

    /***整理列名、行数据***/
    private List<CommonData> getQueryData(List<String> columns, List<List<Object>> values, Map<String, String> tags) {
        List<CommonData> lists = new ArrayList<CommonData>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        boolean hadTagId = false;
        String sname = "";
        if (!columns.contains("tid") && tags != null) {
            hadTagId = true;
            sname = tags.get("tid");
        }
        for (List<Object> list : values) {
            CommonData info = new CommonData();
            for (int i = 0; i < list.size(); i++) {

                String propertyName = columns.get(i);//字段名
                Object value = list.get(i);//相应字段值
                if (propertyName != null && "time".equals(propertyName)) {
                    String time = IfdbDateUtils.UTCToCSTNew(value.toString(), "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

                    info.setTime(time);
                } else if (propertyName != null && "tid".equals(propertyName)) {
                    info.setTid((String) value);
                    hadTagId = false;
                } else if (propertyName != null && "value".equals(propertyName)) {
                    info.setValue(value.toString());
                }
                if (hadTagId) {
                    info.setTid(sname);
                }
            }

            lists.add(info);
        }

        return lists;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getOpenurl() {
        return openurl;
    }

    public void setOpenurl(String openurl) {
        this.openurl = openurl;
    }

    public String getDatabase() {
        return database;
    }

    public void setDatabase(String database) {
        this.database = database;
    }

    public String getAnalogtable() {
        return analogtable;
    }

    public void setAnalogtable(String analogtable) {
        this.analogtable = analogtable;
    }

    public String getMixtable() {
        return mixtable;
    }

    public void setMixtable(String mixtable) {
        this.mixtable = mixtable;
    }

}

四。难点。

由于influxdb插入数据时间默认与实际时间相差8小时,业务上需要自己做时间转换

public static String formatDateByNum(Date time, Integer num) {
        if (time == null) {
            return "";
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = null;
        Calendar cld = Calendar.getInstance();
        cld.setTime(time);
        cld.add(Calendar.HOUR, num);
        date = cld.getTime();
        return sdf.format(date);
    }

单纯读取数据可能只需要做时间减8小时即可,但是当需要做统计平均值,最大值等数据时,需要将influxdb实际计算出的值按一天3个8小时拆分计算(group by time惹的祸)

@Repository
public class HistoryHandleDao {
    private static Map<String,SDTPoints> statusMap = new HashMap<>();
    private static  double accuracyE = 0.5;//压缩精度默认0.5,控制压缩数据差值在1-2左右

    @Value("${spring.influx.table.analog}")
    private String analogtable;//数值表
    @Value("${spring.influx.table.mix}")
    private String mixtable;//混合表(存字符串等数据)

    @Autowired
    private InfluxBaseDao influxBaseOurDao;

    //插入数据解析 --直接插入
    public void analysisData(List<String> sourceDataList){
        List<HistoryData> analogList = new ArrayList<>();

        int length = sourceDataList.size();
        for (int i = 0; i < length; i++) {
            JSONArray dataArray = JSONArray.parseArray(sourceDataList.get(i));
            for (int j = 0; j < dataArray.size(); j++) {
                JSONObject jsonObject = dataArray.getJSONObject(j);
                HistoryData historyData = JSON.toJavaObject(jsonObject, HistoryData.class);

                analogList.add(historyData);
            }
        }
        bathSaveDataToInfluxDb(analogList); //插入数据
    }

    //插入数据解析 ---SDT旋转门压缩算法插入
    public void analysisSDTData(List<String> sourceDataList){
        List<HistoryData> analogList = new ArrayList<>();
        List<HistoryData> analogSDTList = new ArrayList<>(); //压缩后的数据
        int length = sourceDataList.size();
        for(int i=0;i<length;i++){
            JSONArray dataArray = JSONArray.parseArray(sourceDataList.get(i));
            for (int j=0;j<dataArray.size();j++){
                JSONObject jsonObject = dataArray.getJSONObject(j);
                HistoryData historyData = JSON.toJavaObject(jsonObject,HistoryData.class);
                historyData.setTime(new Date().getTime());
                analogList.add(historyData);
            }
        }
        analogSDTList = SDTcompress(analogList); //
        bathSaveDataToInfluxDb(analogSDTList); //插入数据
    }

    //批量存储数据
    public void bathSaveDataToInfluxDb(List<HistoryData> rtDList) {
        int dNum = rtDList.size();
        int maxDataNum = 300000;
        // 循环删除次数
        int num = 1;
        // 求出删除次数
        if (dNum % maxDataNum == 0) {
            num = dNum / maxDataNum;
        } else {
            num = dNum / maxDataNum + 1;
        }
        // 循环删除
        for (int i = 1; i <= num; i++) {
            // 要删除的sname list
            List<HistoryData> strList = new ArrayList<HistoryData>();
            // 每次删除的开始值和结束值
            int start = (i - 1) * maxDataNum;
            int end = i * maxDataNum;
            if (end <= dNum) {
                end = i * maxDataNum;
            } else {
                end = dNum;
            }
            // 获取要删除的sname list
            strList = rtDList.subList(start, end);

            influxBaseOurDao.batchInstertHistoryData(strList);
        }
    }

    //禁用保存策略
    public void forbid() {
        influxBaseOurDao.dropRetentionPolicyDefault();
        influxBaseOurDao.createRetentionPolicyDefault();
    }

    //启用保存策略
    public void use() {
        influxBaseOurDao.dropRetentionPolicyDefault();
        influxBaseOurDao.createRetentionPolicy();
    }

    //SDT旋转门算法压缩数据
    public List<HistoryData> SDTcompress(List<HistoryData> originData){
        List<HistoryData> listSDT=new ArrayList<>();
        if (originData.size() <= 0)
            return listSDT;
        for(HistoryData p : originData){
            if(IfdbDateUtils.isDouble(p.getValue())) { //当数值是double时才进行压缩,否则不压缩
                double nowUp, nowDown;//当前数据的上下斜率
                SDTPoints status = statusMap.get(p.getDevicePointCode());
                if(status==null){
                    status = new SDTPoints();
                    status.setLastReadData(p);
                    status.setLastStoredData(status.getLastReadData());
                    listSDT.add(status.getLastReadData());
                }
                status.setCurrentData(p);
                if((p.getTime() - status.getLastStoredData().getTime())% (1000 * 24 * 60 * 60) / (1000 * 60 * 60)<1){ //当前时间与上次插入时间相差1个小时内,则继续
                    nowUp = (Double.valueOf(p.getValue()) - Double.valueOf(status.getLastStoredData().getValue()) - accuracyE)/(p.getTime() - status.getLastStoredData().getTime());
                    if (nowUp > status.getUpGate())
                        status.setUpGate(nowUp);
                    nowDown = (Double.valueOf(p.getValue()) - Double.valueOf(status.getLastStoredData().getValue()) + accuracyE)/(p.getTime() - status.getLastStoredData().getTime());
                    if (nowDown < status.getDownGate())
                        status.setDownGate(nowDown);
                    if (status.getUpGate() >= status.getDownGate()){
                        listSDT.add(status.getLastReadData());//保存前一个点
                        status.setLastStoredData(status.getLastReadData()) ;//修改最近保存的点
                        status.setUpGate((Double.valueOf(p.getValue())-Double.valueOf(status.getLastStoredData().getValue())-accuracyE)/ (p.getTime() - status.getLastStoredData().getTime()));
                        status.setDownGate((Double.valueOf(p.getValue()) - Double.valueOf(status.getLastStoredData().getValue()) + accuracyE) / (p.getTime() - status.getLastStoredData().getTime()));
                    }
                }else{ //大于1个小时直接保存
                    listSDT.add(status.getLastReadData());//保存前一个点
                    status.setLastStoredData(status.getLastReadData()) ;//修改最近保存的点
                    status.setUpGate((Double.valueOf(p.getValue())-Double.valueOf(status.getLastStoredData().getValue())-accuracyE)/ (p.getTime() - status.getLastStoredData().getTime()));
                    status.setDownGate((Double.valueOf(p.getValue()) - Double.valueOf(status.getLastStoredData().getValue()) + accuracyE) / (p.getTime() - status.getLastStoredData().getTime()));
                }
                status.setLastReadData(p);
                statusMap.put(p.getDevicePointCode(),status);
            }else{
                listSDT.add(p);//直接保存
            }
        }
        return listSDT;
    }

    /**
     * @param tids     对应业务的唯一标示, startTime开始查询时间 ,endTime结束时间
     * @param pageSize
     * @param pageNum
     * @return 参数
     * Map<String,Object>    返回类型
     * @throws
     * @Title: queryDataDataForPage
     * @Description: 获取分页数据(包含数据表和字符表)
     */
    public Map<String, Object> queryDataDataForPage(List<String> tids, Date startTime, Date endTime, int pageSize, int pageNum) {
        Map<String, Object> rsMap = new HashMap<>();

        String command = influxBaseOurDao.joinWhere(tids, startTime, endTime);
        rsMap = influxBaseOurDao.queryDataDataByOffset(analogtable, command, pageSize, pageNum);
        if (rsMap.get("total") != null && (Integer) rsMap.get("total") != 0) {
            return rsMap;
        }
        rsMap = influxBaseOurDao.queryDataDataByOffset(mixtable, command, pageSize, pageNum);
        return rsMap;
    }

    /**
     * @param tids 对应业务的唯一标示, startTime开始查询时间 ,endTime结束时间
     * @return 参数
     * List<CommonData>   返回类型
     * @throws
     * @Title: queryDataData
     * @Description: 获取历史数据(包含数据表和字符表)
     */
    public List<CommonData> queryDataData(List<String> tids, Date startTime, Date endTime) {
        List<CommonData> commonDataList = new ArrayList<>();
        String command = influxBaseOurDao.joinWhere(tids, startTime, endTime);

        commonDataList = influxBaseOurDao.queryHistoryData(analogtable, command);
        if (commonDataList != null && commonDataList.size() > 0) {
            return commonDataList;
        }
        commonDataList = influxBaseOurDao.queryHistoryData(mixtable, command);
        return commonDataList;
    }

    /**
     * @param tids      对应业务的唯一标示 tid集合(查设备测点时,tid为device_point_code;查能效时,tid为:mc_参数id)
     * @param startTime 开始时间  格式必须是 "2019-02-02 00:00:00" 时分秒必须为00:00:00 否则数据会有问题!
     * @param endTime   结束时间    格式必须是 "2019-02-08 23:59:59" 时分秒必须为23:59:59 否则数据会有问题!
     * @param type      1:最大值 2:最小值 3:平均值 4:瞬时值
     * @return 参数
     * List<CommonData>   返回类型
     * @throws
     * @Title: queryDayData
     * @Description: 根据tid和时间范围获取数据表的每天对应值(平均值、最大值、最小值、瞬时值),
     * 由于influxdb存入的时间与现在差8个小时,如果用1天来求对应值会导致求出每天数据有误(出现偏移),这边用8小时作为求(平均值、最大值、最小值、瞬时值),
     * 求出后在将每3个数据再求对应值获得一天的(平均值、最大值、最小值、瞬时值),
     */
    public List<CommonData> queryDayData(List<String> tids, Date startTime, Date endTime, String type) {
        List<CommonData> commonDataList = new ArrayList<>();
        String command = influxBaseOurDao.joinWhere(tids, startTime, endTime);

        commonDataList = influxBaseOurDao.queryDayData(command, type);
        return commonDataList;
    }

    //根据tid和时间范围获取数据表的每小时对应值(平均值、最大值、最小值、瞬时值),无需转换,直接取值
    public List<CommonData> queryHourData(List<String> tids, Date startTime, Date endTime, String type) {
        List<CommonData> commonDataList = new ArrayList<>();
        String command = influxBaseOurDao.joinWhere(tids, startTime, endTime);

        commonDataList = influxBaseOurDao.queryHourData(command, type);
        return commonDataList;
    }
}

五。数据压缩

由于工业上可能产生很多无用的历史数据,需要将数据压缩后进行存储,业务需求也只需要一个小时至少存一条数据即可,这边引入旋转门压缩算法。

旋转门算法是一种比较快速的线性拟合算法,常常用于实时数据库中对数据进行压缩,使存储容量大大的减少。在实时数据库中,数据通常具有如下特点:1. 数据采集量大。2. 数据临近度高。

java每秒在数据库中顺序读取一条 java使用时序数据库_influxdb_05

//SDT旋转门算法压缩数据
    public List<HistoryData> SDTcompress(List<HistoryData> originData){
        List<HistoryData> listSDT=new ArrayList<>();
        if (originData.size() <= 0)
            return listSDT;
        for(HistoryData p : originData){
            if(IfdbDateUtils.isDouble(p.getValue())) { //当数值是double时才进行压缩,否则不压缩
                double nowUp, nowDown;//当前数据的上下斜率
                SDTPoints status = statusMap.get(p.getDevicePointCode());
                if(status==null){
                    status = new SDTPoints();
                    status.setLastReadData(p);
                    status.setLastStoredData(status.getLastReadData());
                    listSDT.add(status.getLastReadData());
                }
                status.setCurrentData(p);
                if((p.getTime() - status.getLastStoredData().getTime())% (1000 * 24 * 60 * 60) / (1000 * 60 * 60)<1){ //当前时间与上次插入时间相差1个小时内,则继续
                    nowUp = (Double.valueOf(p.getValue()) - Double.valueOf(status.getLastStoredData().getValue()) - accuracyE)/(p.getTime() - status.getLastStoredData().getTime());
                    if (nowUp > status.getUpGate())
                        status.setUpGate(nowUp);
                    nowDown = (Double.valueOf(p.getValue()) - Double.valueOf(status.getLastStoredData().getValue()) + accuracyE)/(p.getTime() - status.getLastStoredData().getTime());
                    if (nowDown < status.getDownGate())
                        status.setDownGate(nowDown);
                    if (status.getUpGate() >= status.getDownGate()){
                        listSDT.add(status.getLastReadData());//保存前一个点
                        status.setLastStoredData(status.getLastReadData()) ;//修改最近保存的点
                        status.setUpGate((Double.valueOf(p.getValue())-Double.valueOf(status.getLastStoredData().getValue())-accuracyE)/ (p.getTime() - status.getLastStoredData().getTime()));
                        status.setDownGate((Double.valueOf(p.getValue()) - Double.valueOf(status.getLastStoredData().getValue()) + accuracyE) / (p.getTime() - status.getLastStoredData().getTime()));
                    }
                }else{ //大于1个小时直接保存
                    listSDT.add(status.getLastReadData());//保存前一个点
                    status.setLastStoredData(status.getLastReadData()) ;//修改最近保存的点
                    status.setUpGate((Double.valueOf(p.getValue())-Double.valueOf(status.getLastStoredData().getValue())-accuracyE)/ (p.getTime() - status.getLastStoredData().getTime()));
                    status.setDownGate((Double.valueOf(p.getValue()) - Double.valueOf(status.getLastStoredData().getValue()) + accuracyE) / (p.getTime() - status.getLastStoredData().getTime()));
                }
                status.setLastReadData(p);
                statusMap.put(p.getDevicePointCode(),status);
            }else{
                listSDT.add(p);//直接保存
            }
        }
        return listSDT;
    }

至此基本满足业务需求。