时序数据探索(3)——数据导入InfluxDB方法之直接导入

0 前言

这一个系列是针对SRCC公司的海量传感器数据的存储及处理探索。硬件为戴尔R730xd,系统为Windows Server 2012R。

参考教程:Getting Started: Writing Data to InfluxDB
相关的 Github链接

初学者教程,关于怎么将静态数据批量导入InfluxDB中,本文使用如下三种方法:

  • 直接导入InfluxDB
  • 通过Chronograf 上传数据
  • 使用Telegraf 及其插件

本文只测试第一种方法,直接导入InfluxDB,有兴趣的同学可以再去试试后面两种。(因为我已经有了更适合自己的办法)

本文采用的数据为原始数据中,手动抽出的一部分,CSV格式,如图:
时序数据探索(3)——数据导入InfluxDB方法之直接导入_第1张图片
数据的具体含义已在上一篇文章中介绍完毕,不赘述。

在开始之前,作者强烈建议,将时间戳转为纳秒级(nanosecond precision)。
虽然在写入数据时,你可以指定时间戳量级,但一些插件可能并不支持每个类型的时间戳,花一点点时间转换一下你的时间戳将简化工作量,可以使用Timestamp_Precision.py,具体代码见 github。

3.1 直接导入InfluxDB

如果数据大于25MB,还是使用直接导入InfluxDB要简单快速的多。

  1. 创建import.txt文件,写入以下内容:
# DDL
CREATE DATABASE import

# DML
# CONTEXT-DATABASE: import
# CONTENT-RETENTION-POLICY: autogen

上面DDL创建了一个database,名为“import”,而DML用于你早就创建了"import”的情况,RP使用默认的。
时序数据探索(3)——数据导入InfluxDB方法之直接导入_第2张图片
这里面有两点注意:在DDL和DML之间有(至少)一行空行;文件指针要停在语句的最后一个空行,说人话,就是在autogen后,再回车一下,别将光标停在autogen就不动了,后面会一点点小问题,小问题。

Retention-Policy是什么:
官方:A retention policy describes how long InfluxDB keeps data (DURATION) and how many copies of those data are stored in the cluster (REPLICATION).
就是定义了一下存储的时长,副本数量等信息,默认时长为永远,副本数量为1.

  1. 将CSV文件转为line protocol,其中并将其追加(append)在 import.txt之后,
    程序及解释见后文
    如下:

时序数据探索(3)——数据导入InfluxDB方法之直接导入_第3张图片
其中,对InfluxDB一些基本概念不熟悉的同学可以去查一下。

import database
measurements input
BTC type
point23 / point39 field
151823573xxxx timestamp
  1. 开始导入
    开启influxd.exe,再在命令行中执行以下语句:
influx -import -path=yourfilepath -precision=ns

时序数据探索(3)——数据导入InfluxDB方法之直接导入_第4张图片

  1. 验证是否导入成功
    在influxdb中尝试查询:
    时序数据探索(3)——数据导入InfluxDB方法之直接导入_第5张图片

关于precision rfc3339
converts the Unix timestamps to human-readable time.
加上这句后,显示的time为“Y-m-dTHH:MM:SS”格式;
若不加次语句,显示的time则为“151823573xxxx”;

这就说明,对于这样的数据,是可以导入到influxdb中的,那么,接下来就是把自己的数据整成这样的格式了。

3.2 数据源格式问题

我的原始数据如下:

有几个问题要处理:

  • 表头要去掉。从BEGIN,到 END之间都是表头,然后才是一行行的数据;
  • 列数不一定。每个文件的通道数(列数)不一定。
  • 没有时间戳。这些数其实是一个波形图,横坐标(时间:秒)是不显示的,只显示纵坐标,也就是各个数据值;
  • 只有相对时间,没有绝对时间。每个文件的横坐标都是从0开始的4个小时,而数据库中的UTC时间戳。

所以,针对上面这几个问题,来逐个击破。

3.3 去表头

  • 思路:
    不管表头多少行,也不管其内容是啥,我看到两个特征,就是由"BEGIN"开始,到"END"结束,而且,这个BEGIN是第一行,我不用管,只要找到END在哪一行,这之前的全删掉就行了;

file_path = r'C:\data\origin_data\point_19--22_part.asc'
# 导入文件
df = pd.DataFrame(pd.read_csv(file_path, header=None, names = ['Point']))
# 读取END所在行的行数
line_end = df[df['Point'].isin(['END'])].index.values
# print(line_end)

# 删除前面几行
df_data = df.drop(np.arange(line_end+1), inplace=False)

3.4 列数确定

虽然我不知道每个文件会有多少列,但我知道,最多能有多少列,这样,我就把所有的列都写出来,然后根据实际数据中的列数,再来确定列名。。。

# 导入文件
df = pd.DataFrame(pd.read_csv(file_path, header=None, names = ['Point']))

因为原始数据为asc文件,也就是txt文件,刚读取的时候,所有数据作为一列,名为“Point”。

出来的数据大概为:
时序数据探索(3)——数据导入InfluxDB方法之直接导入_第6张图片

原始数据中,各个单元数据之间是空白符(不仅仅是空格,还有制表符)分开的,所以在去除表头之后,接下来就要分列了。

df_split = pd.DataFrame((x.split() for x in df_data['Point']), 
index=((df_data.index-int(line_end)+1)/1024+time_creative))

这里对原始数据的每一行进行切分,然后将矫正后的时间作为索引。

index这句待会讲。

上面讲所有的数据进行分列了,那么列名有分别是啥呢?

# 本项目中,所有可能的列名
columns_family = ['Point1','Point2','Point3',...,'Point10',...,'Point80']
# 修改列名
df_split.columns = columns_family[:df_split.columns.size]
df_split.index.name = "Time"

这样大概的数据模样就出来了。

基本就是m行*n列的标准格式了。

3.5 时间戳转换

先要把代表时间的横坐标表示为绝对时间,再结合相对时间,转换为正确的时间。

# 获取文件的创建时间
def get_FileCreateTime(filePath):
    t = os.path.getctime(filePath)
    return t
time_creative = get_FileCreateTime(file_path)

这个函数获取的是文件的创建/修改时间,秒级,UTC时间格式,如下:

在这里插入图片描述

因为本项目中的文件创建时间,就是传感器开机工作的时间,也就是波形图开始记录的时间,所以可以这么做。

再就是刚才index那句了。

index=((df_data.index-int(line_end)+1)/1024+time_creative))

前面的文章,原始数据说明里讲了,此数据采用频率为1024Hz,所以这里将行数,也就是点数,除以1024,就得到其相对时长多少秒,再加上秒级的开始时间,就为该数据的绝对时间了。

3.6 导出为CSV文件

将数据规整好以后,就导出为CSV文件了。

为啥呢?因为看3.1中的方法啊啊啊,就是将CSV文件读取为line protocol形式,再存入influxdb中的啊。

导出来大致就是这样:

时序数据探索(3)——数据导入InfluxDB方法之直接导入_第7张图片

注意

同学,你没有看错,最后的最后,还有个逗号。。。待会会讲到。

3.7 CSV_to_Line

看一下line protocol的数据格式,

[,=...] =[,=...] [unix-nano-timestamp]

解释一下,第一列是表名称,然后逗号隔开,一系列的tag名称和值,再就是空格分隔,field的值,这就是本项目中的各列数据,而这各个列之间是拿逗号隔开的。

直到最后,再用空格分开,最后一列为timestamp。

所以,还是有点绕的,也就是说

表名称,tag1=val1 point1=val1,point2=val2 ... ,point2=val2 timestamp

所以,怎么做呢?

csv_file = r'C:\ZYJ_InfluxDB\code\csv_test.csv'
# 读文件
df_full = pd.DataFrame(pd.read_csv(csv_file,low_memory=False))
# 表格名为input,(table/measurement)
df_full["measurement"] = ['input' for n in range(len(df_full))]
# 可以打印出来看一下
print("**********************")
print(df_full.loc[:3])   # 前三行
print("**********************")
print(df_full["Point3"][0:2])

这里先增加了一列,就是line protocol中的第一列,表名称,但是要注意的是,上面的增加列的操作,默认是加在了csv文件的最后一列。

所以,接下来就来一起变个形吧。


# 使用生成器
def get_line(df_full):
	line = []
	cols = df_full.columns.values.tolist()
	print(cols)		# ['Time', 'Point1', 'Point2', 'Point3', 'Point4', 'measurement']
	for row in range(len(df_full)):
		field = ""
		for col in range(1,len(cols)-1):
			field += (str(cols[col]) + "=" + str(df_full.iat[row, col])) + ","

		field = field.rstrip().strip(string.punctuation)
		line = [str(df_full["measurement"][row]) + ",type=test" 
				+ " " 
				+ field
				+ " " 
				+ str(df_full["Time"][row])]
		yield line

lines = get_line(df_full)

# 打印看看
print("----")
print(next(lines))
print("----")

要是看过上面官方参考教程的同学就会发现,我用的完全不是参考文档里的方法,为了防止数据文件过大,用生成式生成列表会内存崩掉,我使用了生成器。

这里我将加过一列(表名称)的dataframe传入,然后就是嵌套for循环啊,对每一行的数据,依次加上每一列。

有同学可能觉得多次一举,直接读取行就行了,为啥要再对每列for循环一次呢,这样时间复杂度就大大增加了啊。

问题是,这个line protocol和我们的dataframe,列的排布不一样,然后没个line里面还要加上pointxx = value这种字样,原始数据就只有value啊,再其次,中间还穿插着两个空格。。。so。。

这个程序应该不难理解,先是取出列名list,然后对每行for循环的范围为

for col in range(1,len(cols)-1)

这是因为,第一列(0)是time索引啊,最后一列是新加入的表名称(input)啊

中间就是按照要求的格式来咯。这里有个问题,就是在最后一个point列加完以后,末尾会多出一个逗号,就像上面注意的那样。。但line protocol要求最后的空格分开的啊。

于是,就有了这么一行程序:

field = field.rstrip().strip(string.punctuation)

这个是不是有点奇怪?因为strip()函数只能去除空白,碰到其他东西(如逗号)就停了,而strip(string.punctuation)相反。。同学们可以自己测试一下

s0 = "~~hello, influxdb,,..   "		# 最后有几个空格
s1 = s0.strip()						# "~~hello, influxdb,,.."
s2 = s0.strip(string.punctuation)		# "hello, influxdb,,..   "
s3 = s1.strip(string.punctuation)		# "hello, influxdb"

ok了。

3.8 import.txt

将上面的生成的line protocol追加到import.txt中吧。

#append lines to a text file with DDL & DML:
with open('import.txt', 'a+') as thefile:
	for item in lines:
		thefile.write("%s\n" % item)

对接上面3.1的教程。完事

3.x 后记

这个方法走了弯路,而且效率不高,因为经历了

读取asc文件 ---> 处理后存为csv文件 ----> 读取csv文件 ---> 双层for循环处理后存为txt文件。

这里光是读 / 写磁盘都用了4次,所以效率应该很差。

直到昨天我还在想,中间能不能不存为csv文件,直接从dateframe操作到txt文件,然后中间对接的时候bug不断,真的是要脑子瓦特了。

突然想到自己咋这么笨呢,为啥不能想python操作MySQL一样的来操作InfluxDB呢。

去找一下官方的API文档,哇,吐血。所以,下一篇来讲讲python操作influxDB吧。

最后贴一下这个方法的源码

  1. asc_to_csv.py
# -*- coding:utf-8 -*-

import numpy as np
import pandas as pd
import time
import os

# 获取文件的创建时间
def get_FileCreateTime(filePath):
    t = os.path.getctime(filePath)
    return t

# 计时开始
# start = time.clock()

# 获取文件创建时间的时间戳
file_path = r'C:\ZYJ_InfluxDB\data\origin_data\point_19--22_part.asc'
time_creative = get_FileCreateTime(file_path)

# 导入文件
df = pd.DataFrame(pd.read_csv(file_path, header=None, names = ['Point']))

# 读取END所在行的行数
line_end = df[df['Point'].isin(['END'])].index.values
# print(line_end)
# 删除前面几行
df_data = df.drop(np.arange(line_end+1), inplace=False)
# 本项目中,所有可能的列名
columns_family = ['Point1','Point2','Point3',...,'Point10',...,'Point80']

# 分列,索引作为时间戳
df_split = pd.DataFrame((x.split(" +") for x in df_data['Point']), index=((df_data.index-int(line_end)+1)/1024+time_creative))

# 修改列名
df_split.columns = columns_family[:df_split.columns.size]
df_split.index.name = "Time"

# 导出csv
df_split.to_csv("./csv_test.csv", index=True, sep=',')

# 计时
# end = time.clock()
# elapsed = end - start
# print("Time used:", elapsed)
  1. csv_to_line.py
#convert csv's to line protocol
import pandas as pd
import numpy as np
import pandas as pd
import time
import os
import string

# 计时开始
# start = time.clock()

# 使用生成器,而不是生成式
def get_line(df_full):
	line = []
	cols = df_full.columns.values.tolist()
	print(cols)		# ['Time', 'Point1', 'Point2', 'Point3', 'Point4', 'measurement']
	for row in range(len(df_full)):
		field = ""
		for col in range(1,len(cols)-1):
			field += (str(cols[col]) + "=" + str(df_full.iat[row, col])) + ","

		field = field.rstrip().strip(string.punctuation)
		line = [str(df_full["measurement"][row]) + ",type=test" 
				+ " " 
				+ field
				+ " " 
				+ str(df_full["Time"][row])]
		yield line

# --------------------------------------------------
asc_file = r'C:\ZYJ_InfluxDB\code\csv_test.csv'
# 导入文件
df_full = pd.DataFrame(pd.read_csv(asc_file,low_memory=False))
print("**********************")
print(df_full.loc[:3])

# 取列名,作为field字段名
# cols = df_full.columns.values.tolist()

# 表格名为input,(table/measurement)
df_full["measurement"] = ['input' for n in range(len(df_full))]
print("**********************")
print(df_full.loc[:3])
print("**********************")
print(df_full["Point3"][0:2])
lines = get_line(df_full)
print("----")
print(next(lines))
print("----")

#append lines to a text file with DDL & DML:
with open('import.txt', 'a+') as thefile:
	for item in lines:
		thefile.write("%s\n" % item)

# 计时结束
# end = time.clock()
# elapsed = end - start
# print("Time used:", elapsed)

你可能感兴趣的:(InfluxDB探索)