这一个系列是针对SRCC公司的海量传感器数据的存储及处理探索。硬件为戴尔R730xd,系统为Windows Server 2012R。
参考教程:Getting Started: Writing Data to InfluxDB
相关的 Github链接
初学者教程,关于怎么将静态数据批量导入InfluxDB中,本文使用如下三种方法:
本文只测试第一种方法,直接导入InfluxDB,有兴趣的同学可以再去试试后面两种。(因为我已经有了更适合自己的办法)
本文采用的数据为原始数据中,手动抽出的一部分,CSV格式,如图:
数据的具体含义已在上一篇文章中介绍完毕,不赘述。
在开始之前,作者强烈建议,将时间戳转为纳秒级(nanosecond precision)。
虽然在写入数据时,你可以指定时间戳量级,但一些插件可能并不支持每个类型的时间戳,花一点点时间转换一下你的时间戳将简化工作量,可以使用Timestamp_Precision.py
,具体代码见 github。
如果数据大于25MB,还是使用直接导入InfluxDB要简单快速的多。
import.txt
文件,写入以下内容:# DDL
CREATE DATABASE import
# DML
# CONTEXT-DATABASE: import
# CONTENT-RETENTION-POLICY: autogen
上面DDL创建了一个database,名为“import”,而DML用于你早就创建了"import”的情况,RP使用默认的。
这里面有两点注意:在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.
line protocol
,其中并将其追加(append)在 import.txt之后,程序及解释见后文
。
其中,对InfluxDB一些基本概念不熟悉的同学可以去查一下。
import | database |
measurements | input |
BTC | type |
point23 / point39 | field |
151823573xxxx | timestamp |
influxd.exe
,再在命令行中执行以下语句:influx -import -path=yourfilepath -precision=ns
关于
precision rfc3339
:
converts the Unix timestamps to human-readable time.
加上这句后,显示的time为“Y-m-dTHH:MM:SS”格式;
若不加次语句,显示的time则为“151823573xxxx”;
这就说明,对于这样的数据,是可以导入到influxdb中的,那么,接下来就是把自己的数据整成这样的格式了。
我的原始数据如下:
有几个问题要处理:
所以,针对上面这几个问题,来逐个击破。
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)
虽然我不知道每个文件会有多少列,但我知道,最多能有多少列,这样,我就把所有的列都写出来,然后根据实际数据中的列数,再来确定列名。。。
# 导入文件
df = pd.DataFrame(pd.read_csv(file_path, header=None, names = ['Point']))
因为原始数据为asc文件,也就是txt文件,刚读取的时候,所有数据作为一列,名为“Point”。
原始数据中,各个单元数据之间是空白符(不仅仅是空格,还有制表符)分开的,所以在去除表头之后,接下来就要分列了。
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列的标准格式了。
先要把代表时间的横坐标表示为绝对时间,再结合相对时间,转换为正确的时间。
# 获取文件的创建时间
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,就得到其相对时长多少秒,再加上秒级的开始时间,就为该数据的绝对时间了。
将数据规整好以后,就导出为CSV文件了。
为啥呢?因为看3.1中的方法啊啊啊,就是将CSV文件读取为line protocol形式,再存入influxdb中的啊。
导出来大致就是这样:
同学,你没有看错,最后的最后,还有个逗号。。。待会会讲到。
看一下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了。
将上面的生成的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的教程。完事
这个方法走了弯路,而且效率不高,因为经历了
读取asc文件 ---> 处理后存为csv文件 ----> 读取csv文件 ---> 双层for循环处理后存为txt文件。
这里光是读 / 写磁盘都用了4次,所以效率应该很差。
直到昨天我还在想,中间能不能不存为csv文件,直接从dateframe操作到txt文件,然后中间对接的时候bug不断,真的是要脑子瓦特了。
突然想到自己咋这么笨呢,为啥不能想python操作MySQL一样的来操作InfluxDB呢。
去找一下官方的API文档,哇,吐血。所以,下一篇来讲讲python操作influxDB吧。
最后贴一下这个方法的源码
# -*- 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)
#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)