跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

用 etcd-cpp-apiv3 搭建 C++ 服务注册发现

基于 etcd 构建服务注册发现中心,利用 C++ 客户端库 etcd-cpp-apiv3 实现服务注册、发现、健康检查与变更监控。涵盖 etcd 单节点安装与配置、库编译依赖,介绍 Client/KeepAlive/Watcher 等核心 API,并给出注册与发现示例代码,演示租约自动续期和目录监控。最后讨论实际落地时的扩展思路。

奇形怪状发布于 2026/6/28更新于 2026/8/2324 浏览
用 etcd-cpp-apiv3 搭建 C++ 服务注册发现

etcd 是一个用 Go 编写的分布式、高可用的键值存储,底层基于 Raft 达成共识。它提供长连接 watch 机制,客户端可以实时感知数据变化;用来做服务注册发现再合适不过,比 ZooKeeper 更轻便。

安装 etcd

在 Ubuntu 上直接 apt 安装:

sudo apt install etcd
sudo systemctl start etcd
sudo systemctl enable etcd

单节点情况下默认配置就够用——节点间通信端口 2380,客户端访问端口 2379。如果要自定义节点名、数据目录或监听地址,编辑 /etc/default/etcd,参考下面的配置:

ETCD_NAME="etcd1"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_CLIENT_URLS="http://192.168.65.132:2379,http://127.0.0.1:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.65.132:2379,http://127.0.0.1:2379"
ETCD_LISTEN_PEER_URLS="http://192.168.65.132:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.65.132:2380"
ETCD_HEARTBEAT_INTERVAL=100
ETCD_ELECTION_TIMEOUT=1000
# 集群配置,单节点可忽略
#ETCD_INITIAL_CLUSTER="etcd1=http://192.168.65.132:2380,etcd2=http://192.168.65.132:2381,etcd3=http://192.168.65.132:2382"
#ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
#ETCD_INITIAL_CLUSTER_STATE="new"
# 安全相关(SSL),按需取消注释并修改路径
#ETCD_CERT_FILE="/etc/ssl/client.pem"
#ETCD_KEY_FILE="/etc/ssl/client-key.pem"
#ETCD_CLIENT_CERT_AUTH="true"
#ETCD_TRUSTED_CA_FILE="/etc/ssl/ca.pem"
#ETCD_AUTO_TLS="true"
#ETCD_PEER_CERT_FILE="/etc/ssl/member.pem"
#ETCD_PEER_KEY_FILE="/etc/ssl/member-key.pem"
#ETCD_PEER_CLIENT_CERT_AUTH="false"
#ETCD_PEER_TRUSTED_CA_FILE="/etc/ssl/ca.pem"
#ETCD_PEER_AUTO_TLS="true"

修改配置后重新加载,然后验证一下:

source /etc/profile
export ETCDCTL_API=3   # 如果在 /etc/profile 里加过这行可以省略
etcdctl put key "SnowK"
etcdctl get key

如果 put 报错 No help topic for 'put',就是因为没声明 ETCDCTL_API=3,加上就好了。

服务注册发现的设计

用 etcd 做注册中心,基本流程就是:

  1. 服务启动时将自己的 IP:Port 写入 etcd(注册)。
  2. 客户端从 etcd 拉取服务地址列表(发现)。
  3. 已注册的服务定期续租(健康检查),一旦停止续租,etcd 会清理掉对应键,调用方通过 watch 感知到节点下线。

官方只维护了 Go 的客户端库,C++ 这边社区方案的 etcd-cpp-apiv3 算是比较完整的。

etcd-cpp-apiv3

etcd-cpp-apiv3 是一个 C++ 的 etcd 客户端,基于 gRPC,依赖 boost、protobuf、cpprestsdk 等。安装步骤:

# 依赖
sudo apt-get install libboost-all-dev libssl-dev
sudo apt-get install libprotobuf-dev protobuf-compiler-grpc
sudo apt-get install libgrpc-dev libgrpc++-dev
sudo apt-get install libcpprest-dev

# 编译安装
git clone https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git
cd etcd-cpp-apiv3
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr
make -j$(nproc)
sudo make install

核心 API

库的几个核心类:

  • Client:连接 etcd 的主入口,支持 put、ls、leasegrant、leasekeepalive、lock 等操作,返回 pplx::task<Response>,可以 .get() 阻塞等待,也可以用 .wait() 或 .then() 异步处理。
  • KeepAlive:通过 Client::leasekeepalive 创建,内部自动定时续租。对象析构时停止续租,对应租约到期后键值会被删除。
  • Response:操作响应,可以检查 is_ok()、获取错误信息、单个或批量 value。
  • Value:表示一个键值对,key() 和 as_string() 获取内容。
  • Watcher:监控指定 key 的变化。提供回调,支持递归监控目录下的所有子节点。
  • Event:在回调中给出变更事件,分为 PUT 和 DELETE_ 两种,同时提供当前值和前值。

类声明大致如下:

namespace etcd {
class Value {
public:
    bool is_dir();
    std::string const& key();
    std::string const& as_string();
    int64_t lease();
};

class Event {
public:
    enum class EventType {
        PUT,
        DELETE_,
        INVALID,
    };
    enum EventType event_type() const;
    const Value& kv() const;
    const Value& prev_kv();
};

class Response {
public:
    bool is_ok();
    std::string const& error_message();
    const Value& value();
    const Value& prev_value();
    const Value& value(int index);
    const std::vector<Event>& events();
};

class KeepAlive {
public:
    KeepAlive(Client const& client, int ttl, int64_t lease_id = 0);
    int64_t Lease();
    void Cancel();
};

class Client {
public:
    Client(std::string const& etcd_url, std::string const& load_balancer = "round_robin");
    pplx::task<Response> put(std::string const& key, std::string const& value);
    pplx::task<Response> put(std::string const& key, std::string const& value, const int64_t leaseId);
    pplx::task<Response> ls(std::string const& key);
    pplx::task<Response> leasegrant(int ttl);
    pplx::task<std::shared_ptr<KeepAlive>> leasekeepalive(int ttl);
    pplx::task<Response> leaserevoke(int64_t lease_id);
    pplx::task<Response> lock(std::string const& key);
};

class Watcher {
public:
    Watcher(Client const& client, std::string const& key,
            std::function<void(Response)> callback,
            bool recursive = false);
    Watcher(std::string const& address, std::string const& key,
            std::function<void(Response)> callback, bool recursive = false);
    bool Wait();
    bool Cancel();
};
}

实际跑一下

下面是一个简单的例子:服务端注册两个服务 user 和 friend,user 绑定了 3 秒 TTL 的租约,10 秒后程序退出,租约过期会被清理;客户端通过 ls 发现服务,再用 Watcher 监控 /service 目录的变化。

运行结果预期:

127.0.0.1:6633 可以提供/service/friend 服务
127.0.0.1:3366 可以提供/service/user 服务
服务信息下线被删除:当前的值:/service/user-
原来的值:/service/user-127.0.0.1:3366

注册端 put.cc:

#include<iostream>
#include<thread>
#include<etcd/Client.hpp>
#include<etcd/KeepAlive.hpp>
#include<etcd/Response.hpp>

int main(int argc, char* argv[]) {
    std::string etcd_host = "http://127.0.0.1:2379";
    etcd::Client client(etcd_host);

    // 创建一个 3 秒 TTL 的租约,keep_alive 会自动续租
    auto keep_alive = client.leasekeepalive(3).get();
    auto lease_id = keep_alive->Lease();

    // 注册 user 服务,绑定租约
    auto resp1 = client.put("/service/user", "127.0.0.1:3366", lease_id).get();
    if (!resp1.is_ok()) {
        std::cout << "新增数据失败:" << resp1.error_message() << std::endl;
        return -1;
    }

    // 注册 friend 服务,不带租约(永久有效)
    auto resp2 = client.put("/service/friend", "127.0.0.1:6633").get();
    if (!resp2.is_ok()) {
        std::cout << "新增数据失败:" << resp2.error_message() << std::endl;
        return -1;
    }

    // 保持 10 秒,然后退出时会释放租约
    std::this_thread::sleep_for(std::chrono::seconds(10));
    return 0;
}

发现端 get.cc:

#include<iostream>
#include<thread>
#include<etcd/Client.hpp>
#include<etcd/KeepAlive.hpp>
#include<etcd/Response.hpp>
#include<etcd/Watcher.hpp>
#include<etcd/Value.hpp>

void CallBack(const etcd::Response& resp) {
    if (!resp.is_ok()) {
        std::cout << "收到一个错误的事件通知:" << resp.error_message() << std::endl;
        return;
    }
    for (const auto& ev : resp.events()) {
        if (ev.event_type() == etcd::Event::EventType::PUT) {
            std::cout << "服务信息发生了改变:" << std::endl;
            std::cout << "当前的值:" << ev.kv().key() << "-" << ev.kv().as_string() << std::endl;
            std::cout << "原来的值:" << ev.prev_kv().key() << "-" << ev.prev_kv().as_string() << std::endl;
        } else if (ev.event_type() == etcd::Event::EventType::DELETE_) {
            std::cout << "服务信息下线被删除:\n";
            std::cout << "当前的值:" << ev.kv().key() << "-" << ev.kv().as_string() << std::endl;
            std::cout << "原来的值:" << ev.prev_kv().key() << "-" << ev.prev_kv().as_string() << std::endl;
        }
    }
}

int main(int argc, char* argv[]) {
    std::string etcd_host = "http://127.0.0.1:2379";
    etcd::Client client(etcd_host);

    // 发现服务
    auto resp = client.ls("/service").get();
    if (!resp.is_ok()) {
        std::cout << "获取键值对数据失败:" << resp.error_message() << std::endl;
        return -1;
    }
    int sz = resp.keys().size();
    for (int i = 0; i < sz; i++) {
        std::cout << resp.value(i).as_string() << " 可以提供" << resp.key(i) << "服务" << std::endl;
    }

    // 监控 /service 目录的变化(递归)
    auto watcher = etcd::Watcher(client, "/service", CallBack, true);
    watcher.Wait();
    return 0;
}

Makefile:

all: get put
get: get.cc
	g++ -o $@ $^ -std=c++17 -letcd-cpp-api -lcpprest
put: put.cc
	g++ -o $@ $^ -std=c++17 -letcd-cpp-api -lcpprest
.PHONY:clean
clean:
	rm get put

这样就搭好了一个最基本的服务注册发现框架。实际用的时候,可以把 keep_alive 放到一个后台线程里保持,服务发现逻辑结合负载均衡策略,Watcher 实时更新可用列表。

目录

  1. 安装 etcd
  2. 集群配置,单节点可忽略
  3. 安全相关(SSL),按需取消注释并修改路径
  4. 服务注册发现的设计
  5. etcd-cpp-apiv3
  6. 依赖
  7. 编译安装
  8. 核心 API
  9. 实际跑一下
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 好的研究想法从哪里来?清华大学刘知远分享
  • AI Agent 推理架构对比:ReAct 与 Plan-and-Execute 的选择
  • n8n Docker Compose 部署指南:官方与中文镜像配置详解
  • NVIDIA RTX PC 开源 AI 工具升级:加速 LLM 与扩散模型性能
  • 前端实现 Word 文档在线编辑与导出:基于 mammoth.js 与 Blob 对象
  • Llama 3.1 中文微调版本地部署指南:Ollama 与 HuggingFace 集成
  • Python 条形码识别库 pyzbar 快速上手指南
  • 基于 ECharts 与 Three.js 的碳排放可视化大屏实现
  • Ubuntu 下编译自定义 ImmortalWrt 固件与软件包
  • CCF-GESP 2025 年 9 月二级 C++ 优美数字题解
  • Git 新建分支首次推送到远程仓库的操作指南
  • 利用文心一言为智能体设计稳定的工作流提示词
  • 6 个月免费的 ChatGPT Pro:开源维护者该怎么申请
  • QGroundControl 跨平台安装与配置指南
  • C# Web API 基础教程:从 Minimal APIs 到 EF Core
  • 零基础自学网络安全技术指南
  • WEBCPM 技术解析:交互式中文长文本问答框架
  • 国外主流 AI 大模型在国内的接入与使用指南
  • OpenClaw Mac 环境部署与使用指南
  • Linux 一切皆文件:深入理解文件与文件 IO

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online