InfluxDB C++库介绍和使用
InfluxDB是现在使用排名最高的开源时序数据库,官方提供很多种语言的API进行库操作,包括Go、Java、JavaScript、JavaScript (Node.js)、PHP、Python等等,但是却不提供C++的支持。对于需要使用C++语言操作InfluxDB的场景,就有点麻烦。还好,gitbub上Influxdb-cxx项目提供了C++的方案。
这里介绍一下Influxdb-cxx的编译、裁剪和使用。需要注意的是,Influxdb-cxx需要使用支持C++17的编译器才能编译。
下载代码https://github.com/awegrzyn/influxdb-cxx
Influxdb-cxx提供了三种协议操作InfluxDB,分别为HTTP,UDP和Unix socket。
表1. Influxdb-cxx支持的协议及依赖的第三方库
Name |
Dependency |
HTTP |
libcurl |
UDP |
boost |
Unix socket |
boost |
HTTP协议依赖libcurl库,UDP和Unix socket依赖boost库。因为我们只需要使用HTTP协议,为了减少依赖库,需要做一些裁剪,修改makefile如下:
注释掉下面两行:
#set(Boost_USE_MULTITHREADED TRUE)
#set(Boost_USE_MULTITHREADED TRUE)
注释掉下面一行
#find_dependency(Boost)
这样,编译之后的库就只支持HTT协议了。
直接使用InfluxDB-cxx库查询数据会报404错误:
error:influx-cxx [HTTP::query]: Status code: 404
原因是代码有bug,构造的查询URL多了一个‘/’,如果将构造的URL打印出来,是下面的样子,多了一个红色位置的‘/’
http://tsuser:tsuser@localhost:8086//query?db=tsDB&q=select%20%2A%20from%20testTable
修改方法为:
修改HTTP.cxx
将函数void HTTP::initCurlRead(const std::string& url)中的
mReadUrl.insert(mReadUrl.find("?"), "query");
替换为如下代码:
auto position = mReadUrl.find("?");
if (position == std::string::npos) {
throw InfluxDBException("HTTP::initCurl", "Database not specified");
}
if (mReadUrl.at(position - 1) != '/') {
mReadUrl.insert(position, "/query");
} else {
mReadUrl.insert(position, "query");
}
依次运行下面的命令:
$cd influxdb-cxx; mkdir build
$cd build
$cmake ..
$sudo make install
动态库和头文件会被安装到如下目录:
/usr/local/lib/libInfluxDB.so
/usr/local/include/Transport.h
/usr/local/include/InfluxDB.h
/usr/local/include/Point.h
/usr/local/include/InfluxDBFactory.h
libInfluxDB.so依赖的第三方库如下:
$ ldd lib/libInfluxDB.so
linux-vdso.so.1 (0x00007ffe59ba3000)
libcurl.so.4 => /usr/lib/x86_64-linux-gnu/libcurl.so.4 (0x00007fa8b795e000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fa8b757e000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fa8b7366000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa8b6f75000)
libnghttp2.so.14 => /usr/lib/x86_64-linux-gnu/libnghttp2.so.14 (0x00007fa8b6d50000)
libidn2.so.0 => /usr/lib/x86_64-linux-gnu/libidn2.so.0 (0x00007fa8b6b33000)
librtmp.so.1 => /usr/lib/x86_64-linux-gnu/librtmp.so.1 (0x00007fa8b6917000)
libpsl.so.5 => /usr/lib/x86_64-linux-gnu/libpsl.so.5 (0x00007fa8b6709000)
libssl.so.1.1 => /usr/lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007fa8b647c000)
libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007fa8b5fb1000)
libgssapi_krb5.so.2 => /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007fa8b5d66000)
libldap_r-2.4.so.2 => /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2 (0x00007fa8b5b14000)
liblber-2.4.so.2 => /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2 (0x00007fa8b5906000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fa8b56e9000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa8b54ca000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa8b512c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa8b7def000)
libunistring.so.2 => /usr/lib/x86_64-linux-gnu/libunistring.so.2 (0x00007fa8b4dae000)
libgnutls.so.30 => /usr/lib/x86_64-linux-gnu/libgnutls.so.30 (0x00007fa8b4a48000)
libhogweed.so.4 => /usr/lib/x86_64-linux-gnu/libhogweed.so.4 (0x00007fa8b4814000)
libnettle.so.6 => /usr/lib/x86_64-linux-gnu/libnettle.so.6 (0x00007fa8b45de000)
libgmp.so.10 => /usr/lib/x86_64-linux-gnu/libgmp.so.10 (0x00007fa8b435d000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fa8b4159000)
libkrb5.so.3 => /usr/lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007fa8b3e83000)
libk5crypto.so.3 => /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007fa8b3c51000)
libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007fa8b3a4d000)
libkrb5support.so.0 => /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007fa8b3842000)
libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007fa8b3627000)
libsasl2.so.2 => /usr/lib/x86_64-linux-gnu/libsasl2.so.2 (0x00007fa8b340c000)
libgssapi.so.3 => /usr/lib/x86_64-linux-gnu/libgssapi.so.3 (0x00007fa8b31cb000)
libp11-kit.so.0 => /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 (0x00007fa8b2e9c000)
libtasn1.so.6 => /usr/lib/x86_64-linux-gnu/libtasn1.so.6 (0x00007fa8b2c89000)
libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007fa8b2a85000)
libheimntlm.so.0 => /usr/lib/x86_64-linux-gnu/libheimntlm.so.0 (0x00007fa8b287c000)
libkrb5.so.26 => /usr/lib/x86_64-linux-gnu/libkrb5.so.26 (0x00007fa8b25ef000)
libasn1.so.8 => /usr/lib/x86_64-linux-gnu/libasn1.so.8 (0x00007fa8b234d000)
libhcrypto.so.4 => /usr/lib/x86_64-linux-gnu/libhcrypto.so.4 (0x00007fa8b2117000)
libroken.so.18 => /usr/lib/x86_64-linux-gnu/libroken.so.18 (0x00007fa8b1f01000)
libffi.so.6 => /usr/lib/x86_64-linux-gnu/libffi.so.6 (0x00007fa8b1cf9000)
libwind.so.0 => /usr/lib/x86_64-linux-gnu/libwind.so.0 (0x00007fa8b1ad0000)
libheimbase.so.1 => /usr/lib/x86_64-linux-gnu/libheimbase.so.1 (0x00007fa8b18c1000)
libhx509.so.5 => /usr/lib/x86_64-linux-gnu/libhx509.so.5 (0x00007fa8b1677000)
libsqlite3.so.0 => /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 (0x00007fa8b136e000)
libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007fa8b1136000)
使用InfluxDBFactory::Get(std::string url)函数可以创建一个Infludb 的client。
具体说明如下:
#include
std::unique_ptr
入参
输入为一个字符串的URL,格式为:
[protocol]://[username:password@]host:port[/?db=database]
各个字段的说明为:
protocol:具体的协议,可以为http/https/udp/unix,见表2
username:访问数据库的用户名
password:用户名对应的密码
host:主机地址
port:端口号,默认为8086
db=database :database替换为使用的数据库
表2:协议格式及例子
Name |
Dependency |
URI protocol |
Sample URI |
HTTP |
cURL |
http/https |
http://localhost:8086/?db= |
UDP |
boost |
udp |
udp://localhost:8094 |
Unix socket |
boost |
unix |
unix:///tmp/telegraf.sock |
返回值
返回一个InfluxDB对象的指针,所有的操作通过此指针完成
示例代码:
#include "InfluxDBFactory.h"
#include "InfluxDBException.h"
#include
using namespace std;
int main()
{
std::unique_ptr dbPtr;
try
{
dbPtr = influxdb::InfluxDBFactory::Get("http://tsuser:tsuser@localhost:8086/?db=tsDB");
}
catch(influxdb::InfluxDBException& e)
{
cout <<"error:"<
使用InfluxDB::write()接口插入数据,具体格式为:
#include "InfluxDB.h"
void InfluxDB::write(Point&& metric);
入参是一个Point对象,所以重要的工作是将数据构造成Point对象。
表3:Point对象的方法
方法名 |
功能 |
Point::Point(const std::string& measurement) |
设定表的名字,在构造函数指定 |
Point&& Point::addField(std::string_view name, std::variant |
添加一个field键值对,field的值可以使init, long int,string和double |
Point&& Point::addTag(std::string_view key, std::string_view value) |
添加一个tag键值对, tag的值只能是string类型 |
std::string Point::getName() |
返回表明 |
std::string Point::getFields() |
返回所有的fields键值对,格式为:”name=value,name=value,name=value” |
std::string Point::getTags() |
返回所有的tags键值对,格式为:”name=value,name=value,name=value” |
示例代码:
#include "InfluxDBFactory.h"
#include "InfluxDBException.h"
#include
using namespace std;
int main()
{
std::unique_ptr dbPtr;
try
{
dbPtr = influxdb::InfluxDBFactory::Get("http://tsuser:tsuser@localhost:8086/?db=tsDB");
influxdb::Point point("testTable");
point.addField("Temperature", 100.0);
point.addTag("Device", "D1");
dbPtr->write(std::move(point));
}
catch(influxdb::InfluxDBException& e)
{
cout <<"error:"<
使用InfluxDB::query ()接口插入数据,具体格式为:
#include "InfluxDB.h"
std::vector
输入
输入为查询的sql语句,例如"SELECT * FROM test"
输出
输出为Point对象的vector。
可以通过表3中说明的Point类的方法获取实际的值。
注意,query ()接口只有在使用Boost库的情况下才能工作,因为需要使用Boost中的Json文件解析功能来处理数据库返回的结果。
示例代码:
#include "InfluxDBFactory.h"
#include "InfluxDBException.h"
#include
using namespace std;
int main()
{
std::unique_ptr dbPtr;
try
{
dbPtr = influxdb::InfluxDBFactory::Get("http://tsuser:tsuser@localhost:8086/?db=tsDB");
std::vector dbPoints = dbPtr->query("select * from testTable");
for(int i = 0; i < dbPoints.size(); i++)
{
cout << "===========================" << endl;
cout << "Name: " <
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/local/eiconfig/influxdb-cxx/lib
$ g++ -std=c++17 -o write write.cpp -L/local/eiconfig/influxdb-cxx/lib -lInfluxDB -I/local/eiconfig/influxdb-cxx/include
$ g++ -std=c++17 -o query query.cpp -L/local/eiconfig/influxdb-cxx/lib -lInfluxDB -I/local/eiconfig/influxdb-cxx/include
influxdb时间的默认显示使用的是unix时间戳,执行命令precision rfc3339 可以设置时间格式
> select * from testTable
name: testTable
time Device Temperature
---- ------ -----------
1602836361586833335 D1 100
> precision rfc3339
> select * from testTable
name: testTable
time Device Temperature
---- ------ -----------
2020-10-16T08:19:21.586833335Z D1 100
但是这里显示采用的时区为UTC时区,与中国时区差了8个小时,因此执行命令
select * from testTable tz('Asia/Shanghai'), 查询结果如下
> select * from testTable tz('Asia/Shanghai')
name: testTable
time Device Temperature
---- ------ -----------
2020-10-16T16:19:21.586833335+08:00 D1 100
1)时间格式为年月日
select * from testTable where time >='2020-10-16T16:19:21Z' tz('Asia/Shanghai')
这时,这里的时间是UTC 0时区的时间
> select * from testTable tz('Asia/Shanghai')
name: testTable
time Device Temperature
---- ------ -----------
2020-10-16T16:19:21.586833335+08:00 D1 100
> select * from testTable where time >='2020-10-16T16:19:21Z' tz('Asia/Shanghai')
查不到数据
> select * from testTable where time >='2020-10-16T08:19:21Z' tz('Asia/Shanghai')
name: testTable
time Device Temperature
---- ------ -----------
2020-10-16T16:19:21.586833335+08:00 D1 100
可以查到数据。
2)时间格式是秒
通过转换函数将'2020-10-16T16:19:21Z'转换为秒,因为influxDB中时间精确到纳秒,所以需要需要乘上6个0.
> select * from testTable where time < 1602838080000000000 and time > 1602837598559188326;
name: testTable
time Device Temperature
---- ------ -----------
1602837655477990673 D1 100
1602837852824072231 D1 100
1602837944389538189 D1 100
1602838064225581200 D1 100
>
具体的用法在官方文档中https://docs.influxdata.com/influxdb/v1.8/tools/api/#query-http-endpoint 有详细说明。
Curl的用法可以参考http://www.ruanyifeng.com/blog/2019/09/curl-reference.html
简单说明一下:
1) -G参数是指定用get方法,如果不加-G,则是post方法。
2)--data-urlencode参数将发送的数据进行 URL 编码,例如发送的数据之间如果有空格,就需要需要进行 URL 编码
写数据库
curl -i -XPOST "http://localhost:8086/write?db=tsDB&u=tsuser&p=tsuser" --data-binary 'mymeas,mytag=1 myfield=91'
查询数据库
curl -G 'http://localhost:8086/query?db=tsDB' --data-urlencode 'q=SELECT * FROM "testTable"'
编码之后的实际URL为:
http://tsuser:tsuser@localhost:8086/query?db=tsDB&q=select%20%2A%20from%20testTable
将此URL输入浏览器也可查询到数据。