C++ 连接 etcd
对于 C++ 开发者,官方推荐的客户端库是 etcd-cpp-apiv3,它在 etcd-cpp-api 基础上重写,全面支持了 v3 的 gRPC API[reference:0][reference:1]。
C 语言没有官方支持的客户端库,通常需要使用 C++ 库进行封装或直接调用 gRPC 接口。
下面的教程会以 etcd-cpp-apiv3 库为例,介绍环境搭建、核心操作以及常见的分布式协调功能实现。
📦 一、环境搭建
首先,需要安装 etcd-cpp-apiv3 客户端库。
1. 依赖安装 (以 Ubuntu 为例)
# 1. 安装基础依赖
sudo apt-get install libboost-all-dev libssl-dev
# 2. 安装 gRPC 和 Protobuf
sudo apt-get install libprotobuf-dev protobuf-compiler-grpc libgrpc-dev libgrpc++-dev
# 3. 安装 cpprestsdk (用于异步处理)
sudo apt-get install libcpprest-dev[reference:2]
2. 编译并安装 etcd-cpp-apiv3
# 1. 克隆项目仓库
git clone https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git
cd etcd-cpp-apiv3
# 2. 创建并进入构建目录
mkdir build && cd build
# 3. 使用 CMake 进行配置和构建
cmake .. -DCMAKE_INSTALL_PREFIX=/usr
make -j$(nproc)
# 4. 安装到系统中
sudo make install[reference:3]
编译时链接库:g++ -std=c++11 main.cpp -letcd-cpp-apiv3 -o main[reference:4]。
🚀 二、快速入门:同步操作
下面的代码演示了最基础的同步 CRUD 操作。
#include <etcd/Client.hpp>
#include <etcd/Response.hpp>
#include <iostream>
int main() {
// 1. 初始化客户端,连接到本地 etcd 服务
etcd::Client client("http://127.0.0.1:2379");
// 2. 写入键值对 (PUT)
etcd::Response put_resp = client.set("/test/key", "hello from cpp").get();
if (put_resp.is_ok()) {
std::cout << "成功写入键值对" << std::endl;
} else {
std::cerr << "写入失败: " << put_resp.error_message() << std::endl;
}
// 3. 读取键值对 (GET)
etcd::Response get_resp = client.get("/test/key").get();
if (get_resp.is_ok()) {
// 使用 .value().as_string() 获取键对应的值
std::cout << "读取到的值: " << get_resp.value().as_string() << std::endl;
} else {
std::cerr << "读取失败: " << get_resp.error_message() << std::endl;
}
return 0;
}[reference:5]
🛠️ 三、核心 API 详解
etcd-cpp-apiv3 的所有操作几乎都返回 pplx::task<Response>,这是一个异步任务对象[reference:6]。你可以使用 .get() 方法阻塞等待结果,实现同步调用,也可以使用 .then() 方法设置回调函数来异步处理。
1. CRUD 操作
| 操作类型 | 函数签名与示例 |
|---|---|
| 写入/修改 | client.set(key, value, ttl=0)示例: client.set("/mykey", "myvalue")[reference:7] |
| 读取 | client.get(key)示例: client.get("/mykey")[reference:8] |
| 前缀读取 | client.ls(key_prefix)示例: client.ls("/services/") 获取该目录下所有子键[reference:9] |
| 删除 | client.rm(key)示例: client.rm("/mykey") |
注意:删除操作与 v2 API 略有不同,v3 API 使用
rm方法。
2. 异步与同步处理
异步非阻塞模式:
// 发起异步读取,设置回调函数
client.get("/some/key").then([](etcd::Response resp) {
if (resp.is_ok()) {
std::cout << "异步获取的值: " << resp.value().as_string() << std::endl;
}
});
// 主线程可以继续执行其他任务,不会阻塞[reference:10]
⏳ 四、高级特性:租约、监听与分布式锁
1. 租约 (Lease) - 自动过期
通过 lease() 方法创建一个带有存活时间 (TTL) 的租约,并用于写入操作。
#include <etcd/Client.hpp>
#include <etcd/Lease.hpp>
#include <thread>
int main() {
etcd::Client client("http://127.0.0.1:2379");
// 创建一个 TTL 为 5 秒的租约
etcd::Lease lease = client.lease(5).get();
// 将键绑定到该租约上
client.set("/temp/data", "this will expire", lease.lease_id()).get();
std::cout << "键已写入,将在5秒后过期" << std::endl;
// 模拟等待6秒,让租约过期
std::this_thread::sleep_for(std::chrono::seconds(6));
// 尝试读取,应该失败
auto resp = client.get("/temp/data").get();
if (!resp.is_ok()) {
std::cout << "键已自动过期删除" << std::endl;
}
return 0;
}[reference:11][reference:12]
2. 监听器 (Watcher) - 实时感知变化
Watcher 类用于监听一个或多个键的变化,并在回调函数中处理这些事件。
#include <etcd/Client.hpp>
#include <etcd/Watcher.hpp>
#include <iostream>
int main() {
etcd::Client client("http://127.0.0.1:2379");
// 定义一个回调函数,用于处理收到的事件
auto callback = [](etcd::Response resp) {
if (resp.is_ok()) {
for (const auto& event : resp.events()) {
std::cout << "事件类型: " << event.event_type()
<< " 键: " << event.kv().key()
<< " 值: " << event.kv().as_string() << std::endl;
}
}
};
// 创建一个 Watcher 对象,监听 "/watch/key" 这个键
etcd::Watcher watcher(client, "/watch/key", callback);
// 保持程序运行,以持续接收事件
std::cout << "正在监听,请在其他终端执行修改操作..." << std::endl;
std::this_thread::sleep_for(std::chrono::minutes(1));
return 0;
}[reference:13]
3. 分布式锁 (Lock) - 互斥与协调
Lock 类提供了一种简单的方式来实现分布式互斥锁,确保同一时间只有一个客户端持有锁。
#include <etcd/Client.hpp>
#include <etcd/Lock.hpp>
int main() {
etcd::Client client("http://127.0.0.1:2379");
// 1. 创建一个锁对象,并指定要锁定的资源名称
etcd::Lock lock(client, "/resource/lock_name");
// 2. 尝试获取锁
if (lock.lock().get().is_ok()) {
std::cout << "成功获取分布式锁" << std::endl;
// 执行需要互斥访问的代码...
// 3. 释放锁
lock.unlock().get();
std::cout << "锁已释放" << std::endl;
} else {
std::cout << "获取锁失败" << std::endl;
}
return 0;
}[reference:14]
📝 五、综合示例:服务注册与发现
这是一个结合了租约和监听器功能的完整服务注册与发现示例,实际应用中可以参考该示例进行封装[reference:15]。
#include <etcd/Client.hpp>
#include <etcd/Lease.hpp>
#include <etcd/Watcher.hpp>
#include <iostream>
#include <thread>
class ServiceRegistry {
private:
etcd::Client client_;
int64_t lease_id_;
std::string key_;
std::unique_ptr<etcd::Watcher> watcher_;
public:
ServiceRegistry(const std::string& etcd_url, const std::string& key)
: client_(etcd_url), key_(key) {}
// 注册服务,租约自动续期
void register_service(int ttl_seconds) {
// 1. 创建租约
auto lease_resp = client_.lease(ttl_seconds).get();
lease_id_ = lease_resp.value().lease_id();
// 2. 写入服务地址并绑定租约
client_.set(key_, "http://127.0.0.1:8080", lease_id_).get();
// 3. 启动一个线程,定期刷新租约
std::thread([this, ttl_seconds]() {
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(ttl_seconds / 2));
client_.refresh_lease(lease_id_).get();
}
}).detach();
}
// 监听服务变更
void watch_service(const std::string& prefix) {
watcher_ = std::make_unique<etcd::Watcher>(
client_, prefix, [](etcd::Response resp) {
for (const auto& ev : resp.events()) {
std::cout << "服务列表变更: " << ev.kv().key() << std::endl;
}
}, true);
}
};
int main() {
ServiceRegistry registry("http://127.0.0.1:2379", "/services/api");
registry.register_service(5);
registry.watch_service("/services/");
std::cout << "服务注册成功,正在运行..." << std::endl;
std::this_thread::sleep_for(std::chrono::minutes(1));
return 0;
}⚠️ 六、关键注意事项
- 线程安全:该库是线程安全的,可以在多个线程中共享
etcd::Client对象。 - 错误处理:务必检查
Response.is_ok(),处理网络错误或 etcd 返回的错误[reference:16]。 - 连接池:
etcd::Client内部维护了连接池,通常无需自行管理[reference:17]。 - API 兼容性:确保
etcd-cpp-apiv3库的版本与 etcd 服务器版本兼容,推荐使用较新版本。 - 配置连接:客户端构造时支持指定多个集群地址(逗号分隔)并设置负载均衡策略,如
round_robin(默认)、pick_first等[reference:18]。
通过以上介绍,你应该能在 C++ 项目中快速上手使用 etcd 了。etcd-cpp-apiv3 库的 examples/ 目录下还有更多完整示例可供参考[reference:19]。如果需要更深入的实践,建议查阅其官方文档。