InfluxDB 数据库操作 C++(curl) 的详细实现

前几天写过一篇博文InfluxDB 写入&查询 Qt工程(C++ 客户端 API),主要介绍了整理的Qt工程的使用方法,今天介绍一下其具体实现(C++),这里主要借助于curl工具进行的数据库远程操作。

写入数据

写入数据的标准URL为[protocol]://[username:password@]host:port[/?db=database][/write/measure,tags field],所以第一步需要创建数据库,并且确定访问协议,其次当然是将用户名和密码设置好,并且赋予读写权限,如果权限不足则会导致数据库访问失败,程序异常退出。然后是host:port要申请好,当然本文中采用本地的influxdb,所以两者默认为localhost:8086,然后确定写入操作的关键字/write,之后加入操作的命令字符串/measure,tags field即可,这样的一个URL就可以直接操作数据库了,使用浏览器也可以实现操作。

当使用本工程中的API时,则可以直接使用http://admin:admin@localhost:8086/?db=mydb这样一个URL设置参数,并使用writeaddTagaddField三个函数配合实现写入操作。

具体的调用格式为:

auto influxdb = influxdb::InfluxDBFactory::Get("http://admin:admin@localhost:8086/?db=mydb");
influxdb->write(influxdb::Point{ "cpu" }
    .addTag("host", "localhost")
    .addTag("region", "china")
    .addField("north_latitude", 36)
    .addField("east_longitude", 114)
);

下面讲解一下写入操作中的每一步的详细实现过程的代码实现:

第一步是输入URL的解析,当然这是为了方便用户使用设计的,读者也可以通过参数直接设置上述的所有参数。 相信读者可以通过函数名了解每一个解析函数的功能,其中ParseHttpUrl是将URL

static inline int ExtractPort(std::string &hostport) {
    int port;
    std::string portstring = TailSlice(hostport, ":");
    try { port = atoi(portstring.c_str()); }
    catch (std::exception &e) { port = -1; }
    return port;
}

static inline std::string ExtractPath(std::string &in) { return TailSlice(in, "/", true); }
static inline std::string ExtractProtocol(std::string &in) { return HeadSlice(in, "://"); }
static inline std::string ExtractSearch(std::string &in) { return TailSlice(in, "?"); }
static inline std::string ExtractPassword(std::string &userpass) { return TailSlice(userpass, ":"); }
static inline std::string ExtractUserpass(std::string &in) { return HeadSlice(in, "@"); }


//--- Public Interface -------------------------------------------------------------~
static inline url ParseHttpUrl(std::string &in) {
    url ret;
    ret.urlString = in;
    ret.port = -1;
    ret.protocol = ExtractProtocol(in);
    ret.search = ExtractSearch(in);
    ret.path = ExtractPath(in);
    std::string userpass = ExtractUserpass(in);
    ret.password = ExtractPassword(userpass);
    ret.user = userpass;
    ret.port = ExtractPort(in);
    ret.host = in;

    return ret;
}

第二步是写入操作的命令字符串/measure,tags fields的获取。 在该工程中每一条操作(或信息)都是通过一个Point类实现的,在该类初始化时,measure作为参数输入,因为在Influxdb中写入时的字符串就是查询时获取的全部信息,所以这样一个类变可以用于写入和读取了,由于Tag和Field可以是多个,所以这里展示了Tag和Field的添加操作以及两者合并为操作字符串的操作,实际上就是字符串管理,主要是分隔符的合理使用。这里使用两个字符串对象:mTags 和 mFields来存储两个属性的字符串存储格式。具体操作如下

Point&& Point::addField(std::string_view name, std::variant<int, long long int, std::string, double> value)
{
  std::stringstream convert;
  if (!mFields.empty()) convert << ","; //Field间用逗号分隔

  convert << name.data() << "=";
  std::visit(overloaded {
    [&convert](int value) { convert << value << 'i'; },
    [&convert](long long int value) { convert << value << 'i'; },
    [&convert](double value) { convert << value; },
    [&convert](const std::string& value) { convert << '"' << value << '"'; },
    }, value);
  mFields += convert.str();
  return std::move(*this);
}

Point&& Point::addTag(std::string_view key, std::string_view value)
{
  mTags += ","; //Tag间用逗号分隔
  mTags += key;
  mTags += "=";
  mTags += value;
  return std::move(*this);
}
//合并为操作字符串 measure,tags fields
std::string Point::toLineProtocol() const
{
    return mMeasurement + mTags + " " + mFields + " " + std::to_string(
    std::chrono::duration_cast <std::chrono::nanoseconds>(mTimestamp.time_since_epoch()).count()
  );     //Fields和Tags间用空格分隔
}

第三部就是post操作了 ,通过toLineProtocol函数获取的字符串将传入下面的函数进行Post操作,这里借助的是libcurl库。

void HTTP::send(std::string&& post)
{
  CURLcode response;
  long responseCode;
#ifdef DEBUG_OUTPUT
  std::cout << "post string : " << post << std::endl;
#endif
  curl_easy_setopt(writeHandle, CURLOPT_POSTFIELDS, post.c_str());
  curl_easy_setopt(writeHandle, CURLOPT_POSTFIELDSIZE, (long) post.length());
  response = curl_easy_perform(writeHandle);
  curl_easy_getinfo(writeHandle, CURLINFO_RESPONSE_CODE, &responseCode);
  if (response != CURLE_OK) {
    throw std::runtime_error(curl_easy_strerror(response));
  }
  if (responseCode < 200 || responseCode > 206) {
    throw std::runtime_error("Response code : " + std::to_string(responseCode));
  }
}

读取数据

第一步是设置读取的操作命令,该程序未对读取进一步的封装,所以读取字符串例如SELECT * FROM cpu将直接传送至InfluxDB::query函数中便可以获取操作命令的相应报文。所以查询操作调用格式如下:

auto points = influxdb->query("SELECT * FROM cpu");
std::cout << "query over!" << std::endl;
std::cout << "name : " << points.back().getName() <<std::endl
            << "field : "<<points.back().getFields() << std::endl
            << "timestamp : " << points.back().getTimestamp().time_since_epoch().count()  << std::endl;

这里的points是以std::vector的类对象,可以直接使用迭代器对每一条数据进行读取和操作。

而在InfluxDB::query函数中第一步也就是读取操作的第二步是通过curl获取反馈的报文字符串

这里详细展示一下curl获取反馈的报文字符串的函数实现,仍然是调用curl API,设置参数后访问URL。

std::string HTTP::query(const std::string& query)
{
  CURLcode response;
  std::string buffer;
#ifdef DEBUG_OUTPUT
  std::cout << "start http query!" <<std::endl;
#endif
  char* encodedQuery = curl_easy_escape(readHandle, query.c_str(), query.size());
  auto fullUrl = mReadUrl + std::string(encodedQuery);  
#ifdef DEBUG_OUTPUT
  std::cout << "full url : " << fullUrl<< std::endl;
#endif
  curl_easy_setopt(readHandle, CURLOPT_URL, fullUrl.c_str());
  curl_easy_setopt(readHandle, CURLOPT_WRITEDATA, &buffer);
#ifdef DEBUG_OUTPUT
  std::cout << "get buffer : " << buffer <<std::endl;
#endif
  response = curl_easy_perform(readHandle);
  if (response != CURLE_OK) {
    throw std::runtime_error(curl_easy_strerror(response));
  }
  return buffer;
}

第三步是进行报文的解析,所获取的信息流主要在result中的以series呈现,series包含了columns(主要有measurement Tags标签 Fields标签 time)以及values(时间序列数据)。注意这里的TIME_TYPE是为了获取时间设置的,当然时间的形式各有不同,自己的可以根据浏览器获取的报文格式来设置,本地默认的时间报文格式为"%Y-%m-%dT%H:%M:%S.%fZ"

// TIME_TYPE is used to get time stamp
#define TIME_TYPE "%Y-%m-%dT%H:%M:%S.%fZ"

std::vector<Point> InfluxDB::query(const std::string&  query)
{
  auto response = mTransport->query(query);//获取反馈的字符串。
  std::stringstream ss;
  ss << response;
  std::vector<Point> points;
  boost::property_tree::ptree pt;
  boost::property_tree::read_json(ss, pt);
#ifdef DEBUG_OUTPUT
  std::cout << "start query process !" <<std::endl;
#endif
  for (auto& result : pt.get_child("results")) {
    auto isResultEmpty = result.second.find("series");
    if (isResultEmpty == result.second.not_found()) return {};
    for (auto& series : result.second.get_child("series")) {
      auto columns = series.second.get_child("columns");

      for (auto& values : series.second.get_child("values")) {
        Point point{series.second.get<std::string>("name")};
        auto iColumns = columns.begin();
        auto iValues = values.second.begin();
        for (; iColumns != columns.end() && iValues != values.second.end(); iColumns++, iValues++) {
          auto value = iValues->second.get_value<std::string>();
          auto column = iColumns->second.get_value<std::string>();
          if (!column.compare("time")) {
            std::tm tm = {};
            std::stringstream ss;
            ss << value;
            ss >> std::get_time(&tm, TIME_TYPE);
            point.setTimestamp(std::chrono::system_clock::from_time_t(std::mktime(&tm)));
            continue;
          }
          // By observing whether it is numerical data, we can judge whether it is field or tag
          try { point.addField(column, boost::lexical_cast<double>(value)); }
          catch(...) { point.addTag(column, value); }
        }
        points.push_back(std::move(point));
      }
    }
  }
  return points;
}

总结

本文主要讲解的是influxdb查询写入的c++实现,主要还是使用数据库本身的命令进行数据访问所以这里进行influxdb数据库操作的命令供大家参考。

-- 查看所有的数据库
show databases;
-- 使用特定的数据库
use database_name;
-- 查看所有的measurement
show measurements;
-- 查询10条数据
select * from measurement_name limit 10;
-- 查看一个measurement中所有的tag key 
show tag keys
-- 查看一个measurement中所有的field key 
show field keys

当然我感觉也可以用到其他数据库读取中,不是本职工作就没有尝试,如果读者有需求可以自己实现尝试一下,如果有疑问的话可以留言提问,虽然我也是萌新。

你可能感兴趣的:(InfluxDB,数据库,c++,influxdb)