LinuxC++——etcd分布式键值存储系统API(libetcd-cpp-api3)下载与二次封装

文章目录

etcdAPI的介绍与安装

etcd-cpp-apiv3 是一个 C++ 语言编写的 etcd 客户端库,用于与 etcd 分布式键值存储系统进行交互。下面这个表格汇总了它的核心信息:

特性 说明
项目简介 基于 C++ 的 etcd v3 API 客户端库
核心功能 分布式键值存储、配置管理、服务发现、分布式锁
主要依赖 Boost、gRPC、Protobuf、cpprestsdk
官方仓库 github下载地址
通信协议 通过 gRPC 与 etcd 服务器通信 (HTTP2 + protobuf)

安装 etcd-cpp-apiv3

安装 etcd-cpp-apiv3 前需要解决其依赖。以下是在 Ubuntu 系统上的主要步骤:

  1. 安装系统依赖

    打开终端,执行以下命令安装编译所需的工具和库:

    bash 复制代码
    sudo apt install -y ca-certificates ccache cmake libboost-all-dev libcurl4-openssl-dev libgrpc-dev libgrpc++-dev libprotobuf-dev libssl-dev libz-dev lsb-release protobuf-compiler-grpc screenfetch wget

    这个命令安装了 Boost、Protobuf、gRPC 等必要的开发库。

  2. 编译安装 cpprestsdk
    etcd-cpp-apiv3 依赖 cpprestsdk。如果系统仓库的版本不满足要求,可以手动编译:

    bash 复制代码
    git clone https://github.com/microsoft/cpprestsdk.git
    cd cpprestsdk
    mkdir build && cd build
    cmake .. -DBUILD_TESTS=ON -DBUILD_SAMPLES=ON -DCPPREST_EXCLUDE_WEBSOCKETS=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
    make -j 
    sudo make install

    这里克隆了 cpprestsdk 的源码,并通过 CMake 进行配置和编译安装。

  3. 编译安装 etcd-cpp-apiv3

    最后,克隆 etcd-cpp-apiv3 的官方仓库并编译安装:

    bash 复制代码
    git clone https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git
    cd etcd-cpp-apiv3
    mkdir build && cd build
    cmake .. -DBUILD_ETCD_TESTS=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
    make -j
    sudo make install

    这个过程会将库文件和头文件安装到系统路径。

使用示例与编译

安装完成后,你就可以在 C++ 项目中使用 etcd-cpp-apiv3 了。

  1. 示例代码

    下面是一个简单的示例,演示如何设置和获取键值对:

    cpp 复制代码
    #include <etcd/Client.hpp>
    #include <etcd/Response.hpp>
    #include <iostream>
    
    int main() {
        // 初始化客户端,连接到本地 etcd 服务器
        etcd::Client etcd("http://127.0.0.1:2379");
        
        // 设置键值对
        etcd::Response response = etcd.set("foo", "bar").get();
        if (response.is_ok()) {
            std::cout << "Key set successfully" << std::endl;
        } else {
            std::cerr << "Error: " << response.error_message() << std::endl;
        }
        
        // 获取键值对
        response = etcd.get("foo").get();
        if (response.is_ok()) {
            std::cout << "Value: " << response.value().as_string() << std::endl;
        } else {
            std::cerr << "Error: " << response.error_message() << std::endl;
        }
        return 0;
    }
  2. 编译命令

    假设你的源代码文件名为 main.cpp,可以使用类似下面的命令来编译:

    复制代码
    g++ -std=c++11 main.cpp -o main -letcd-cpp-api -lprotobuf -lgrpc++ -lgrpc -lz -lcpprest -lssl -lcrypto -lboost_system -lpthread

    然后运行程序:

    复制代码
    ./main

主要特性与应用场景

  • 主要特性etcd-cpp-apiv3 提供了简单易用的 C++ 接口,支持包括 KV 存储、租约(Lease)、观察者(Watcher)和分布式锁(Lock)在内的多种操作。
  • 应用场景 :这个库常用于微服务架构 中的服务发现配置管理,可以实现服务实例的自动注册与发现,以及配置信息的集中管理和动态更新

etcd二次封装

etcdTool.hpp

cpp 复制代码
#pragma once
#include <algorithm>
#include <cstdint>
#include <etcd/Client.hpp>
#include <etcd/Response.hpp>
#include <etcd/SyncClient.hpp>
#include <etcd/Value.hpp>
#include <etcd/Watcher.hpp>
#include <etcd/KeepAlive.hpp>
#include <mutex>
#include <pplx/pplxtasks.h>
#include <string>
#include <functional>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include "Log.hpp"

namespace Etcd_Tool
{
    typedef struct ResgisterInfo
    {
        //注册信息 服务名------服务地址------租约时间
        std::string service_name_;
        std::string service_addr_;
        int ttl_ = 30;
    }resinfo_t;

    //根目录
    //inline static std::string rootservice = "/services/";

    //注册类
    class RegisterEtcd
    {
        public:
            RegisterEtcd(const std::string& serverinfo)
            {
                //初始化日志
                LogModule::Log::Init();
                //创建客户端连接服务器
                try
                {
                    etcd_client_ = std::make_shared<etcd::Client>(serverinfo);
                    LOG_TRACE("etcd Client link sucess!: {}", serverinfo);
                }
                catch (const std::exception& e) 
                {
                    LOG_DEBUG("create Client error: {}", e.what());
                }
                catch (...)
                {
                    LOG_ERROR("Unknown Error!");
                }

                //LOG_INFO("create client sucess!");
            }

            //注册服务函数
            bool Register(const resinfo_t& info)
            {
                if(keepalives_.find(info.service_name_) != keepalives_.end())
                {
                    //检查是否已经注册过服务
                    LOG_DEBUG("Service already registered: {}", info.service_name_);
                    return false;
                }

                //加锁保护数据
                std::lock_guard<std::mutex> lock(mutex_);

                try
                {
                    //创建租约保活
                    std::shared_ptr<etcd::KeepAlive> keepalive = etcd_client_->leasekeepalive(info.ttl_).get();
                    if(!keepalive)
                    {
                        LOG_DEBUG("keepalive is null! leasekeepalive fail!");
                        return false;
                    }
                    //获取租约id
                    int64_t leaseid = keepalive->Lease();

                    //注册服务信息,绑定租约
                    etcd::Response r = etcd_client_->set(info.service_name_, info.service_addr_, leaseid).get();
                    if(!r.is_ok())
                    {
                        LOG_DEBUG("set fail: {}" , r.error_message());
                        //注册失败,处理租约
                        keepalive.reset();
                        return false;
                    }

                    //注册服务保存进入类信息
                    keepalives_[info.service_name_] = keepalive;
                    services_.push_back(info);

                    LOG_INFO("Register sucess, service name is : {}", info.service_name_);
                }
                catch (const std::exception& e)
                {
                    LOG_ERROR("Register fail: {}", e.what());
                    return false;
                }
                catch (...)
                {
                    LOG_ERROR("Unknown Error!");
                    return false;
                }

                return true;
            }

            //注销服务函数------服务名
            bool UnRegister(const std::string& service_name)
            {
                std::lock_guard<std::mutex> lock(mutex_);

                //检查服务是否存在
                auto it = keepalives_.find(service_name);
                if(it == keepalives_.end())
                {
                    LOG_INFO("service name invalid!");
                    return false;
                }

                try
                {
                    //服务存在,取消租约
                    it->second.reset();
                    //删除服务器中的服务
                    auto res = etcd_client_->rm(it->first).get();
                    if(!res.is_ok())
                    {
                        LOG_DEBUG("Client rm error: {}", res.error_message());
                        return false;
                    }

                    //删除类中信息
                    keepalives_.erase(it);

                    services_.erase(std::remove_if(services_.begin(), services_.end()
                    , [&](const resinfo_t& it){
                        return it.service_name_ == service_name;
                    }), services_.end());

                    LOG_INFO("UnRegister sucess, service name: {}", service_name);

                    return true;
                }
                catch (const std::exception& e)
                {
                    LOG_ERROR("UnRegister fail: {}", e.what());
                    return false;
                }
                catch (...)
                {
                    LOG_ERROR("Unknown Error!");
                    return false;
                }

                return true;
            }

            bool UpdateRegister(const resinfo_t& info)
            {
                //检查服务是否存在
                auto it = keepalives_.find(info.service_name_);
                if(it == keepalives_.end())
                {
                    LOG_DEBUG("Service not find: {}", info.service_name_);
                    return false;
                }

                std::lock_guard<std::mutex> lock(mutex_);
                try
                {
                    int64_t leaseid = it->second->Lease();
                    //不存在则创建,存在则更新键值对
                    auto res = etcd_client_->set(info.service_name_, info.service_addr_,  leaseid).get();
                    if(!res.is_ok())
                    {
                        LOG_ERROR("Update Register Client set error: {}", res.error_message());
                        return false;
                    }

                    //更新本地缓存
                    for(auto& e : services_)
                    {
                        if(e.service_name_ == info.service_name_)
                        {
                            e = info;
                            break;
                        }
                    }
                }
                catch (const std::exception& e)
                {
                    LOG_ERROR("UpdateRegister fail: {}", e.what());
                    return false;
                }
                catch (...)
                {
                    LOG_ERROR("Unknown Error!");
                    return false;
                }

                return true;
            }

            std::vector<resinfo_t> Have_Services()
            {
                std::lock_guard<std::mutex> lock(mutex_);
                return services_;
            }

            bool Clear()
            {
                //防止死锁
                bool allsucess = true;
                std::vector<std::string> service_names;
                {
                    std::lock_guard<std::mutex> lock(mutex_);
                    for(auto& e : keepalives_)
                    {
                        service_names.push_back(e.first);
                    }
                }

                for(auto& e : service_names)
                {
                    bool b = UnRegister(e);
                    if(!b)
                    {
                        LOG_ERROR("Clear fail!");
                        allsucess = false;
                    }
                }

                return allsucess;
            }

            ~RegisterEtcd() = default;
        private:
            std::shared_ptr<etcd::Client> etcd_client_;
            std::unordered_map<std::string, std::shared_ptr<etcd::KeepAlive>> keepalives_;
            std::vector<resinfo_t> services_;
            mutable std::mutex mutex_;
    };

    enum filetype
    {
        DIR,
        FILE
    };

    //监视回调函数
    using callbackfunc = std::function<void (const etcd::Response& res)>;

    typedef struct Monitorinfo
    {
        filetype type_;
        std::string monitor_path_;
        callbackfunc cbfunc_;
    } monitor_t;

    class MonitorEtcd
    {
        public:
            MonitorEtcd(const std::string& serverinfo)
            {
                try
                {
                    LogModule::Log::Init();
                    etcd_Client_ = std::make_shared<etcd::Client>(serverinfo);
                    LOG_TRACE("etcd Client link sucess!: {}", serverinfo);
                }
                catch (const std::exception& e)
                {
                    LOG_ERROR("std::make_shared<etcd::Client> fail, server: {}, exception: {}", serverinfo, e.what());
                    return;
                }
                catch (...)
                {
                    LOG_ERROR("Unknown Error!, server: {}", serverinfo);
                    return;
                }
            }

            bool PushMonitor(const monitor_t& info)
            {
                if (!etcd_Client_) 
                {
                    LOG_ERROR("etcd客户端未初始化");
                    return false;
                }

                try
                {
                    std::lock_guard<std::mutex> lock(mtx_);
                    auto watchopt = info.type_ == DIR ? true : false;
                    std::unique_ptr<etcd::Watcher> w = std::make_unique<etcd::Watcher>(
                        *etcd_Client_.get(), 
                        info.monitor_path_,
                        info.cbfunc_,
                        watchopt);
                    
                    monitoring_set_[info.monitor_path_] = 
                        std::make_pair(std::move(w), info);
                    LOG_INFO("PushMonitor sucess: {}", info.monitor_path_);
                }
                catch (const std::exception& e)
                {
                    LOG_ERROR("PushMonitor fail, monitor path: {}, exception: {}", info.monitor_path_, e.what());
                    return false;
                }
                catch (...)
                {
                    LOG_ERROR("Unknown Error!, monitor path: {}", info.monitor_path_);
                    return false;
                }
                
                return true;
            }

            bool CancelMonitor(const std::string& monitor_path)
            {
                if (!etcd_Client_) 
                {
                    LOG_ERROR("etcd客户端未初始化");
                    return false;
                }

                std::lock_guard<std::mutex> lock(mtx_);

                auto it = monitoring_set_.find(monitor_path);
                if (it == monitoring_set_.end())
                {
                    LOG_DEBUG("monitor_path cannot find: {}", monitor_path);
                    return false;
                }

                try
                {
                    bool b = it->second.first->Cancel();
                    if (!b)
                    {
                        LOG_DEBUG("Cancel monitor error: {}", monitor_path);
                    }

                    monitoring_set_.erase(it);
                    LOG_INFO("CancelMonitor sucess: {}", monitor_path);
                }
                catch (const std::exception& e)
                {
                    LOG_ERROR("CancelMonitor fail, monitor path: {}, exception: {}", monitor_path, e.what());
                    return false;
                }
                catch (...)
                {
                    LOG_ERROR("Unknown Error!, monitor path: {}", monitor_path);
                    return false;
                }

                return true;
            }

            ~MonitorEtcd()
            {
                std::lock_guard<std::mutex> lock(mtx_);
                for (auto& [path, pair] : monitoring_set_) 
                {
                    pair.first->Cancel();  // 取消所有监听器
                }
                monitoring_set_.clear();
            }
        private:
            mutable std::mutex mtx_;
            std::shared_ptr<etcd::Client> etcd_Client_;
            std::unordered_map<std::string, std::pair<std::unique_ptr<etcd::Watcher>, monitor_t>> monitoring_set_;
    };
}; // namespace etcd_service

Log.hpp

cpp 复制代码
#pragma once
#include <atomic>
#include <cstddef>
#include <memory>
#include <string>
#include <vector>

#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/async_logger.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>

namespace LogModule 
{
    enum OutputMode
    {
        CONSOLE_ONLY,
        FILE_ONLY,
        BOTH
    };

    typedef struct LogInfo
    {   
        OutputMode outmode = CONSOLE_ONLY;
        bool is_debug_ = true;
        std::string logfile_ = "logfile.txt";
        bool is_async_ = false;
        size_t queue_size_ = 1 << 12;
        size_t thread_num_ = 1;
    } LogInfo_t;

    class Log
    {
    public:
        static void Init(const LogInfo_t& loginfo = LogInfo_t())
        {
            if(is_init_)
                return;
            logconf_ = loginfo;
            create_logger();
            is_init_ = true;
        }

        static Log& GetInstance()
        {
            static Log log;
            return log;
        }

        template<typename... Args>
        void trace(const char* fmt, const Args&... args)
        {
            if(logger_)
                logger_->trace(fmt, args...);
        }

        template<typename... Args>
        void info(const char* fmt, const Args&... args)
        {
            if(logger_)
                logger_->info(fmt, args...);
        }

        template<typename... Args>
        void debug(const char* fmt, const Args&... args)
        {
            if(logger_)
                logger_->debug(fmt, args...);
        }

        template<typename... Args>
        void warn(const char* fmt, const Args&... args)
        {
            if(logger_)
                logger_->warn(fmt, args...);
        }

        template<typename... Args>
        void error(const char* fmt, const Args&... args)
        {
            if(logger_)
                logger_->error(fmt, args...);
        }

        template<typename... Args>
        void critical(const char* fmt, const Args&... args)
        {
            if(logger_)
                logger_->critical(fmt, args...);
        }

        static void shutdown()
        {
            spdlog::shutdown();
        }

    private:
        Log() = default;
        ~Log() = default;

        Log& operator=(const Log&) = delete;
        Log(const Log&) = delete;

        static void create_logger()
        {
            std::vector<spdlog::sink_ptr> sinks;

            if(logconf_.outmode == CONSOLE_ONLY || logconf_.outmode == BOTH)
            {
                auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();  
                sinks.push_back(console_sink);
            }
            if(logconf_.outmode == FILE_ONLY || logconf_.outmode == BOTH)
            {
                auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(logconf_.logfile_);
                sinks.push_back(file_sink);
            }

            spdlog::level::level_enum lenum = 
                logconf_.is_debug_ ? spdlog::level::trace : spdlog::level::info;
            
            if(logconf_.is_async_)
            {
                spdlog::init_thread_pool(logconf_.queue_size_, logconf_.thread_num_);
                logger_ = std::make_shared<spdlog::async_logger>(
                    "mainlog", sinks.begin(), sinks.end(), 
                    spdlog::thread_pool(), 
                    spdlog::async_overflow_policy::block);
            }
            else
            {
                logger_ = std::make_shared<spdlog::logger>(
                    "mainlog", sinks.begin(), sinks.end());
            }

            logger_->set_level(lenum);
            spdlog::set_default_logger(logger_);  // 重要:设置默认日志器
            logger_->set_pattern("[%Y-%m-%d %H:%M:%S.%e][%t][%-8l]%v");
        }

    private:
        static inline std::shared_ptr<spdlog::logger> logger_;
        static inline LogInfo_t logconf_;
        static inline std::atomic<bool> is_init_ = false;
    };
};

// // 使用简化版本的宏定义
// #define LOG_TRACE(...) LogModule::Log::GetInstance().trace(__VA_ARGS__)
// #define LOG_INFO(...) LogModule::Log::GetInstance().info(__VA_ARGS__)
// #define LOG_DEBUG(...) LogModule::Log::GetInstance().debug(__VA_ARGS__)
// #define LOG_WARN(...) LogModule::Log::GetInstance().warn(__VA_ARGS__)
// #define LOG_ERROR(...) LogModule::Log::GetInstance().error(__VA_ARGS__)
// #define LOG_CRITICAL(...) LogModule::Log::GetInstance().critical(__VA_ARGS__)

// 修改后宏定义:
#define LOG_TRACE(fmt, ...) LogModule::Log::GetInstance().trace("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) LogModule::Log::GetInstance().info("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) LogModule::Log::GetInstance().debug("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) LogModule::Log::GetInstance().warn("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) LogModule::Log::GetInstance().error("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_CRITICAL(fmt, ...) LogModule::Log::GetInstance().critical("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)

etcd-cpp-api精简版源代码参考

参考源代码函数参数

相关推荐
Jiezcode2 小时前
LeetCode 138.随机链表的复制
数据结构·c++·算法·leetcode·链表
前方一片光明2 小时前
Linux—升级openssh常见的问题与解决方案
linux·运维·服务器
那个什么黑龙江3 小时前
关于C++中的“类中的特殊成员函数”
开发语言·c++
siriuuus4 小时前
Linux rsyslog 日志服务及日志转发实践
linux·rsyslog
dawnsky.liu4 小时前
RHEL - 在离线的 RHEL 10 中部署 Lightspeed 命令行助手
linux·人工智能·ai
promising-w4 小时前
TYPE-C接口,其实有4种
linux·c语言·开发语言
云道轩4 小时前
在rocky linux 9.5上安装yq
linux·kubernetes
烦躁的大鼻嘎4 小时前
【Linux】深入探索多线程编程:从互斥锁到高性能线程池实战
linux·运维·服务器·开发语言·c++·算法·ubuntu
wdfk_prog4 小时前
`git rm --cached`:如何让文件“脱离”版本控制
大数据·linux·c语言·笔记·git·学习·elasticsearch