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. 入门示例)
- [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 版本:
bashsudo 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 作为一个内存键值存储中心,且提供了数据改变通知功能,非常适合作为服务注册发现中心。典型的服务管理包括三个操作:
- 服务注册:服务启动时,向 Etcd 注册服务名称和访问地址的键值对
- 服务发现:客户端通过 Etcd 获取服务的访问地址信息
- 健康检查:服务定期向 Etcd 发送心跳(租约保活),维持注册信息的有效性
6.1 设计思路
| 组件 | 职责 |
|---|---|
| SvcProvider(服务注册) | 将 {/服务名称/实例ID, 访问地址} 的键值对注册到 Etcd,并通过 KeepAlive 维持租约 |
| SvcWatcher(服务发现) | 通过 ls 和 watch 监控 / 目录,感知所有服务的上线和下线 |
6.2 服务注册客户端 --- SvcProvider
cpp
class SvcProvider {
public:
using ptr = std::shared_ptr<SvcProvider>;
SvcProvider(const std::string ®_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 ®_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 ®_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 实现可靠的服务注册与发现,为分布式系统提供动态的服务路由能力。