C++封装的clickhouse客户端连接池

865 阅读1分钟

使用了SPD_LOG第三方日志库

clickhousePool.h

#include <iostream>
#include <unordered_map>
#include <vector>
#include <clickhouse/client.h>
#include <mutex>

struct ClickhousePoolConfig {
    std::string name; //名称
    std::vector<std::string> hostVec; //服务的地址
    int poolSize; // 连接池大小
    std::string user;  //用户名
    std::string password; //密码
};

//每次获取到一个client,使用完以后都需要释放资源
struct ClientWrapper {
    clickhouse::Client *pClient;
    std::mutex mutex_; //临界资源
};

class ClickhousePool {
public:
    //创建单例,第一次获取的时候创建
    static ClickhousePool &GetInstance() {
        static ClickhousePool instance_;
        return instance_;
    }

    void test() {
        printf("内存地址: %p\n", this);
    }

    bool SetPoolByName(const ClickhousePoolConfig &chConfig);

    //通过名称随机获取一个连接
    ClientWrapper *DB(const std::string &name);

protected:
    ClickhousePool() = default;

    virtual ~ClickhousePool() = default;

private:
private:
    ClickhousePool(const ClickhousePool &) = delete;

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

    //每个clickhouse服务建立一个vector连接池
    std::unordered_map<std::string, std::vector<ClientWrapper *>> clientPoolMap;
};


clickhousePool.cpp

#include "ClickhousePool.h"
#include <chrono>
#include <thread>
#include <chrono>
#include "../utils/utils.h"
#include "spdlog/spdlog.h"

bool ClickhousePool::SetPoolByName(const ClickhousePoolConfig &chConfig) {
    //创建连接池
    if (clientPoolMap.find(chConfig.name) != clientPoolMap.end()) {
        SPDLOG_ERROR("{},连接池中已经有此名称的连接", chConfig.name);
        return false;
    }
    auto &poolVec = clientPoolMap[chConfig.name];
    poolVec.reserve(chConfig.poolSize);
    //集群中多个服务建立连接,每台clickhouse机器建立poolSize个连接,总计连接=(poolSize * hostCount)
    for (auto host : chConfig.hostVec) {
        for (int i = 0; i < chConfig.poolSize; i++) {
            try {
                clickhouse::Client *pClient =
                        new clickhouse::Client(clickhouse::ClientOptions()
                                                       .SetHost(host)
                                                       .SetUser(chConfig.user)
                                                       .SetPassword(chConfig.password)
                                                       .SetPingBeforeQuery(true)
                                                       .SetCompressionMethod(clickhouse::CompressionMethod::LZ4));
                ClientWrapper *pClientWrapper = new ClientWrapper;
                pClientWrapper->pClient = pClient;
                poolVec.push_back(pClientWrapper);
            } catch (std::exception &e) {
                std::cerr << "创建clickhouse连接异常," << e.what() << std::endl;
                SPDLOG_ERROR("{},创建clickhouse连接异常", chConfig.name);
                return false;
            }
            SPDLOG_INFO("{},创建clickhouse连接,{},{}", chConfig.name, host, i);
        }
    }
    return true;
}

//每次获取一个资源,都需要释放,记得在应用层unlock
ClientWrapper *ClickhousePool::DB(const std::string &name) {
    if (clientPoolMap.find(name) == clientPoolMap.end()) {
        SPDLOG_ERROR("{}没有找到对应的连接池,请检查是否创建", name);
        return nullptr;
    }
    auto &clientVec = clientPoolMap[name];
    //从连接池中选择一个空闲的连接,尝试获取3次
    int reTryCount = 3;
    while (reTryCount) {
        //根据时间,随机选择一台机器服务
        int64_t nanoSecond = risk::util::getNanoSecond();
        auto pos = nanoSecond % clientVec.size();
        auto pClient = clientVec[pos];
        //尝试加锁,加锁成功返回
        if (pClient->mutex_.try_lock()) {
            SPDLOG_INFO("获取了第{}个连接", pos);
            return pClient;
        }
        reTryCount--;
        //获取失败等待5毫秒再获取
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
    }
    SPDLOG_ERROR("{}服务资源忙,请开发人员增加此资源的数量", name);
    return nullptr;
}

demo使用

 //初始化 (连接池为单例模式)
ClickhousePoolConfig clickhousePoolConfig;
clickhousePoolConfig.name = "clikchouse-cluster-server-01";
clickhousePoolConfig.poolSize = 5;
clickhousePoolConfig.hostVec = {"host1","host2","host3"}; //支持多个服务地址,每个地址创建poolSize个连接
clickhousePoolConfig.user = "username";
clickhousePoolConfig.password = "password";
if (false == ClickhousePool::GetInstance().SetPoolByName(clickhousePoolConfig)) {
  std::cerr << "初始化clickhouse失败\n";
  exit(-1);
}

//获取
ClientWrapper *client = ClickhousePool::GetInstance().DB("clikchouse-cluster-server-01");
if (!client) {
  SPDLOG_ERROR("获取连接{}失败", riskMonitorClickhouseServer01);
  return -1;
}

//使用
client->pClient->Select(sqlStr, func);

//记得释放锁,归还资源
client->mutex_.unlock();