首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Linux系统C++开发环境搭建工具(二)—— etcd 使用指南

Linux系统C++开发环境搭建工具(二)—— etcd 使用指南

作者头像
用户11719974
发布2025-11-15 10:42:32
发布2025-11-15 10:42:32
80
举报

etcd简介

  etcd 是一个分布式、高可用的键值存储系统(以key: val的形式做数据存储),主要用于在分布式系统中安全地存储和管理关键数据。 它最著名的角色是 Kubernetes 的“大脑”,负责存储整个集群的状态和配置信息。   etcd 服务器类似于一个数据库,存储键值对数据。所有客户端都可以通过长连接共享这些数据。一个客户端修改了键值对,etcd 服务器会通知所有正在监听该键值对的客户端。

常用场景:服务发现(Service Discovery)

  • 场景:在微服务架构中,服务实例的 IP 和端口是动态变化的。服务启动时可以将自己的地址注册到 etcd,消费者则从 etcd 查询可用的服务地址。
  • 类比:就像电话簿,服务在这里“登记”和“查找”。

图示:

在这里插入图片描述
在这里插入图片描述

etcd安装与使用

安装:

代码语言:javascript
复制
sudo apt-get install etcd

配置文件:

代码语言:javascript
复制
vim /etc/default/etcd

更改绑定监听地址,使用外部网络能访问: ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0" 更改外部访问的地址: ETCD_ADVERTISE_CLIENT_URLS="http://公网ip:端口"

重新启动:

代码语言:javascript
复制
sudo systemctl restart etcd

配置指定API版本:

代码语言:javascript
复制
vim /etc/profile

在末尾加: export ETCDCTL_API=3 重新加载配置文件:

代码语言:javascript
复制
source /etc/profile

验证:

在这里插入图片描述
在这里插入图片描述

搭建c++客户端,使用第三方库API接口 下载库:

代码语言:javascript
复制
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

下载etcd-cpp-apiv3库源码(可选):

代码语言:javascript
复制
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

API接口的使用

  • Client对象:客户端操作句柄对象 在构建该对象时需要传入参数:url_or_endpoints,即字符串或字符串向量,指定 etcd 服务地址。键值数据的推送和拉取,租约的设置等都是通过该对象提供的接口完成。
  • KeepAlive保活对象:针对一个租约可以不断进行续租,从而维持租约数据的有效性。 该对象通过Client对象提供的leasekeepalive()租约接口返回值再进行get()而得到。
  • Response对象:针对请求进行响应。 该对象通常由Client对象提供的接口put()/ls()返回而得到。
  • Value对象:存放键值对数据。
  • Watcher对象:进行数据变化通知。 构建该对象需要参数:
    • client对象
    • 要监听的键名
    • 变化回调函数
    • 是否递归监听前缀匹配的所有键,通常填true

图示:

在这里插入图片描述
在这里插入图片描述

示例: put.cc

代码语言:javascript
复制
#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Watcher.hpp>
#include <thread>
int main(int argc,char* argv[])
{
    //创建客户端
    etcd::Client client("http://127.0.0.1:2379");
    //指定租约并获取租约保活对象
    auto keep_alive = client.leasekeepalive(3).get();
    //获取租约id
    auto lease_id = keep_alive->Lease();
    //向etcd新增数据
    auto rsp = client.put("/source/user","127.0.0.1:6000",lease_id).get();
    if(rsp.is_ok()==false)
        std::cout<<"新增数据失败"<<std::endl;
    rsp = client.put("/source/file","127.0.0.1:6001",lease_id).get();
    if(rsp.is_ok()==false)
        std::cout<<"新增数据失败"<<std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(10));
    return 0;
}

get.cc

代码语言:javascript
复制
#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/Value.hpp>
#include <etcd/SyncClient.hpp>

void callback(const etcd::Response& rsp)
{
    if(rsp.is_ok()==false)
    {
        std::cout<<"错误事件通知"<<rsp.error_message()<<std::endl;
        return;
    }
    for(auto const& ev: rsp.events())
    {
        if (ev.event_type() == etcd::Event::EventType::PUT) {
            std::cout << "服务信息发生了改变:\n" ;
            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 << "服务信息下线被删除:\n";
            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[])
{
    //创建客户端
    etcd::Client client("http://127.0.0.1:2379");
    //获取数据
    auto rsp = client.ls("/source").get();
    if(rsp.is_ok()==false)
    {
        std::cout<<"数据获取失败"<<std::endl;
        return -1;
    }
    int n = rsp.keys().size();
    for(int i = 0;i<n;i++)
    {
        std::cout<<rsp.value(i).as_string()<<":"<<rsp.key(i)<<std::endl;
    }
    auto watcher = etcd::Watcher(client,"/source",callback,true);
    watcher.Wait();
    return 0;
}

Makefile:

代码语言:javascript
复制
all:put get
put:put.cc
	g++ -o $@ $^ -letcd-cpp-api -lcpprest
get:get.cc
	g++ -o $@ $^ -letcd-cpp-api -lcpprest
.PHONY:clean
clean:
	rm -rf put get
在这里插入图片描述
在这里插入图片描述

以服务发现场景为例进行二次封装

使用原生 API 接口较为复杂繁琐,接下来我们针对服务发现场景进行二次封装

两个客户端:

  • 服务注册客户端:向服务器新增服务信息数据,并进行保活
  • 服务发现客户端:从服务器查找服务信息数据,并进行改变事件监控

思想:

  1. 封装服务注册客户端类: 提供一个接口:向服务器新增数据并保活 参数:注册中心地址、新增的服务信息
  2. 封装服务发现客户端类: 提供两个设置回调函数的接口:提供服务上线和下线的事件处理接口 提供一个设置根目录的接口:用于获取指定目录下的数据以及监控目录下的数据的改变

示例:

代码语言:javascript
复制
#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/Value.hpp>
#include <etcd/SyncClient.hpp>
#include <memory>
#include <functional>
class Registry
{
public:
    Registry(const std::string& host)
    :_client(std::make_shared<etcd::Client>(host))
    ,_keep_alive(_client->leasekeepalive(3).get())
    ,_lease_id(_keep_alive->Lease())
    {}
    bool registry(const std::string& key,const std::string& val)
    {
        auto rsp = _client->put(key,val,_lease_id,true).get();
        if(rsp.is_ok()==false)
        {
            //建议替换为日志输出
            std::cout<<key<<": "<<val<<"注册失败"<<std::endl;
            return false;
        }
        else return true;
    }
    ~Registry()
    {
        _client->Cancel();
    }
private:
    std::shared_ptr<etcd::Client> _client;
    std::shared_ptr<etcd::KeepAlive> _keep_alive;
    uint64_t _lease_id;
};
class Discovery
{
public:
    using NotifyCallback = std::function<void(std::string,std::string)>;
    Discovery(const std::string& host
        ,const std::string& basedir
        ,const NotifyCallback& put_cb
        ,const NotifyCallback& del_cb)
    :
    _client(std::make_shared<etcd::Client>(host))
    ,_put_cb(put_cb)
    ,_del_cb(del_cb)
    {
        auto rsp = _client->ls(basedir).get();
        if(rsp.is_ok()==false)
        {
            std::cout<<"获取数据失败 "<<rsp.error_message()<<std::endl;
        }
        int sz = rsp.keys().size();
        for(int i=0;i<sz;i++)
        {
            if(_del_cb)
                _put_cb(rsp.key(i),rsp.value(i).as_string());
        }
        _watcher = std::make_shared<etcd::Watcher>(*_client,basedir
            ,std::bind(&Discovery::callback,this, std::placeholders::_1),true);
    }
    void callback(const etcd::Response& rsp)
    {
        if(rsp.is_ok()==false)
        {
            std::cout<<"错误事件通知"<<rsp.error_message()<<std::endl;
            return;
        }
        for(auto const& ev: rsp.events())
        {
            if (ev.event_type() == etcd::Event::EventType::PUT) {
                std::cout << "上线服务:" << ev.kv().key() << "-" << ev.kv().as_string() << std::endl;
            }else if (ev.event_type() == etcd::Event::EventType::DELETE_) {
                std::cout << "下线服务:" << ev.kv().key() << "-" << ev.kv().as_string() << std::endl;
            }
        }
    }
    ~Discovery()
    {
        _watcher->Cancel();
    }
private:
    NotifyCallback _put_cb;
    NotifyCallback _del_cb;
    std::shared_ptr<etcd::Client> _client;
    std::shared_ptr<etcd::Watcher> _watcher;
};

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • etcd简介
  • etcd安装与使用
  • API接口的使用
  • 以服务发现场景为例进行二次封装
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档