1. 简介
- Etcd 是一个 golang 编写的分布式、高可用的一致性键值存储系统,用于配置共享和服务发现等
- 它使用 Raft 一致性算法来保持集群数据的一致性,且客户端通过长连接 watch 功能,能够及时收到数据变化通知,相较于 Zookeeper 框架更加轻量化
2. 安装
1. 安装 etcd
- 安装:
sudo apt install etcd - 启动服务:
sudo systemctl start etcd - 设置开机自启:
sudo systemctl enable etcd
2. 节点配置
- 如果是单节点集群其实就可以不用进行配置,默认 etcd 的集群节点通信端口为 2380,客户端访问端口为 2379
- 若需要修改,则可以配置:
/etc/default/etcd
#节点名称,默认为 "default"
ETCD_NAME="etcd1"
#数据目录,默认为 "${name}.etcd"
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"
#初始集群令牌 - 集群的 ID
#ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
#ETCD_INITIAL_CLUSTER_STATE="new"
#以下为安全配置,如果要求 SSL 连接 etcd 的话,把下面的配置启用,并修改文件路径
#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"
3. 运行验证
- 命令行输入:
etcdctl put key "SnowK"
完成后,加载配置文件,并重新执行测试命令
$ source /etc/profile
$ etcdctl put key "SnowK"
OK
$ etcdctl get key
key SnowK
$ etcdctl del mykey
如果以下出现报错,在 /etc/profile 默认声明环境变量 ETCDCTL_API=3 以确定 etcd 版本
$ etcdctl put key "SnowK"
No help topic for'put'
3. 搭建服务注册发现中心
1. 服务注册与发现逻辑
- 使用 etcd 作为服务注册发现中心,需要定义服务的注册和发现逻辑
- 通常涉及到以下几个操作:
- 服务注册:服务启动时,向 etcd 注册自己的地址和端口
- 服务发现:客户端通过 etcd 获取服务的地址和端口,用于远程调用
- 健康检查:服务定期向 Etcd 发送心跳,以维持其注册信息的有效性
- 官方只维护了 golang 的 client 库,因此需要找到 C/C++ 非官方的 client 开发库
2. etcd-cpp-apiv3
- etcd-cpp-apiv3 是一个 etcd 的 C++ 版本客户端 API,它依赖于 mipsasm, boost, protobuf, gRPC, cpprestsdk 等库
- Github
API 框架安装:
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
依赖安装:
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. 客户端
类与接口介绍
- Client 对象:客户端操作句柄对象
- 提供了新增,获取数据的接口
- 提供了获取保活对象的接口,以及租约的接口
- KeepAlive 保活对象:一旦被析构,则无法保活,则租约数据失效被删除
- 本身提供一个获取租约 ID 的接口
- 作用:针对一个可以不断进行续租 --> 一直维持租约数据的有效性
- Response 对象:针对请求进行的响应
- Value 对象:存放键值对数据的对象
- Watcher 对象:进行数据变化通知的类
// pplx::task 并行库异步结果对象
// 阻塞方式 get(): 阻塞直到任务执行完成,并获取任务结果
// 非阻塞方式 wait(): 等待任务到达终止状态,然后返回任务状态
namespace etcd {
class Value {
public:
bool is_dir(); // 判断是否是一个目录
std::string const& key(); // 键值对的 key 值
std::string const& as_string(); // 键值对的 val 值
int64_t lease(); // 用于创建租约的响应中,返回租约 ID
};
// etcd 会监控所管理的数据的变化,一旦数据产生变化会通知客户端
// 在通知客户端的时候,会返回改变前的数据和改变后的数据
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;
;
;
;
;
;
};
{
:
(Client & client, ttl, lease_id = );
;
;
};
{
:
(std::string & etcd_url, std::string & load_balancer = );
;
;
;
;
pplx::task<std::shared_ptr<KeepAlive>> ( ttl);
;
;
};
{
:
(Client & client, std::string & key,
std::function<(Response)> callback,
recursive = );
(std::string & address, std::string & key, std::function<(Response)> callback, recursive = );
;
;
};
}
使用示例
运行结果:
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);
// 获取租约保活对象 --> 伴随着创建一个指定有效时长的租约
auto keep_alive = client.leasekeepalive(3).get();
// 获取租约 ID
auto lease_id = keep_alive->Lease();
// 向 etcd 新增数据
auto resp1 = client.put("/service/user", "127.0.0.1:3366", lease_id).get();
if(resp1.is_ok() == false) {
std::cout << "新增数据失败:" << resp1.error_message() << std::endl;
return -1;
}
auto resp2 = client.put("/service/friend", "127.0.0.1:6633").get();
(resp() == ) {
std::cout << << resp() << std::endl;
;
}
std::this_thread::(std::chrono::());
;
}
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() == false) {
std::cout << "收到一个错误的事件通知:" << resp.error_message() << std::endl;
return;
} else {
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 (ev.() == etcd::Event::EventType::DELETE_) {
std::cout << ;
std::cout << << ev.().() << << ev.().() << std::endl;
std::cout << << ev.().() << << ev.().() << std::endl;
}
}
}
}
{
std::string etcd_host = ;
;
resp = client.().();
(resp.() == ) {
std::cout << << resp.() << std::endl;
;
}
sz = resp.().();
( i = ; i < sz; i++) {
std::cout << resp.(i).() << << resp.(i) << << std::endl;
}
watcher = etcd::(client, , CallBack, );
watcher.();
;
}
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


