【C++脚手架】etcd 的介绍与使用

Etcd-SDK 使用指南:C++ 服务注册与发现

摘要:本文详细介绍了如何在 C++ 项目中基于 Etcd 实现服务注册与发现。内容涵盖 Etcd 的安装配置、etcd-cpp-apiv3 客户端 SDK 的核心 API 使用、租约保活机制、数据监控机制,并提供了完整的服务注册与发现封装方案。通过 SvcProvider 和 SvcWatcher 两个核心组件,实现了服务自动注册、健康检查和服务动态发现功能,为微服务架构提供了可靠的服务治理基础。

C++脚手架仓库地址: https://gitee.com/chen-weifeng-cwf/developing-scaffolding-for-c

📋 目录

  • [1. Etcd 介绍](#1. Etcd 介绍)
  • [2. 安装 Etcd](#2. 安装 Etcd)
    • [2.1 基本安装](#2.1 基本安装)
    • [2.2 节点配置](#2.2 节点配置)
    • [2.3 运行验证](#2.3 运行验证)
  • [3. 客户端 SDK](#3. 客户端 SDK)
    • [3.1 依赖安装](#3.1 依赖安装)
    • [3.2 API 框架安装](#3.2 API 框架安装)
    • [3.3 头文件](#3.3 头文件)
  • [4. 核心数据结构与 API](#4. 核心数据结构与 API)
    • [4.1 Value --- 键值对数据](#4.1 Value — 键值对数据)
    • [4.2 Event --- 数据变化事件](#4.2 Event — 数据变化事件)
    • [4.3 Response --- 请求响应](#4.3 Response — 请求响应)
    • [4.4 Client --- Etcd 客户端](#4.4 Client — Etcd 客户端)
    • [4.5 KeepAlive --- 租约保活](#4.5 KeepAlive — 租约保活)
    • [4.6 Watcher --- 数据监控](#4.6 Watcher — 数据监控)
  • [5. 入门示例](#5. 入门示例)
    • [5.1 目录结构](#5.1 目录结构)
    • [5.2 put.cc --- 写入数据(带租约保活)](#5.2 put.cc — 写入数据(带租约保活))
    • [5.3 get.cc --- 获取数据并监控变化](#5.3 get.cc — 获取数据并监控变化)
    • [5.4 Makefile](#5.4 Makefile)
    • [5.5 运行演示](#5.5 运行演示)
  • [6. 服务管理封装](#6. 服务管理封装)
    • [6.1 设计思路](#6.1 设计思路)
    • [6.2 服务注册客户端 --- SvcProvider](#6.2 服务注册客户端 — SvcProvider)
    • [6.3 服务发现客户端 --- SvcWatcher](#6.3 服务发现客户端 — SvcWatcher)
  • [7. 完整使用示例](#7. 完整使用示例)
    • [7.1 目录结构](#7.1 目录结构)
    • [7.2 registry.cc --- 服务注册](#7.2 registry.cc — 服务注册)
    • [7.3 discovery.cc --- 服务发现](#7.3 discovery.cc — 服务发现)
    • [7.4 CMakeLists.txt](#7.4 CMakeLists.txt)
    • [7.5 运行演示](#7.5 运行演示)
  • [8. 总结](#8. 总结)

Etcd 是一个分布式、高可用的键值存储系统,常用于服务发现和配置管理。本文详细介绍 Etcd 的安装配置,以及如何在 C++ 项目中通过 etcd-cpp-apiv3 客户端 SDK 实现服务注册与发现。


1. Etcd 介绍

Etcd 是一个用 Go 语言编写的分布式、高可用的一致性键值存储系统,用于配置共享和服务发现等场景。它使用 Raft 一致性算法 来保持集群数据的一致性,且客户端通过长连接 watch 功能能够及时收到数据变化通知,相较于 Zookeeper 框架更加轻量化。


2. 安装 Etcd

2.1 基本安装

bash 复制代码
# 安装 Etcd
sudo apt-get install etcd

# 启动 Etcd 服务
sudo systemctl start etcd

# 设置开机自启
sudo systemctl enable etcd

2.2 节点配置

如果是单节点集群,可以不做配置。Etcd 默认集群节点通信端口为 2380 ,客户端访问端口为 2379

如需修改,编辑 /etc/default/etcd

ini 复制代码
# 节点名称,默认为 "default"
ETCD_NAME="etcd1"

# 数据目录
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"

# 客户端连接的 URL
ETCD_LISTEN_CLIENT_URLS="http://192.168.65.132:2379,http://127.0.0.1:2379"

# 客户端访问的公开 URL
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.65.132:2379,http://127.0.0.1:2379"

# 集群节点间通信的 URL
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"

单节点运行示例:

bash 复制代码
etcd --name etcd1 --initial-advertise-peer-urls http://192.168.65.132:2380 \
   --listen-peer-urls http://192.168.65.132:2380 \
   --listen-client-urls http://192.168.65.132:2379 \
   --advertise-client-urls http://192.168.65.132:2379 \
   --initial-cluster-token etcd-cluster \
   --initial-cluster etcd1=http://192.168.65.132:2380,etcd2=http://192.168.65.132:2381,etcd3=http://192.168.65.132:2382 \
   --initial-cluster-state new &> nohup1.out &

2.3 运行验证

使用 etcdctl 命令行工具测试:

bash 复制代码
$ etcdctl put mykey "this is awesome"
OK

$ etcdctl get mykey
mykey
this is awesome

$ etcdctl del mykey

常见问题 :如果出现 No help topic for 'put' 报错,需声明 API 版本:

bash 复制代码
sudo vi /etc/profile
# 在末尾添加
export ETCDCTL_API=3

source /etc/profile

3. 客户端 SDK

Etcd v3 版本通信采用 gRPC API (HTTP/2 + protobuf),官方只维护了 Go 语言版本的 client 库。对于 C/C++ 项目,我们使用非官方的客户端库:etcd-cpp-apiv3

3.1 依赖安装

bash 复制代码
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

3.2 API 框架安装

bash 复制代码
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

3.3 头文件

cpp 复制代码
#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/Response.hpp>
#include <etcd/Value.hpp>

4. 核心数据结构与 API

4.1 Value --- 键值对数据

cpp 复制代码
namespace etcd {
class Value {
    bool is_dir();                    // 判断是否是一个目录
    std::string const& key();         // 键值对的 key 值
    std::string const& as_string();   // 键值对的 value 值
    int64_t lease();                  // 租约 ID(用于创建租约的响应中)
};
}

4.2 Event --- 数据变化事件

当 Etcd 监控的数据发生变化时,会通知客户端,同时返回改变前和改变后的数据。

cpp 复制代码
namespace etcd {
class Event {
    enum class EventType {
        PUT,       // 键值对新增或数据发生改变
        DELETE_,   // 键值对被删除
        INVALID,
    };

    enum EventType event_type();     // 事件类型
    const Value& kv();               // 当前键值对的数据
    const Value& prev_kv();          // 改变前键值对的数据
};
}

4.3 Response --- 请求响应

cpp 复制代码
namespace etcd {
class Response {
    bool is_ok();                              // 请求是否成功
    std::string const& error_message();        // 错误信息
    Value const& value();                      // 当前数值或请求处理结果
    Value const& prev_value();                 // 之前的数值
    Value const& value(int index);             // 按索引获取数值
    std::vector<Event> const& events();        // 触发的事件列表

    using Values = std::vector<Value>;
    Values const& values() const;              // 多组数据的响应结果(针对目录)
};
}

4.4 Client --- Etcd 客户端

cpp 复制代码
namespace etcd {
class Client {
    // 构造客户端,etcd_url 格式: "http://127.0.0.1:2379"
    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);

    // 获取指定 key 的值
    pplx::task<Response> get(std::string const& key);

    // 获取指定 key 目录下的数据列表
    pplx::task<Response> ls(std::string const& key);

    // 创建并获取一个存活 ttl 时间的租约
    pplx::task<Response> leasegrant(int ttl);

    // 获取一个租约保活对象
    pplx::task<std::shared_ptr<KeepAlive>> leasekeepalive(int ttl);

    // 删除指定 key(不存在时返回 ERROR_KEY_NOT_FOUND)
    pplx::task<Response> rm(std::string const& key);

    // 删除指定目录(不存在时返回 ERROR_KEY_NOT_FOUND)
    pplx::task<Response> rmdir(std::string const& key, bool recursive = false);

    // 监控一个 key 或子目录的变化
    pplx::task<Response> watch(std::string const& key, bool recursive = false);

    // 撤销一个指定的租约
    pplx::task<Response> leaserevoke(int64_t lease_id);

    // 分布式锁
    pplx::task<Response> lock(std::string const& key);
    pplx::task<Response> unlock(std::string const& lock_key);

    // 执行事务
    pplx::task<Response> txn(etcdv3::Transaction const& txn);
};
}

关于 pplx::task:这是微软并行库的异步结果对象。

  • 阻塞方式get() --- 阻塞直到任务执行完成,并获取任务结果
  • 非阻塞方式wait() --- 等待任务到达终止状态,然后返回任务状态

4.5 KeepAlive --- 租约保活

cpp 复制代码
namespace etcd {
class KeepAlive {
    KeepAlive(Client const& client, int ttl, int64_t lease_id = 0);
    KeepAlive(std::string const& address,
              std::function<void(std::exception_ptr)> const& handler,
              int ttl, int64_t lease_id = 0);

    int64_t Lease();    // 返回租约 ID
    void Cancel();      // 停止保活
};
}

4.6 Watcher --- 数据监控

cpp 复制代码
namespace etcd {
class Watcher {
    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 Wait(std::function<void(bool)> callback);  // 异步非阻塞,任务取消时回调
    bool Cancel();                            // 取消监控
};
}

5. 入门示例

5.1 目录结构

复制代码
.
├── get.cc
├── put.cc
└── makefile

5.2 put.cc --- 写入数据(带租约保活)

cpp 复制代码
#include <etcd/Client.hpp>
#include <etcd/Response.hpp>
#include <etcd/KeepAlive.hpp>
#include <thread>

void put(const std::string &host,
         const std::string &key,
         const std::string &val,
         std::shared_ptr<etcd::KeepAlive> &keepalive,
         int ttl = 3) {

    etcd::Client client(host);

    // 1. 先创建一个租约
    auto lease_resp = client.leasegrant(ttl).get();
    if (lease_resp.is_ok() == false) {
        std::cout << "申请租约失败:" << lease_resp.error_message() << std::endl;
        exit(-1);
    }
    auto lease_id = lease_resp.value().lease();

    // 2. 使用租约写入键值对
    auto resp = client.put(key, val, lease_id).get();
    if (resp.is_ok() == false) {
        std::cout << "新增数据失败: " << resp.error_message() << std::endl;
        exit(-1);
    }

    // 3. 设置保活回调(租约过期后自动重新注册)
    auto handler = [host, key, val, &keepalive, ttl](std::exception_ptr eptr) {
        try {
            put(host, key, val, keepalive, ttl);
        } catch (std::exception &e) {
            std::cout << "Etcd KeepAlive 异常: " << e.what() << std::endl;
            exit(-1);
        }
    };
    keepalive.reset(new etcd::KeepAlive(host, handler, ttl, lease_id));
}

int main() {
    std::string etcd_host = "http://192.168.65.128:2379";
    std::string key = "/test/instance";
    std::string val = "112.23.23.120:9090";
    std::shared_ptr<etcd::KeepAlive> keepalive;

    put(etcd_host, key, val, keepalive);

    std::cout << "回车后退出....\n";
    getchar();
    return 0;
}

5.3 get.cc --- 获取数据并监控变化

cpp 复制代码
#include <etcd/Client.hpp>
#include <etcd/Watcher.hpp>

// Watcher 回调函数:处理数据变化通知
void watcherCallback(etcd::Response const& resp) {
    if (resp.error_code()) {
        std::cout << "监视出错: " << resp.error_code();
        std::cout << "-" << resp.error_message() << std::endl;
    } else {
        for (auto &ev : resp.events()) {
            if (ev.event_type() == etcd::Event::EventType::PUT) {
                // 新增或修改:通过当前值查看
                std::cout << ev.kv().key() << " 新增/修改数据:";
                std::cout << ev.kv().as_string() << std::endl;
            } else if (ev.event_type() == etcd::Event::EventType::DELETE_) {
                // 删除:通过改变前的值了解哪个值被删除
                std::cout << ev.prev_kv().key() << " 删除数据:";
                std::cout << ev.prev_kv().as_string() << std::endl;
            }
        }
    }
}

void watch(const std::string &host,
           const std::string &key,
           std::shared_ptr<etcd::Watcher> &watcher) {

    etcd::Client etcd(host);

    // 1. 先列出当前已有数据
    auto resp = etcd.ls(key).get();
    if (resp.is_ok()) {
        for (int i = 0; i < resp.keys().size(); i++) {
            std::cout << resp.key(i) << " = ";
            std::cout << resp.value(i).as_string() << std::endl;
        }
    } else {
        std::cout << "获取数据出错: " << resp.error_code();
        std::cout << "-" << resp.error_message() << std::endl;
    }

    // 2. 设置 Watcher 监控数据变化(recursive=true 递归监控子目录)
    auto cb = std::bind(watcherCallback, std::placeholders::_1);
    watcher.reset(new etcd::Watcher(host, key, cb, true));

    // 3. 异步等待,Watcher 取消时自动重连
    watcher->Wait([host, key, &watcher](bool cancelled) {
        if (cancelled) {
            std::cout << "Etcd Watcher 重连被主动取消!" << std::endl;
            return;
        }
        watch(host, key, watcher);
    });
}

int main() {
    std::string etcd_host = "http://192.168.65.128:2379";
    std::string key = "/test";
    std::shared_ptr<etcd::Watcher> watcher;

    watch(etcd_host, key, watcher);

    std::cout << "回车后退出....\n";
    getchar();
    return 0;
}

5.4 Makefile

makefile 复制代码
all: get put

get: get.cc
	g++ -std=c++17 $^ -o $@ -lcpprest -letcd-cpp-api

put: put.cc
	g++ -std=c++17 $^ -o $@ -lcpprest -letcd-cpp-api

5.5 运行演示

bash 复制代码
$ make
$ ./put &
回车后退出....

$ ./get
/test/instance = 112.23.23.120:9090
回车后退出....
/test/instance 删除数据:112.23.23.120:9090    # put 进程退出后,租约过期自动删除

6. 服务管理封装

Etcd 作为一个内存键值存储中心,且提供了数据改变通知功能,非常适合作为服务注册发现中心。典型的服务管理包括三个操作:

  1. 服务注册:服务启动时,向 Etcd 注册服务名称和访问地址的键值对
  2. 服务发现:客户端通过 Etcd 获取服务的访问地址信息
  3. 健康检查:服务定期向 Etcd 发送心跳(租约保活),维持注册信息的有效性

6.1 设计思路

组件 职责
SvcProvider(服务注册) {/服务名称/实例ID, 访问地址} 的键值对注册到 Etcd,并通过 KeepAlive 维持租约
SvcWatcher(服务发现) 通过 lswatch 监控 / 目录,感知所有服务的上线和下线

6.2 服务注册客户端 --- SvcProvider

cpp 复制代码
class SvcProvider {
public:
    using ptr = std::shared_ptr<SvcProvider>;

    SvcProvider(const std::string &reg_center_addr,
                const std::string &svc_name,
                const std::string &svc_addr);

    // 注册服务,如:registry("user", "192.168.65.130:9000")
    // 实际 key = /user/<instance_id>,  value = 192.168.65.130:9000
    bool registry();

private:
    std::string make_key();   // 生成 key: /<svc_name>/<instance_id>

private:
    std::string _reg_center_addr;           // 注册中心地址
    std::string _instance_id;               // 当前节点的唯一标识
    std::string _svc_name;                  // 提供的服务名称
    std::string _svc_addr;                  // 节点地址
    std::shared_ptr<etcd::KeepAlive> _keepalive;  // 租约保活对象
};

核心实现:

cpp 复制代码
SvcProvider::SvcProvider(const std::string &reg_center_addr,
                         const std::string &svc_name,
                         const std::string &svc_addr)
    : _reg_center_addr(reg_center_addr)
    , _instance_id(biteutil::Random::code())
    , _svc_name(svc_name)
    , _svc_addr(svc_addr) {}

std::string SvcProvider::make_key() {
    std::stringstream ss;
    ss << "/" << _svc_name << "/" << _instance_id;
    return ss.str();
}

bool SvcProvider::registry() {
    // 1. 实例化 etcd 客户端对象
    etcd::Client client(_reg_center_addr);
    wait_for_connection(client);

    // 2. 创建 3s 租约
    auto lease_resp = client.leasegrant(3).get();
    if (lease_resp.is_ok() == false) {
        ERR("创建租约失败: {}", lease_resp.error_message());
        return false;
    }
    auto lease_id = lease_resp.value().lease();

    // 3. 向服务器添加数据(带租约)
    auto resp = client.put(make_key(), _svc_addr, lease_id).get();
    if (resp.is_ok() == false) {
        ERR("添加数据失败: {}", lease_resp.error_message());
        return false;
    }

    // 4. 实例化保活对象,对租约进行保活
    auto handler = [this](const std::exception_ptr &eptr) {
        this->registry();  // 异常时自动重新注册
    };
    _keepalive.reset(new etcd::KeepAlive(_reg_center_addr, handler, 3, lease_id));
    return true;
}

连接等待辅助函数:

cpp 复制代码
void wait_for_connection(etcd::Client &client) {
    while (!client.head().get().is_ok()) {
        WRN("连接 etcd 服务器失败...");
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

6.3 服务发现客户端 --- SvcWatcher

cpp 复制代码
class SvcWatcher {
public:
    using ptr = std::shared_ptr<SvcWatcher>;
    // 回调函数类型:参数1-服务名称,参数2-节点地址
    using ModCallbck = std::function<void(std::string, std::string)>;

    SvcWatcher(const std::string &reg_center_addr,
               ModCallbck &&online_callback,
               ModCallbck &&offline_callback);

    bool watch();   // 开始监控

private:
    void callback(const etcd::Response &resp);
    std::string parse_key(const std::string &key);  // 从 key 中提取服务名称

private:
    std::string _reg_center_addr;               // 注册中心地址
    ModCallbck _online_callback;                 // 服务上线回调
    ModCallbck _offline_callback;                // 服务下线回调
    std::shared_ptr<etcd::Watcher> _watcher;     // 监控对象
};

核心实现:

cpp 复制代码
// 从 key 中解析服务名称,需与注册时的格式对应
// key 格式: /user/instance_id  →  解析出 "user"
std::string SvcWatcher::parse_key(const std::string &key) {
    std::vector<std::string> arry;
    biteutil::STR::split(key, "/", arry);
    return arry[0];
}

void SvcWatcher::callback(const etcd::Response &resp) {
    if (resp.is_ok() == false) {
        ERR("监控出错: {}", resp.error_message());
        return;
    }
    const auto &events = resp.events();
    for (auto &e : events) {
        if (e.event_type() == etcd::Event::EventType::PUT) {
            std::string svc_name = parse_key(e.kv().key());
            std::string svc_addr = e.kv().as_string();
            DBG("{} 服务上线节点: {}", svc_name, svc_addr);
            if (_online_callback) _online_callback(svc_name, svc_addr);

        } else if (e.event_type() == etcd::Event::EventType::DELETE_) {
            std::string svc_name = parse_key(e.prev_kv().key());
            std::string svc_addr = e.prev_kv().as_string();
            DBG("{} 服务下线节点: {}", svc_name, svc_addr);
            if (_offline_callback) _offline_callback(svc_name, svc_addr);

        } else {
            WRN("无效通知类型");
        }
    }
}

bool SvcWatcher::watch() {
    // 1. 连接 etcd 服务器
    etcd::Client client(_reg_center_addr);
    wait_for_connection(client);

    // 2. 浏览根目录,获取当前所有已注册的服务节点
    auto resp = client.ls("/").get();
    if (resp.is_ok() == true) {
        auto values = resp.values();
        for (auto &v : values) {
            std::string svc_name = parse_key(v.key());
            std::string svc_addr = v.as_string();
            DBG("已有 {} 节点: {}", svc_name, svc_addr);
            if (_online_callback) _online_callback(svc_name, svc_addr);
        }
    }

    // 3. 递归监控根目录,获取服务上下线的实时通知
    auto cb = std::bind(&SvcWatcher::callback, this, std::placeholders::_1);
    _watcher.reset(new etcd::Watcher(_reg_center_addr, "/", cb, true));

    _watcher->Wait([this](bool cond) {
        if (cond == true) { return; }
        this->watch();  // Watcher 断开时自动重连
    });
    return true;
}

7. 完整使用示例

7.1 目录结构

复制代码
.
├── CMakeLists.txt
├── discovery.cc
└── registry.cc

7.2 registry.cc --- 服务注册

cpp 复制代码
#include "bite_scaffold/etcd.h"
#include "bite_scaffold/log.h"
#include <iostream>

int main() {
    bitelog::bitelog_init();

    const std::string url = "http://192.168.65.128:2379";
    const std::string svc_name = "user";
    const std::string svc_addr = "192.168.65.128:9000";

    // 1. 实例化服务提供者对象
    bitesvc::SvcProvider provider(url, svc_name, svc_addr);
    // 2. 注册服务
    provider.registry();

    getchar();
    return 0;
}

7.3 discovery.cc --- 服务发现

cpp 复制代码
#include "bite_scaffold/etcd.h"
#include "bite_scaffold/log.h"
#include <iostream>

void online(const std::string &svc_name, const std::string &svc_addr) {
    INF("新服务 {} 上线节点: {}", svc_name, svc_addr);
}

void offline(const std::string &svc_name, const std::string &svc_addr) {
    INF("新服务 {} 下线节点: {}", svc_name, svc_addr);
}

int main() {
    bitelog::bitelog_init();

    const std::string url = "http://192.168.65.128:2379";

    // 1. 实例化服务监控者对象
    bitesvc::SvcWatcher watcher(url, online, offline);
    // 2. 开始发现服务
    watcher.watch();

    getchar();
    return 0;
}

7.4 CMakeLists.txt

cmake 复制代码
cmake_minimum_required(VERSION 3.1.3)
project(discovery VERSION 1.0)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++17")

set(server_target "discovery")
add_executable(${server_target} ${CMAKE_CURRENT_SOURCE_DIR}/discovery.cc)

set(client_target "registry")
add_executable(${client_target} ${CMAKE_CURRENT_SOURCE_DIR}/registry.cc)

find_package(bite_scaffold REQUIRED)
target_link_libraries(${server_target} PRIVATE bite_scaffold::bite_scaffold)
target_link_libraries(${client_target} PRIVATE bite_scaffold::bite_scaffold)

7.5 运行演示

bash 复制代码
$ mkdir build && cd build
$ cmake ..
$ make

# 先启动服务发现端
$ ./discovery &
[06:16:10][debug]: user 服务上线节点: 192.168.65.128:9000
[06:16:10][info]: 新服务 user 上线节点: 192.168.65.128:9000

# 再启动服务注册端
$ ./registry

当 registry 退出(租约过期)时,discovery 端会收到服务下线通知。


8. 总结

本文系统介绍了基于 Etcd 的 C++ 服务注册与发现方案:

层次 内容
基础设施 Etcd 安装、配置、集群部署
SDK 层 etcd-cpp-apiv3 的安装与核心 API(Client、Value、Event、Response)
基础用法 put/get 操作、租约(LeaseGrant)、保活(KeepAlive)、监控(Watcher)
封装层 SvcProvider(服务注册 + 保活自动重连)、SvcWatcher(服务发现 + 递归监控 + 断线重连)
架构要点 key 命名规范(/服务名/实例ID)、租约 TTL 设置、回调解耦、目录递归监控

掌握了这些知识,你就可以在微服务架构中利用 Etcd 实现可靠的服务注册与发现,为分布式系统提供动态的服务路由能力。

相关推荐
飞翔中文网1 小时前
Java学习笔记之泛型
java·笔记·学习
春生野草1 小时前
Socket、Servlet、Tomcat
运维·服务器·网络
kvnew1 小时前
Ubuntu 26.04 一键安装/修复拼音输入法fcitx5+Rime
linux·运维·ubuntu
小则又沐风a1 小时前
进程篇: 进程概念的补充(了解环境变量和虚拟地址空间)
linux·运维·服务器·c++
艾莉丝努力练剑1 小时前
【Linux网络】Linux 网络编程:传输层协议TCP(五)
linux·运维·网络·计算机网络·udp
郝学胜-神的一滴1 小时前
[简化版 GAMES 101] 计算机图形学 11:频域·卷积·抗锯齿
c++·unity·图形渲染·opengl·three·unreal
无足鸟ICT1 小时前
【RHCA+】toilet命令(生成艺术字)
linux
a83331961 小时前
c语言课程设计小游戏,c语言小游戏设计案例
c语言·开发语言
kebidaixu1 小时前
设备树修改
linux