最近在搭后台项目环境时,接触到了ETCD。
又是新组件,又是一堆依赖库!
不过跑通一遍样例后,才发现它的定位非常清晰 ——分布式键值对注册中心。
这篇就简单记录一下我对 ETCD 的理解,以及 C++ 客户端的基本使用方式。
一、ETCD 是干什么的?
一句话总结:
ETCD 是一个分布式键值对数据存储服务器。
它的特点是:
多台主机可以共享一套键值数据,并且数据变化可以被实时感知。
举个直观的例子:
- 主机 A 在 ETCD 上写入:
name -> A - 主机 B 去查
name,也能得到A - 主机 A 把
name改成B - 主机 B 立刻收到通知:值从 A 变成了 B
所以它非常适合用来做服务注册与发现。
二、长连接与租约(Lease)机制
ETCD 里有一个很重要的概念:租约(Lease)。
你可以这样理解:
客户端向服务器“租”一个 ID,并定期续租。
只要租约还在,和这个租约绑定的数据就有效。
一旦客户端不再续租(程序退出或崩溃),数据自动过期删除。
我自己的理解更形象一点:
客户端就像在 ETCD 服务器上“付房租”。
一直交房租 → 数据一直存在
不交房租 → 数据被清理
其他客户端会收到通知:这个数据没了
这正好可以用来判断某个服务到底还在线还是已经下线。
三、ETCD 与 C++ 客户端环境安装
这一块我一开始也有点蒙:
我明明已经安装了 etcd,为什么后面还要装一堆 boost、protobuf、grpc 之类的库?
后来才理清:
etcd 本体 = 服务器程序
etcd-cpp-apiv3 = C++ 客户端接口库
前者负责存储键值数据
后者负责让我在 C++ 代码里能调用 ETCD 的接口
所以需要分两部分安装。
1)安装 ETCD 服务端
先安装 etcd 可执行程序:
etcd --version
能看到版本号说明安装成功。
启动方式(开发阶段直接前台运行即可):
etcd
默认监听地址:
http://127.0.0.1:2379
2)安装 etcd-cpp-apiv3 所需依赖
因为 etcd-cpp-apiv3 底层是通过 gRPC + protobuf + boost + 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
这一步的作用很简单:
为 C++ 调用 ETCD 的 API 准备底层通信与序列化环境
3)安装 etcd-cpp-apiv3 客户端库
接着安装 C++ 客户端接口库:
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
安装完成后,就可以在代码里直接:
#include “etcd/Client.hpp”
说明 C++ 客户端接口已经就绪。
4)最后环境关系图
到这里整个结构就很清晰了:
你的 C++ 代码
↓
etcd-cpp-apiv3 (C++ 客户端 API)
↓
gRPC + protobuf + boost + cpprestsdk
↓
ETCD 服务端 (127.0.0.1:2379)
这样你的代码才能真正通过网络去访问 ETCD 服务器。
小结一下
etcd = 键值存储服务器程序
etcd-cpp-apiv3 = C++ 调用接口封装
依赖库 = 支撑网络通信与序列化
三者配齐后,后面的 put / get / watch 示例代码才能正常运行。
四、几个核心对象理解
结合 C++ 客户端 API,我目前主要用到这几个对象:
Client 对象
提供最基本的接口:
- 新增键值对
- 获取键值对
- 删除键值对
也就是所有操作 ETCD 的入口。
KeepAlive 对象
它是对Lease / 续租的封装。
作用:
- 创建租约 ID
- 自动帮你续租
- 只要 KeepAlive 对象存在 → 数据不会过期
- 一旦对象释放 → 租约失效 → 数据自动删除
Response 对象
所有请求都会返回 Response,用来:
- 判断请求是否成功
- 获取返回的数据
- 获取事件变化内容
Watcher 对象
对某个 Key 或前缀进行监听。
一旦数据发生变化:
- PUT(新增 / 修改)
- DELETE(删除)
就会触发回调函数通知客户端。
五、ETCD 在项目中的作用
我的项目里,ETCD 的核心目的就是:
让所有节点知道当前有哪些服务在线。
服务端启动:
- 向 ETCD 注册自己的服务信息
- 绑定租约
- 定期续租
客户端启动:
- Watch
/service前缀 - 一旦有新增 / 删除服务,立即收到通知
这样就能实现自动感知服务上线和下线。
六、样例代码
下面是我当前跑通的两个样例:
- put.cc:向 ETCD 写入数据并绑定租约
- get.cc:获取数据并监听变化
put.cc
#include<iostream>#include"etcd/Client.hpp"#include"etcd/Response.hpp"#include"etcd/KeepAlive.hpp"#include<thread>intmain(intargc,char*argv[]){std::string etcd_host="http://127.0.0.1:2379";//实例化客户端对象etcd::Clientclient(etcd_host);//创建租约对象autokeep_alive=client.leasekeepalive(3).get();//获取租约对象autolease_id=keep_alive->Lease();//像etcd新增数据autoresp1=client.put("/service/user","111",lease_id).get();if(!resp1.is_ok()){std::cout<<"put key failed, error code: "<<resp1.error_code()<<", error message: "<<resp1.error_message()<<std::endl;return-1;}autoresp2=client.put("/service/haha","222").get();if(!resp2.is_ok()){std::cout<<"put key failed, error code: "<<resp2.error_code()<<", error message: "<<resp2.error_message()<<std::endl;return-1;}std::this_thread::sleep_for(std::chrono::seconds(10));return0;}get.cc
#include"etcd/Client.hpp"#include"etcd/Response.hpp"#include"etcd/KeepAlive.hpp"#include"etcd/Watcher.hpp"#include<thread>#include<iostream>voidcallback(constetcd::Response&resp){if(!resp.is_ok()){std::cout<<"watch key failed, error code: "<<resp.error_code()<<", error message: "<<resp.error_message()<<std::endl;return;}for(autoconst&event:resp.events()){if(event.event_type()==etcd::Event::EventType::PUT){std::cout<<"服务信息发生了改变"<<std::endl;std::cout<<"原来的值为:"<<event.prev_kv().as_string()<<std::endl;std::cout<<"现在的值为:"<<event.kv().as_string()<<std::endl;}elseif(event.event_type()==etcd::Event::EventType::DELETE_){std::cout<<"服务信息被删除了"<<std::endl;std::cout<<"删除的值为:"<<event.prev_kv().as_string()<<std::endl;}}}intmain(intargc,char*argv[]){std::string etcd_host="http://127.0.0.1:2379";//实例化客户端etcd::Clientclient(etcd_host);//获取指定的键值对信息autoresp=client.ls("/service").get();if(!resp.is_ok()){std::cout<<"get key failed, error code: "<<resp.error_code()<<", error message: "<<resp.error_message()<<std::endl;return-1;}intsz=resp.keys().size();for(inti=0;i<sz;++i)std::cout<<resp.values()[i].as_string()<<"可以提供"<<resp.keys()[i]<<std::endl;//实例化一个键值对事件监控对象autowatcher=etcd::Watcher(client,"/service",callback,true);std::this_thread::sleep_for(std::chrono::seconds(10));return0;}七、运行现象说明
运行顺序:
- 启动 ETCD 服务端
- 运行 get.cc(监听端)
- 运行 put.cc(注册端)
可以看到:
- get.cc 启动时先打印已有服务
- put.cc 写入数据
- get.cc 立即收到 PUT 事件通知
- put.cc 程序结束
- 租约到期
- get.cc 收到 DELETE 事件通知
这说明:
租约控制数据生命周期
Watcher 实现实时变更通知
八、总结一下
目前我对 ETCD 的理解可以概括成三点:
- ETCD 是分布式键值存储服务器
- Lease / KeepAlive 用来维持数据有效期
- Watcher 用来感知数据变化
组合起来,就能实现:
服务注册 → 自动保活 → 自动下线 → 实时通知
这正是后台项目里服务注册与发现的基础能力。