文章目录
etcdAPI的介绍与安装
etcd-cpp-apiv3 是一个 C++ 语言编写的 etcd 客户端库,用于与 etcd 分布式键值存储系统进行交互。下面这个表格汇总了它的核心信息:
特性 | 说明 |
---|---|
项目简介 | 基于 C++ 的 etcd v3 API 客户端库 |
核心功能 | 分布式键值存储、配置管理、服务发现、分布式锁 |
主要依赖 | Boost、gRPC、Protobuf、cpprestsdk |
官方仓库 | github下载地址 |
通信协议 | 通过 gRPC 与 etcd 服务器通信 (HTTP2 + protobuf) |
安装 etcd-cpp-apiv3
安装 etcd-cpp-apiv3
前需要解决其依赖。以下是在 Ubuntu 系统上的主要步骤:
-
安装系统依赖 :
打开终端,执行以下命令安装编译所需的工具和库:
bashsudo apt install -y ca-certificates ccache cmake libboost-all-dev libcurl4-openssl-dev libgrpc-dev libgrpc++-dev libprotobuf-dev libssl-dev libz-dev lsb-release protobuf-compiler-grpc screenfetch wget
这个命令安装了 Boost、Protobuf、gRPC 等必要的开发库。
-
编译安装 cpprestsdk :
etcd-cpp-apiv3
依赖cpprestsdk
。如果系统仓库的版本不满足要求,可以手动编译:bashgit clone https://github.com/microsoft/cpprestsdk.git cd cpprestsdk mkdir build && cd build cmake .. -DBUILD_TESTS=ON -DBUILD_SAMPLES=ON -DCPPREST_EXCLUDE_WEBSOCKETS=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache make -j sudo make install
这里克隆了
cpprestsdk
的源码,并通过 CMake 进行配置和编译安装。 -
编译安装 etcd-cpp-apiv3 :
最后,克隆
etcd-cpp-apiv3
的官方仓库并编译安装:bashgit clone https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git cd etcd-cpp-apiv3 mkdir build && cd build cmake .. -DBUILD_ETCD_TESTS=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache make -j sudo make install
这个过程会将库文件和头文件安装到系统路径。
使用示例与编译
安装完成后,你就可以在 C++ 项目中使用 etcd-cpp-apiv3
了。
-
示例代码
下面是一个简单的示例,演示如何设置和获取键值对:
cpp#include <etcd/Client.hpp> #include <etcd/Response.hpp> #include <iostream> int main() { // 初始化客户端,连接到本地 etcd 服务器 etcd::Client etcd("http://127.0.0.1:2379"); // 设置键值对 etcd::Response response = etcd.set("foo", "bar").get(); if (response.is_ok()) { std::cout << "Key set successfully" << std::endl; } else { std::cerr << "Error: " << response.error_message() << std::endl; } // 获取键值对 response = etcd.get("foo").get(); if (response.is_ok()) { std::cout << "Value: " << response.value().as_string() << std::endl; } else { std::cerr << "Error: " << response.error_message() << std::endl; } return 0; }
-
编译命令
假设你的源代码文件名为
main.cpp
,可以使用类似下面的命令来编译:g++ -std=c++11 main.cpp -o main -letcd-cpp-api -lprotobuf -lgrpc++ -lgrpc -lz -lcpprest -lssl -lcrypto -lboost_system -lpthread
然后运行程序:
./main
主要特性与应用场景
- 主要特性 :
etcd-cpp-apiv3
提供了简单易用的 C++ 接口,支持包括 KV 存储、租约(Lease)、观察者(Watcher)和分布式锁(Lock)在内的多种操作。 - 应用场景 :这个库常用于微服务架构 中的服务发现 和配置管理,可以实现服务实例的自动注册与发现,以及配置信息的集中管理和动态更新
etcd二次封装
etcdTool.hpp
cpp
#pragma once
#include <algorithm>
#include <cstdint>
#include <etcd/Client.hpp>
#include <etcd/Response.hpp>
#include <etcd/SyncClient.hpp>
#include <etcd/Value.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/KeepAlive.hpp>
#include <mutex>
#include <pplx/pplxtasks.h>
#include <string>
#include <functional>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include "Log.hpp"
namespace Etcd_Tool
{
typedef struct ResgisterInfo
{
//注册信息 服务名------服务地址------租约时间
std::string service_name_;
std::string service_addr_;
int ttl_ = 30;
}resinfo_t;
//根目录
//inline static std::string rootservice = "/services/";
//注册类
class RegisterEtcd
{
public:
RegisterEtcd(const std::string& serverinfo)
{
//初始化日志
LogModule::Log::Init();
//创建客户端连接服务器
try
{
etcd_client_ = std::make_shared<etcd::Client>(serverinfo);
LOG_TRACE("etcd Client link sucess!: {}", serverinfo);
}
catch (const std::exception& e)
{
LOG_DEBUG("create Client error: {}", e.what());
}
catch (...)
{
LOG_ERROR("Unknown Error!");
}
//LOG_INFO("create client sucess!");
}
//注册服务函数
bool Register(const resinfo_t& info)
{
if(keepalives_.find(info.service_name_) != keepalives_.end())
{
//检查是否已经注册过服务
LOG_DEBUG("Service already registered: {}", info.service_name_);
return false;
}
//加锁保护数据
std::lock_guard<std::mutex> lock(mutex_);
try
{
//创建租约保活
std::shared_ptr<etcd::KeepAlive> keepalive = etcd_client_->leasekeepalive(info.ttl_).get();
if(!keepalive)
{
LOG_DEBUG("keepalive is null! leasekeepalive fail!");
return false;
}
//获取租约id
int64_t leaseid = keepalive->Lease();
//注册服务信息,绑定租约
etcd::Response r = etcd_client_->set(info.service_name_, info.service_addr_, leaseid).get();
if(!r.is_ok())
{
LOG_DEBUG("set fail: {}" , r.error_message());
//注册失败,处理租约
keepalive.reset();
return false;
}
//注册服务保存进入类信息
keepalives_[info.service_name_] = keepalive;
services_.push_back(info);
LOG_INFO("Register sucess, service name is : {}", info.service_name_);
}
catch (const std::exception& e)
{
LOG_ERROR("Register fail: {}", e.what());
return false;
}
catch (...)
{
LOG_ERROR("Unknown Error!");
return false;
}
return true;
}
//注销服务函数------服务名
bool UnRegister(const std::string& service_name)
{
std::lock_guard<std::mutex> lock(mutex_);
//检查服务是否存在
auto it = keepalives_.find(service_name);
if(it == keepalives_.end())
{
LOG_INFO("service name invalid!");
return false;
}
try
{
//服务存在,取消租约
it->second.reset();
//删除服务器中的服务
auto res = etcd_client_->rm(it->first).get();
if(!res.is_ok())
{
LOG_DEBUG("Client rm error: {}", res.error_message());
return false;
}
//删除类中信息
keepalives_.erase(it);
services_.erase(std::remove_if(services_.begin(), services_.end()
, [&](const resinfo_t& it){
return it.service_name_ == service_name;
}), services_.end());
LOG_INFO("UnRegister sucess, service name: {}", service_name);
return true;
}
catch (const std::exception& e)
{
LOG_ERROR("UnRegister fail: {}", e.what());
return false;
}
catch (...)
{
LOG_ERROR("Unknown Error!");
return false;
}
return true;
}
bool UpdateRegister(const resinfo_t& info)
{
//检查服务是否存在
auto it = keepalives_.find(info.service_name_);
if(it == keepalives_.end())
{
LOG_DEBUG("Service not find: {}", info.service_name_);
return false;
}
std::lock_guard<std::mutex> lock(mutex_);
try
{
int64_t leaseid = it->second->Lease();
//不存在则创建,存在则更新键值对
auto res = etcd_client_->set(info.service_name_, info.service_addr_, leaseid).get();
if(!res.is_ok())
{
LOG_ERROR("Update Register Client set error: {}", res.error_message());
return false;
}
//更新本地缓存
for(auto& e : services_)
{
if(e.service_name_ == info.service_name_)
{
e = info;
break;
}
}
}
catch (const std::exception& e)
{
LOG_ERROR("UpdateRegister fail: {}", e.what());
return false;
}
catch (...)
{
LOG_ERROR("Unknown Error!");
return false;
}
return true;
}
std::vector<resinfo_t> Have_Services()
{
std::lock_guard<std::mutex> lock(mutex_);
return services_;
}
bool Clear()
{
//防止死锁
bool allsucess = true;
std::vector<std::string> service_names;
{
std::lock_guard<std::mutex> lock(mutex_);
for(auto& e : keepalives_)
{
service_names.push_back(e.first);
}
}
for(auto& e : service_names)
{
bool b = UnRegister(e);
if(!b)
{
LOG_ERROR("Clear fail!");
allsucess = false;
}
}
return allsucess;
}
~RegisterEtcd() = default;
private:
std::shared_ptr<etcd::Client> etcd_client_;
std::unordered_map<std::string, std::shared_ptr<etcd::KeepAlive>> keepalives_;
std::vector<resinfo_t> services_;
mutable std::mutex mutex_;
};
enum filetype
{
DIR,
FILE
};
//监视回调函数
using callbackfunc = std::function<void (const etcd::Response& res)>;
typedef struct Monitorinfo
{
filetype type_;
std::string monitor_path_;
callbackfunc cbfunc_;
} monitor_t;
class MonitorEtcd
{
public:
MonitorEtcd(const std::string& serverinfo)
{
try
{
LogModule::Log::Init();
etcd_Client_ = std::make_shared<etcd::Client>(serverinfo);
LOG_TRACE("etcd Client link sucess!: {}", serverinfo);
}
catch (const std::exception& e)
{
LOG_ERROR("std::make_shared<etcd::Client> fail, server: {}, exception: {}", serverinfo, e.what());
return;
}
catch (...)
{
LOG_ERROR("Unknown Error!, server: {}", serverinfo);
return;
}
}
bool PushMonitor(const monitor_t& info)
{
if (!etcd_Client_)
{
LOG_ERROR("etcd客户端未初始化");
return false;
}
try
{
std::lock_guard<std::mutex> lock(mtx_);
auto watchopt = info.type_ == DIR ? true : false;
std::unique_ptr<etcd::Watcher> w = std::make_unique<etcd::Watcher>(
*etcd_Client_.get(),
info.monitor_path_,
info.cbfunc_,
watchopt);
monitoring_set_[info.monitor_path_] =
std::make_pair(std::move(w), info);
LOG_INFO("PushMonitor sucess: {}", info.monitor_path_);
}
catch (const std::exception& e)
{
LOG_ERROR("PushMonitor fail, monitor path: {}, exception: {}", info.monitor_path_, e.what());
return false;
}
catch (...)
{
LOG_ERROR("Unknown Error!, monitor path: {}", info.monitor_path_);
return false;
}
return true;
}
bool CancelMonitor(const std::string& monitor_path)
{
if (!etcd_Client_)
{
LOG_ERROR("etcd客户端未初始化");
return false;
}
std::lock_guard<std::mutex> lock(mtx_);
auto it = monitoring_set_.find(monitor_path);
if (it == monitoring_set_.end())
{
LOG_DEBUG("monitor_path cannot find: {}", monitor_path);
return false;
}
try
{
bool b = it->second.first->Cancel();
if (!b)
{
LOG_DEBUG("Cancel monitor error: {}", monitor_path);
}
monitoring_set_.erase(it);
LOG_INFO("CancelMonitor sucess: {}", monitor_path);
}
catch (const std::exception& e)
{
LOG_ERROR("CancelMonitor fail, monitor path: {}, exception: {}", monitor_path, e.what());
return false;
}
catch (...)
{
LOG_ERROR("Unknown Error!, monitor path: {}", monitor_path);
return false;
}
return true;
}
~MonitorEtcd()
{
std::lock_guard<std::mutex> lock(mtx_);
for (auto& [path, pair] : monitoring_set_)
{
pair.first->Cancel(); // 取消所有监听器
}
monitoring_set_.clear();
}
private:
mutable std::mutex mtx_;
std::shared_ptr<etcd::Client> etcd_Client_;
std::unordered_map<std::string, std::pair<std::unique_ptr<etcd::Watcher>, monitor_t>> monitoring_set_;
};
}; // namespace etcd_service
Log.hpp
cpp
#pragma once
#include <atomic>
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/async_logger.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
namespace LogModule
{
enum OutputMode
{
CONSOLE_ONLY,
FILE_ONLY,
BOTH
};
typedef struct LogInfo
{
OutputMode outmode = CONSOLE_ONLY;
bool is_debug_ = true;
std::string logfile_ = "logfile.txt";
bool is_async_ = false;
size_t queue_size_ = 1 << 12;
size_t thread_num_ = 1;
} LogInfo_t;
class Log
{
public:
static void Init(const LogInfo_t& loginfo = LogInfo_t())
{
if(is_init_)
return;
logconf_ = loginfo;
create_logger();
is_init_ = true;
}
static Log& GetInstance()
{
static Log log;
return log;
}
template<typename... Args>
void trace(const char* fmt, const Args&... args)
{
if(logger_)
logger_->trace(fmt, args...);
}
template<typename... Args>
void info(const char* fmt, const Args&... args)
{
if(logger_)
logger_->info(fmt, args...);
}
template<typename... Args>
void debug(const char* fmt, const Args&... args)
{
if(logger_)
logger_->debug(fmt, args...);
}
template<typename... Args>
void warn(const char* fmt, const Args&... args)
{
if(logger_)
logger_->warn(fmt, args...);
}
template<typename... Args>
void error(const char* fmt, const Args&... args)
{
if(logger_)
logger_->error(fmt, args...);
}
template<typename... Args>
void critical(const char* fmt, const Args&... args)
{
if(logger_)
logger_->critical(fmt, args...);
}
static void shutdown()
{
spdlog::shutdown();
}
private:
Log() = default;
~Log() = default;
Log& operator=(const Log&) = delete;
Log(const Log&) = delete;
static void create_logger()
{
std::vector<spdlog::sink_ptr> sinks;
if(logconf_.outmode == CONSOLE_ONLY || logconf_.outmode == BOTH)
{
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
sinks.push_back(console_sink);
}
if(logconf_.outmode == FILE_ONLY || logconf_.outmode == BOTH)
{
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(logconf_.logfile_);
sinks.push_back(file_sink);
}
spdlog::level::level_enum lenum =
logconf_.is_debug_ ? spdlog::level::trace : spdlog::level::info;
if(logconf_.is_async_)
{
spdlog::init_thread_pool(logconf_.queue_size_, logconf_.thread_num_);
logger_ = std::make_shared<spdlog::async_logger>(
"mainlog", sinks.begin(), sinks.end(),
spdlog::thread_pool(),
spdlog::async_overflow_policy::block);
}
else
{
logger_ = std::make_shared<spdlog::logger>(
"mainlog", sinks.begin(), sinks.end());
}
logger_->set_level(lenum);
spdlog::set_default_logger(logger_); // 重要:设置默认日志器
logger_->set_pattern("[%Y-%m-%d %H:%M:%S.%e][%t][%-8l]%v");
}
private:
static inline std::shared_ptr<spdlog::logger> logger_;
static inline LogInfo_t logconf_;
static inline std::atomic<bool> is_init_ = false;
};
};
// // 使用简化版本的宏定义
// #define LOG_TRACE(...) LogModule::Log::GetInstance().trace(__VA_ARGS__)
// #define LOG_INFO(...) LogModule::Log::GetInstance().info(__VA_ARGS__)
// #define LOG_DEBUG(...) LogModule::Log::GetInstance().debug(__VA_ARGS__)
// #define LOG_WARN(...) LogModule::Log::GetInstance().warn(__VA_ARGS__)
// #define LOG_ERROR(...) LogModule::Log::GetInstance().error(__VA_ARGS__)
// #define LOG_CRITICAL(...) LogModule::Log::GetInstance().critical(__VA_ARGS__)
// 修改后宏定义:
#define LOG_TRACE(fmt, ...) LogModule::Log::GetInstance().trace("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) LogModule::Log::GetInstance().info("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) LogModule::Log::GetInstance().debug("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) LogModule::Log::GetInstance().warn("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) LogModule::Log::GetInstance().error("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_CRITICAL(fmt, ...) LogModule::Log::GetInstance().critical("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)