ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

QT操作InfluxDB:从数据写入到曲线查询的完整实践指南

QT操作InfluxDB:从数据写入到曲线查询的完整实践指南 简介面向QT开发者与时间序列数据场景该压缩包整合了QT Creator 5.12.10环境下操作InfluxDB的完整工程与客户端库核心解决C/QT应用向InfluxDB写入、查询、分析时序数据的问题适用于监控、物联网及大数据采集类项目。压缩包共2000个文件以hpp头文件为主辅以ipp、h、cpp等源码文件另有少量构建脚本与说明文档整体大小22.74MB目录结构清晰方便按需提取。内容围绕libcurl 7_82_0进行HTTP通信、QNetworkAccessManager异步请求、QJsonDocument构造与解析JSON、InfluxDB写入和查询API及line protocol格式并覆盖异步信号槽处理、错误捕获、身份认证和批处理调优等关键点实测环境下可实现10万条记录约4秒完成更新。包内influxdb-cxx-master提供了可复用的客户端实现与示例代码能帮助开发者快速理解协议封装和工程集成方式避开数据传输与性能上的常见坑。已有897人学习浏览适合具备一定C基础、希望直接在QT项目中接入InfluxDB的开发者参考。1. QT操作influxdb为什么你的界面程序需要一个时序数据库我最早接触QT操作influxdb是在一台上位机设备上。界面用QT写数据点是几十个温度、压力、流量通道当时用内存环形缓冲存历史趋势客户一句“我要看昨天的曲线”直接卡壳。换成一个本地部署的influxdb之后写入用HTTP一推查询按时间一拉功能就完整了。这个标题真正讲的事不是给QT找什么数据库专用驱动而是把你用QT写的界面程序接到influxdb这根时序数据的管子上让它能写、能查、能画曲线。适合正在做设备监控、能源管理、环境采集、IoT边缘网关这一类天然带时间标签数据的开发也适合那些业务系统已经用了influxdb、现在需要给桌面端补一个数据入口的场景。它的价值在于influxdb对时间序列做了索引和压缩查询“某台设备过去一年温升曲线”这种需求比在MySQL里按时间戳扫表快得多而且部署极轻。下面从选型开始把一条能落地的路线完整走一遍。2. 开工前先定两件事influxdb版本和QT侧的连接路线2.1 influxdb 1.x和2.x的模型差异先理清database、bucket、token这条线做过几年QT开发的人都有体会搜资料时最大的混乱来源是influxdb自身的版本割裂1.x和2.x不只是界面变化数据模型完全是两套。1.x用的是经典的database概念写入和查询分别走/write和/query两个端点鉴权靠用户名密码查询语言叫InfluxQL长得像SQL。2.x则改成了org加bucket的组织方式鉴权用Token字符串查询默认走Flux是从时序数据角度重新设计的函数式查询语言。这个差异直接影响QT代码里的URL拼接和Header设置。比如1.x写入URL是http://127.0.0.1:8086/write?dbmydb2.x则要写成http://127.0.0.1:8086/api/v2/write?orgmyorgbucketmybucketprecisionns并且要在请求头里带Authorization: Token。查询更是两套语法1.x用SELECT2.x用from(...) | range(...)。所以第一步必须确认后端到底跑的是哪个版本。我用一个表把差别列清楚维度influxdb 1.8.xinfluxdb 2.x数据组织单位database retention policybucket自带保留期鉴权方式用户名/密码Token写入端点/write?dbxxx/api/v2/write?org..bucket..查询端点/query?dbxxxq.../api/v2/queryPOSTFlux查询返回格式JSONannotated CSV上手难度低接近老式数据库习惯中高Flux需要新学如果你是从零开始的一个新项目我建议直接上2.x虽然Flux有一点学习成本但长期维护不吃亏。如果是给老设备补功能或者部署环境是离线内网、运维更新困难那1.8.x反而更稳InfluxQL上手成本低到同事看一眼就会写。2.2 QT侧的三条连接路线官方C库、QNetworkAccessManager、libcurl确认了influxdb版本之后还要定QT这一侧用什么方式去连。我见到的方案有三类各有各的适用场景。第一类是官方C客户端库比如influxdb-cxx。优点是类型安全查询语句有封装适合一个团队长期围绕influxdb做产品。但代价是编译链重它依赖curl、openssl、boost还要考虑和QT的版本混用问题。很多项目在集成时被告知cannot find -lpublic或者链接顺序不对白耗一下午。我的判断是如果你的项目只是把influxdb当附属存储用而不是核心业务上官方库有点重。第二类是用QT自带的QNetworkAccessManager直接发HTTP请求。这几乎不需要装配QT项目里QT core network就有网络模块JSON解析用QJsonDocument都不用引第三方库。influxdb的写入和查询本质上就是一组HTTP接口所以这条路最贴合“QT操作influxdb”这类项目的实际需求。后面我给的方案主要就是这条线。第三类是用libcurl或者自己封装的HTTP库。一些老程序员习惯cURL那一套回调模型在命令行里怎么都通但到了QT工程里就要处理cURL的callback和QT信号槽之间的桥接稍不注意就出现跨线程访问。不是不能做只是多一层维护成本。2.3 我一般怎么选按部署形态和维护成本来决定我的选择逻辑可以归纳成三句话桌面单机采集场景选QNetworkAccessManager零额外依赖出问题好排查产品化多端部署、后端统一由平台组维护选influxdb 2.x加FluxQT端只做协议封装如果团队里已经有现成的cURL封装愿意投入精力去维护那也不拦着但要清楚QT的事件循环和cURL的select模型不是一回事线程模型要提前设计好否则后面用户反馈的闪退、卡死会集中爆发。版本上的建议是influxdb 1.8 InfluxQL QNetworkAccessManager适合快速交付和小型工具influxdb 2.x Token Flux QNetworkAccessManager适合长期演进的平台型项目。你在QT Creator里新建工程时模块只要勾上Network不用安装任何influxdb专用插件。3. 用QT原生HTTP写入influxdb最小可跑通流程3.1 环境准备与版本对应QT 5.15.2配哪个influxdb最省心先说环境。QT侧我一般用QT 5.15.2原因很简单5.15是LTS网上遇到问题能找到大量现成答案且QNetworkAccessManager的API稳定。QT 6.x也能用但HTTP请求头设置和部分枚举名有差异遇到问题可参考的资料还少一些。influxdb这侧如果你要配1.8.x直接用默认配置就能跑配2.x的话记得在启动参数里指定--reporting-disabled关掉遥测内网部署更干净。工程配置上QT项目只需要在.pro文件里确保一行QT core network如果后面要画曲线再加一行QT charts。influxdb的服务端部署在写代码之前就应该启动好用浏览器打开http://127.0.0.1:8086能看到界面说明服务活着。这一段看似废话但排查过很多报错后你会发现大半“连不上数据库”的问题其实是influxdb服务没起来或者端口被占用。3.2 写入一条数据从line protocol到POST请求influxdb的行协议是文本格式核心是四个部分测量名、标签、字段、时间戳。写法如下device_temp,device_iddev01,locationroom_a temperature23.5 1609459200000000000逗号前面是测量名逗号后面到第一个空格之间是tag空格后是field最后一个是纳秒时间戳。这里最难受的是格式细节tag之间用逗号tag和field之间用空格field如果有多个也用逗号但tag和field之间不能加逗号。新手第一次拼行协议十个有八个会在这里翻车。QT端把这条行协议拼出来再发送POST请求// 构建line protocol QString measurement device_temp; QString tags device_iddev01,locationroom_a; QString fields temperature23.5; qint64 nowNs QDateTime::currentMSecsSinceEpoch() * 1000000; // 毫秒转纳秒 QString line QString(%1,%2 %3 %4) .arg(measurement, tags, fields) .arg(nowNs); // 发送POST请求到influxdb 1.x QNetworkRequest req; req.setUrl(QUrl(http://127.0.0.1:8086/write?dbmydbprecisionns)); req.setHeader(QNetworkRequest::ContentTypeHeader, text/plain; charsetutf-8); QNetworkAccessManager *manager new QNetworkAccessManager(this); QNetworkReply *reply manager-post(req, line.toUtf8()); connect(reply, QNetworkReply::finished, this, [reply]() { int status reply-attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); qDebug() influxdb write status: status; if (reply-error() ! QNetworkReply::NoError) { qDebug() error: reply-errorString(); } reply-deleteLater(); });代码的逻辑是把测量名、标签、字段、时间戳拼成一个字符串转成UTF-8用QT网络模块做POST。参数说明上dbmydb是1.x的数据库名precisionns表示行协议里时间戳单位是纳秒。如果你的时间戳是秒级必须写成precisions否则数字会被当成纳秒处理查出来的时间点全部错乱。response的状态码是204表示写入成功这是influxdb的典型行为不是你发完请求就完事了要检查这个状态。3.3 批量写入与时间戳精度一次请求写多条别一条条磨如果你每秒采集几十个点还一条一条POST很快就会把HTTP连接栈压垮。influxdb的Line Protocol天然支持一个请求体里放多行每一行一个回车换行符分隔。批量写入的核心就是把多行拼成一个QByteArray一次POST发出去。QStringList lines; lines device_temp,device_iddev01,locationroom_a temperature23.5 1609459200000000000 device_temp,device_iddev01,locationroom_a humidity41.2 1609459200000000000 device_temp,device_iddev02,locationroom_b temperature22.1 1609459200000000000; QByteArray payload lines.join(\n).toUtf8(); // 把payload传给manager-post(req, payload)其余逻辑同单条写入批量大小我一般控制在5000行以内太大会出现HTTP请求超时。时间戳精度是批量写入里最容易出错的地方同一批数据里如果混用了秒和纳秒的时间戳influxdb会部分写入失败返回400并指出错误行。建议QT程序内部统一用毫秒时间戳存数拼行协议时统一乘以1000000转纳秒。你不要想着“反正服务端能识别”它默认就按纳秒处理精度参数是你和它之间的约定不是自动识别。3.4 写入成功的判断HTTP状态码与返回体细节很多人写QT时只看reply-error()是不是NoError但HTTP层面的4xx、5xx在QNetworkReply里同样会被标成错误你大概率能拿到errorString却不知道服务端具体拒了什么。正确做法是优先看HTTP状态码再看响应体内容。int status reply-attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (status 204) { qDebug() write ok; } else { QByteArray body reply-readAll(); qDebug() status: status body: body; }204是influxdb最常见的成功返回码查询返回200。如果是400或422服务端通常会把出错原因写进body里例如提示“unable to parse”后面会跟出错的那一行行协议内容。我遇到过的实际情况是标签值里带了中文空格行协议解析失败influxdb把整个batch都拒掉一个点都不写。所以批量写入时不要只看有没有报错有条件的话把响应体打出来看一眼。4. 数据查询与曲线展示把历史数据拉回QT界面4.1 InfluxQL和Flux在请求写法上的差异写入只是上半场QT操作influxdb的完整闭环是把历史数据查回到界面上画曲线。查询这一步InfluxQL和Flux的差别会直接体现在QT代码里。如果你用的是influxdb 1.x查询走的是GET请求参数放在URL里QString query SELECT mean(\temperature\) AS temp FROM \device_temp\ WHERE time now() - 1h GROUP BY time(5m); QUrl url(http://127.0.0.1:8086/query?dbmydb); QUrlQuery params; params.addQueryItem(q, query); url.setQuery(params); QNetworkRequest req(url); req.setRawHeader(Accept, application/json); // 用manager-get(req)发送响应体是JSONinfluxdb 2.x的Flux查询则要POST到/api/v2/query请求体里放Flux脚本Header带Token返回的默认是带#datatype前缀的CSV不是JSONQString flux from(bucket: \mydb\) | range(start: -1h) | filter(fn: (r) r._measurement \device_temp\ and r._field \temperature\) | mean();返回CSV给QT端带来的额外工作就是解析格式。虽然Flux是2.x的主流但为了降低工程复杂度我在QT项目里更倾向于在influxdb 2.x上走1.8兼容接口如果你的部署环境支持的话。如果不支持那就老老实实处理CSV。4.2 从JSON返回体里抠数据results、series、values三层结构以1.x的InfluxQL返回为例JSON结构是固定的三层results数组里是每个语句的结果每个结果里series数组对应一组返回列values才是实际数据行。解析代码可以这样写void parseInfluxResponse(const QByteArray raw) { QJsonDocument doc QJsonDocument::fromJson(raw); QJsonObject root doc.object(); QJsonArray results root.value(results).toArray(); if (results.isEmpty()) return; QJsonObject firstResult results.first().toObject(); if (firstResult.contains(error)) { qWarning() query error: firstResult.value(error).toString(); return; } QJsonArray seriesList firstResult.value(series).toArray(); QJsonObject series seriesList.first().toObject(); QJsonArray columns series.value(columns).toArray(); QJsonArray values series.value(values).toArray(); int timeIdx -1, valueIdx -1; for (int i 0; i columns.size(); i) { QString col columns.at(i).toString(); if (col time) timeIdx i; if (col temp) valueIdx i; } // 到这里values里每一行取 at(timeIdx) 和 at(valueIdx) 即可 }这段代码的用意是把返回JSON里的时间列和数值列找出来再逐行取数。参数说明上columns一定包含time列分组聚合后还会有mean、sum这类聚合列名。比较容易踩的坑是如果你查询时用了GROUP BY tag返回里会多出tag列列索引位置不对应写代码时不能用写死的固定下标必须像上面这样动态找列名。2.x的Flux返回CSV时前几行开头带#datatype的元信息行真正的数据从第一个不以#开头的行开始用逗号split后列顺序固定为result,table,_time,_value,_field,_measurement这一组。解析思路类似先split表头再逐行映射。4.3 画成曲线用QChart把查询结果接到界面数据解析出来后画曲线我用的是QT自带的QChart不需要引第三方绘图库。在.pro里确保有chart模块然后把解析出来的时间序列填进QLineSeries#include QtCharts/QChart #include QtCharts/QChartView #include QtCharts/QLineSeries using namespace QtCharts; void plotSeries(QLineSeries *series, const QVectorQPairqint64, double points) { for (const auto p : points) { // QLineSeries接收的是double坐标时间戳转成相对值便于显示 series-append(p.first, p.second); } QChart *chart new QChart(); chart-addSeries(series); chart-createDefaultAxes(); QChartView *view new QChartView(chart); // 把view加入QT界面布局即可 }这里有个实用细节influxdb返回的时间戳是纳秒或毫秒QChart的X轴直接用这么大的数字会出现刻度无法整除的问题。我通常先把第一批时间戳归一到0点偏移显示时再格式化。另外查询结果里可能出现空值点QChart对NaN值会断线处理时要把无效采样过滤掉否则曲线会出现从屏幕左上到右下的斜线看着像见鬼了。4.4 查询别堵UI线程异步与信号槽的正确打开方式QNetworkAccessManager的请求本身就是异步入队事件循环会回调finished信号所以入门者最容易犯的错是在按钮点击函数里用阻塞式写法等reply比如用QEventLoop嵌套或者干脆写while (!reply-isFinished())。这些做法轻则界面卡顿重则直接死锁因为finished信号的派发依赖事件循环继续跑而你的while循环又占住了事件循环。正确的做法是保持信号槽异步驱动后续数据量变大时把解析和画点的耗时操作丢到线程池里QtConcurrent::run([raw reply-readAll()]() { // 在这里做QJsonDocument的解析以及数据点转换 // 返回结果后再通过信号槽回到主线程刷新界面 });这里的逻辑是网络IO本身不阻塞UI线程但JSON解析和大量append到QLineSeries会阻塞。QtConcurrent把解析任务放后台线程完成后借助信号槽切回主线程更新图表。还有一个小建议1秒钟最多刷新一次UI就够了高频刷新除了让你的界面看起来像抽搐没有任何收益。5. QT操作influxdb避坑清单版本、崩溃、编码、数据查不到5.1 启动崩溃fatal: cannot mix incompatible qt library现象程序编译通过一启动就弹fatal: cannot mix incompatible qt library (version ex50601) with this library或者直接在main函数里崩掉。原因机器上装了两套以上QT版本编译时用的头文件和运行时加载的QT动态库不是同一套。这个情况在你从网上下载一个依赖QT的第三方库、或者手工改了PATH之后特别常见。解决先看qmake路径和QT Creator里配置的编译器路径是否一致。命令行下qmake -v查版本再看.pro文件所在目录的构建输出链接的是哪个Qt5Core.dll或libQt5Core.so。如果项目里引入了influxdb官方C库它编译时用的QT和你工程用的QT版本必须一致否则这个报错就会出现。最简单的排查办法是临时把PATH里多余的QT路径清掉只保留你需要的那一套。5.2 编译错 cannot find -lpublic现象QT工程编译时链接报cannot find -lpublic而且你根本没写过叫public的库。原因大多数人第一反应是代码问题实际上这个报错多半是.pro文件里LIBS写错或者手误多写了-l前缀。还有一种情况是QT Creator的构建目录和源文件目录不在一个地方库的相对路径不对。解决先打开.pro文件检查有没有类似LIBS -lpublic的笔误同时确认你引用的库文件实际名称是libinfluxdb-cxx而不是libpublic。如果项目里用了influxdb-cxx官方库还会遇到需要连接curl和openssl的问题需要在LIBS里按顺序加上-lcurl -lssl -lcrypto链接器是从右向左解析依赖的顺序反了照样报找不到符号。我把这个经验总结成一句话链接报错时先检查冒号前的库名是不是你真正想链接的那个名字。5.3 UI线程直接发HTTP请求导致界面卡死甚至崩溃现象按钮点击后界面无响应十几秒后QT弹窗提示程序停止工作错误码是0xc0000005这类访问冲突。原因在按钮槽函数里创建QNetworkAccessManager发请求然后用QEventLoop阻塞等待reply完成。QNetworkReply的finished信号需要事件循环流转才能派发你的QEventLoop虽然开启了新的事件循环但如果influxdb服务端没有响应或者网络超时设置不合理整个UI线程就被拖死。更糟的情况是QNetworkAccessManager在请求未完成时随函数栈析构网络请求的底层资源释放触发崩溃。解决让QNetworkAccessManager的生命周期跟随窗口类作为成员变量请求全部走信号槽异步返回禁止在业务代码里写同步等待。需要做真正的并发访问时另起QThread在线程里创建独立的QNetworkAccessManager并确保这个线程有事件循环在跑。这个坑在QT操作influxdb的项目里出现频率极高每次有人问我“为什么请求一多就崩溃”我先问的就是这句话你那个manager是不是在栈上new的。5.4 写入返回204却查不到数据时间戳精度与retention policy现象influxdb返回204QT界面也提示写入成功但用influxdb studio一查一条数据都没有。原因最容易查出来的是时间戳精度问题。influxdb的保留策略也很常见——如果你的1.x数据库创建了RETENTION POLICY且保留期是7天写入时没指定RP用的是默认策略那你查询老数据时自然什么都查不到。2.x的bucket创建时自带保留期默认是30天如果你忘记调整它自动把你的历史数据全清掉。解决写入请求里显式带上precisionns查询前先确认bucket或RP的保留时长。这已经是老生常谈但每次还会有人来。另一个我印象深刻的案例是QT程序里QDateTime取值用的单位是毫秒转纳秒时少乘一个1000写入的数据全部变成1970年1月1日附近的时间戳查询时查最近一小时当然查不到。你在验证数据时优先看influxdb studio里的原始时间戳一眼就能揪出这种问题。5.5 field值类型反直觉字符串没加引号、数字加了引号现象温度值写入后做平均值查询返回的数据全是0或者空但你看行协议没错。原因influxdb的field类型是写入时自动推断的。temperature23.5是浮点数temperature23.5是字符串。如果你把数值类型的字段用双引号包起来写进去后面SELECT mean(temperature)执行会返回空因为mean聚合函数根本不接受字符串。再一个衍生问题字符串field里的空格和逗号必须转义over 01这个字符串写成statusover 01会被influxdb当成格式错误拒绝整行。解决拼行协议之前先想清楚这个字段将来要不要做数值聚合。温度、湿度、流量这类全部不带引号状态码、告警信息这类字符串全部显式加双引号。在QT代码里可以写一个小校验函数if (isNumericField) append(value); else append(\ value \)。这个方法丑但能挡掉大半写入失败和查询为空的问题。6. 把操作封装成后台任务批量队列、重试与influxdb studio验证6.1 一个简单可靠的异步批量写入队列QT项目里每来一个数据点就发一次HTTP请求是不对的。我习惯做一个简单的写入队列类采集线程只负责把行协议字符串丢进队列QT的网络模块定时批量发送。class InfluxBatchWriter : public QObject { Q_OBJECT public: explicit InfluxBatchWriter(QObject *parent nullptr) : QObject(parent) { m_manager new QNetworkAccessManager(this); m_timer new QTimer(this); m_timer-setInterval(1000); connect(m_timer, QTimer::timeout, this, InfluxBatchWriter::flush); m_timer-start(); } public slots: void enqueue(const QString line) { m_batch.append(line); if (m_batch.size() 5000) flush(); } private slots: void flush() { if (m_batch.isEmpty()) return; QByteArray payload m_batch.join(\n).toUtf8(); m_batch.clear(); // 发送POST失败时可以把payload加回队列最多重试3次 } private: QNetworkAccessManager *m_manager; QTimer *m_timer; QStringList m_batch; };这样做的用意是采集端和网络端解耦采集线程只往队列里塞数据网络请求统一由定时器批次发出。参数上我设置积攒5000条或者1秒定时触发一次两个条件谁先到就发这样既保证吞吐量也保证了数据不会在内存里积压太久。重试逻辑要控制重试次数influxdb写入接口不是幂等的不检查结果就无限重发会导致重复数据。6.2 用influxdb studio快速验证数据和排查问题QT代码写完联调阶段我强烈建议用一个叫influxdb studio的工具辅助。它本质上是一个跨平台的influxdb图形化客户端可以直接查看数据库里有哪些measurement、有哪些字段、字段是什么类型还能直接执行查询语句看结果。排查QT代码为什么查不到数据时我通常先打开它看一眼写入的数据量对不对、字段类型是string还是float、时间戳单位是什么一眼就清楚。这个工具最有用的地方是能直接对比“同样一段查询在工具里执行的结果”和“QT程序返回的结果”。如果工具里有数据但QT查不到问题一定在QT的URL拼接、参数编码或者JSON解析上如果工具里也没数据那问题出在写入端。把排查范围缩小到一端效率能提升一倍。6.3 几个值得养成的端上习惯QT操作influxdb这类项目做到最后拼的不是复杂功能而是稳。我有几个固定习惯写在这里供你参考。第一个写入请求统一封装不要让业务代码里四处散落着拼URL的逻辑后面换influxdb版本时只需要改一个文件第二个日志必须记下“状态码响应体前200字节”不然线上出问题时你手里连个抓手都没有第三个凡是涉及时间戳的单位换算一律写注释标单位我见过太多人把毫秒写成纳秒、把秒当毫秒用这类问题排查成本最高。最后每次写完一批查询或写入代码先用influxdb studio把原始协议验证一遍再集成到QT工程里这个动作能帮你少加班。希望帮到你。本文还有配套的精品资源点击获取
返回列表