ETCD
概述
Etcd
是一个golang
编写的分布式、高可用的一致性键值存储系统,用于配置共享和服务发现等。它使用Raft
一致性算法来保持集群数据的一致性,且客户端通过长连接watch
功能,能够及时收到数据变化通知,相较于Zookeeper
框架更加轻量化。以下是关于etcd
的安装与使用方法的详细介绍。
节点配置
如果是单节点集群其实就可以不用进行配置,默认etcd
的集群节点通信端口为2380
,客户端访问端口为2379
。
若需要修改,则可以配置:/etc/default/etcd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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作为服务注册发现中心,你需要定义服务的注册和发现逻辑。这通常涉及到以下几个操作:
- 服务注册:服务启动时,向Etcd注册自己的地址和端口。
- 服务发现:客户端通过Etcd获取服务的地址和端口,用于远程调用。
- 健康检查:服务定期向Etcd发送心跳,以维持其注册信息的有效性。
etcd采用golang编写,v3版本通信采用grpc API,即(HTTP2+protobuf)
官方只维护了go语言版本的client库, 因此需要找到C/C++ 非官方的client 开发库:
etcd-cpp-apiv3
etcd-cpp-apiv3
是一个etcd的C++版本客户端API。它依赖于mipsasm, boost, protobuf, gRPC, cpprestsdk等库。
命名空间:
客户端类与接口介绍

Value对象:存放键值对数据的对象。
1 2 3 4 5 6 7
| class Value { bool is_dir(); std::string const& key() std::string const& as_string() int64_t lease() }
|
etcd会监控所管理的数据的变化,一旦数据产生变化会通知客户端。在通知客户端的时候,会返回改变前的数据和改变后的数据
1 2 3 4 5 6 7 8 9 10 11 12
| class Event { enum class EventType { PUT, DELETE_, INVALID, }; enum EventType event_type() const Value& kv() const Value& prev_kv() }
|
Response对象:针对请求进行响应。
1 2 3 4 5 6 7 8 9
| 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(); }
|
KeepAlive保活对象:一旦被析构,就无发保活,则租约数据失效被删除。
- 本身提供一个获取租约的接口
- 作用:针对一个租约可以不断进行续租,一直维护租约数据的有效性。
1 2 3 4 5 6 7 8 9 10
| class KeepAlive { KeepAlive(Client const& client, int ttl, int64_t lease_id = 0); int64_t Lease();
void Cancel(); }
|
Client对象:客户端操作句柄对象
- 提供了新增,获取数据的接口。
- 提供了获取保活对象的接口,以及租约的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| pplx::task 并行库异步结果对象 阻塞方式 get(): 阻塞直到任务执行完成,并获取任务结果 非阻塞方式 wait(): 等待任务到达终止状态,然后返回任务状态
class Client { 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); }
|
Watcher对象:进行数据变化通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Watcher { Watcher(Client const& client, std::string const& key, std::function<void(Response)> callback, bool recursive = false); 参数: Client const& client:监控的客户端对象 std::string const& key:要监控的键值对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(); }
|
举例
get.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #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; } for(auto const& 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 << "服务信息下线被删除" << 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; } } }
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() == false) { 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) << "服务" << endl;
auto watcher = etcd::Watcher(client, "/service", callback, true); watcher.Wait(); return 0; }
|
put.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #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(); auto lease_id = keep_alive->Lease(); auto resp1 = client.put("/service/user", "127.0.0.1:8080", 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:9090", lease_id).get(); if(resp2.is_ok() == false) { std::cout << "新增数据失败" << resp2.error_message() << std::endl; return -1; }
std::this_thread::sleep_for(std::chrono::seconds(10)); return 0; }
|
运行结果
10ms之后新增的键值对没有人再续租过期销毁,销毁过程中调用了回调函数。
1 2 3 4 5 6 7 8 9 10 11 12
| user@Ubuntu:~/build$ ./get.exe 127.0.0.1:9090可以提供/service/friend服务 127.0.0.1:8080可以提供/service/user服务 服务信息下线被删除 当前的值:/service/friend- 原来的值:/service/friend-127.0.0.1:9090 服务信息下线被删除 当前的值:/service/user- 原来的值:/service/user-127.0.0.1:8080
user@Ubuntu:~/build$ ./put.exe user@Ubuntu:~/build$
|