日期 | 作者 | 版本 | 备注 |
---|---|---|---|
2020-12-7 | dingbin | v1.0 | |
Paeony:Etcdv3-cpp-api 库简介
Paeony是作者开源的用C++语言实现成熟的etcd v3版本客户端API库。Github地址是:https://github.com/apollo008/paeony 。它达到稳定可靠的企业级应用效果。它在原始单纯etcdv3-cpp-api基础上,封装了很多面向负载均衡和连接重试的特性。它具有以下优势:
- 重试机制: 当etcd服务端不可连接或网络出现问题后,Paeony客户端会自动做出合理时间间隔的多次连接重试,并且兼顾重新连接的负载均衡,直到再次连接成功为止,保证系统不因为一个etcd server连接不可用而宕机;连接重试过程同步输出详细日志,帮助用户迅速跟踪定位Etcd服务连接状况异常原因,以快速解决etcd服务连接问题。整个重连过程用户无感知。
- 连接状态监控: Paeony初始化之后会一直对etcd连接进行监听,一旦发现连接状态发生变化将会作出相应的处理。
- Etcd客户端连接实例管理:Paeony会对etcdv3客户端到server集群的连接进行管理,并在需要的时候重建etcd连接实例,保证与etcd集群连接的可靠性。
- 采用作者开源成熟稳定的日志系统 tulip-log , Github地址:https://github.com/apollo008/tulip-log ,同步输出丰富的etcdv3客户端操作状态和连接状态日志信息,便于排查线上问题。
- 对etcdv3 客户端的watch和leaseKeepAlive 过程做了尤其增强支持,使得该etcdv3 客户端不会因为某个etcd server不可用或网络抖动导致watch失效或关键心跳节点丢失等严重问题。顽强稳定地支持适用各种网络环境和etcd servers 连接异常状况的watch 和leaseKeepAlive表现。
- 客户端全部接口都支持多线程同步访问,保证多线程操作访问下结果也能正确可靠。
- 支持Etcdv3版本事务Transaction操作。
- 结合demo示例:src/paeony/paeony/core/examples/hello_paeony.cpp, 接口设计便捷易用。
基于Paeony的Etcd v3 c++ api基本用法
项目的src/paeony/paeony/core/examples目录给出了常用的etcdv3 client使用示例。
具体来说,demo使用入口见 src/paeony/paeony/core/examples/hello_paeony.cpp代码示例:
#include "paeony/core/examples/paeony_demo.h"
PAEONY_USE_NAMESPACE;
STD_USE_NAMESPACE;
int main(int argc, char** argv) {
TLOG_CONFIG("logger.conf");
TLOG_DECLARE_AND_SETUP_LOGGER(HELLOPAEONY, MAIN);
EtcdDemoConnInfo info;
PaeonyDemo demo(info);
demo.DemoEtcdv3ClientGetPut();
demo.DemoEtcdv3Lease();
demo.DemoEtcdv3Watch();
demo.DemoEtcdv3Transaction();
TLOG_LOG_FLUSH();
TLOG_LOG_SHUTDOWN();
return 0;
}
具体的4种主要的Etcdv3 client 接口Demo参考文件:src/paeony/paeony/core/examples/paeony_demo.cpp
Etcdv3Client/Get/Put
/**
*@brief Method to demo etcdv3 client and Get/Put method
*@author [email protected]
*@date 2020/12/7, 上午1:14
*/
void PaeonyDemo::DemoEtcdv3ClientGetPut() {
Etcdv3ClientPtr client = std::make_shared(m_info.m_certificateContents,
m_info.m_user,
m_info.m_passwd,
m_info.m_allEndpoints,
time(NULL),
m_info.m_etctTestKey);
try {
while (true) {
paeony::EtcdRangeResponse getResponse = client->Get(m_info.m_etctTestKey, true, false,
false, 0, 3,
2, 50);
if (getResponse.IsOk()) {
TLOG_LOG(INFO,"Successfully get, value=[%s].",
(getResponse.m_rpcResp.kvs_size()>0?getResponse.m_rpcResp.kvs(0).value().c_str(): "") );
}
else {
TLOG_LOG(ERROR,"Failed to get,error_code is:[%d],operation failed,detail message:[%s]",
getResponse.GetErrorCode(), getResponse.GetErrorMsg().c_str() );
}
EtcdPutResponse putResponse = client->Put(m_info.m_etctTestKey + "/123","hello,paeony!!!",
0, false, 5, 2, 50);
if (putResponse.IsOk()) {
TLOG_LOG(INFO,"Successfully put key:[%s]", m_info.m_etctTestKey.c_str());
}
else {
TLOG_LOG(ERROR,"Failed to put, error_code is:[%d],operation failed,detail message:[%s]",
putResponse.GetErrorCode(),putResponse.GetErrorMsg().c_str() );
}
sleep(3);
}
}
catch (std::exception const & ex)
{
TLOG_LOG(ERROR,"Communication problem,details:[%s]", ex.what());
}
}
Watch
/**
*@brief Method used to demo Etcdv3 Watch interface
*@author [email protected]
*@date 2020/12/7, 上午1:15
*/
void PaeonyDemo::DemoEtcdv3Watch() {
Etcdv3ClientPtr client = std::make_shared(m_info.m_certificateContents,
m_info.m_user,
m_info.m_passwd,
m_info.m_allEndpoints,
time(NULL),
m_info.m_etctTestKey);
string key = "/paeony/TestWatch";
client ->Put(key,"0");
function watchCallback = [](EtcdWatchResponse response) {
PaeonyDemo::_logger->Log(tulip::TLOG_LEVELNO_ERROR,__FILE__,__LINE__,__FUNCTION__,
"got watch response, with response's events size:[%d] and if for the firstResponseCallback:[%s]",
response.m_rpcResp.events_size(), (response.m_bFirstResponse ? "true":"false"));
};
vector filtersVec;
EtcdWatchWorkerPtr watchWorker = make_shared(client,key,true,true,true,filtersVec,
watchCallback,100,30,4 * 1024 * 1024);
watchWorker->Start();
while(!watchWorker->IsStarted()){
TLOG_LOG(INFO, "wait EtcdWatchWorker start...");
usleep(10);
}
TLOG_LOG(INFO,"EtcdWatchWorker started......");
sleep(180);
TLOG_LOG(INFO, "=======================now terminate EtcdWatchWorker...");
watchWorker->Terminate();
TLOG_LOG(INFO,"EtcdWatchWorker terminate, wait done");
watchWorker->Join();
TLOG_LOG(INFO,"EtcdWatchWorker done.");
int remainTimeSecond = 10;
int t = 0;
while (remainTimeSecond - t * 2 > 0) {
TLOG_LOG(INFO,"Wait 10 seconds to exit Function:[%s], remain [%d] second..",
__FUNCTION__ , remainTimeSecond - t * 2);
++t;
sleep(2);
}
TLOG_LOG(INFO,"now exit function:[%s]", __FUNCTION__ );
}
Lease
/**
*@brief Method used to demo Etcdv3 Lease interface
*@author [email protected]
*@date 2020/12/7, 上午1:15
*/
void PaeonyDemo::DemoEtcdv3Lease() {
Etcdv3ClientPtr client = std::make_shared(m_info.m_certificateContents,
m_info.m_user,
m_info.m_passwd,
m_info.m_allEndpoints,
time(NULL),
m_info.m_etctTestKey);
int64_t ttl = 60;
EtcdLeaseGrantResponse lgr;
do {
lgr = client ->LeaseGrant(ttl,0,3,2,50);
if (lgr.IsOk()) {
break;
}
else {
TLOG_LOG(ERROR,"Lease grant errCode:[%d],errMsg:[%s]",
lgr.GetErrorCode(),lgr.GetErrorMsg().c_str());
}
sleep(1);
} while(true);
client ->Put("/paeony/TestLeaseKeepAlive","0",lgr.m_rpcResp.id());
function reLeaseGrantCallback = [client](LeaseGrantResponse response) {
time_t tm = time(NULL);
ostringstream oss;
oss <Put("/paeony/TestLeaseKeepAlive",oss.str(),response.id());
PaeonyDemo::_logger->Log(tulip::TLOG_LEVELNO_INFO,__FILE__,__LINE__,__FUNCTION__,
"got reLeaseGrant response, with id:[%ld] and ttl:[%ld]", response.id(),response.ttl());
};
EtcdLeaseKeepAliveWorkerPtr leaseKeepAliveWorker = make_shared (client,
lgr.m_rpcResp.id(),lgr.m_rpcResp.ttl(),reLeaseGrantCallback,100,ttl + 2,2 * 1024 * 1024);
leaseKeepAliveWorker->Start();
while(!leaseKeepAliveWorker->IsStarted()){
TLOG_LOG(INFO,"wait LeaseKeepAliveWorker start");
usleep(10);
}
TLOG_LOG(INFO,"LeaseKeepAliveWorker started");
sleep(60 * 60);
TLOG_LOG(INFO,"=======================now terminate LeaseKeepAliveWorker...");
leaseKeepAliveWorker->Terminate();
TLOG_LOG(INFO,"LeaseKeepAliveWorker terminate, wait done");
leaseKeepAliveWorker->Join();
TLOG_LOG(INFO,"LeaseKeepAliveWorker done.");
int remainTimeSecond = 3 * 60;
int t = 0;
while (remainTimeSecond - t * 5 > 0) {
TLOG_LOG(INFO,"Wait 3 minute to exit Function:[%s], remain [%d] second...",
__FUNCTION__,remainTimeSecond - t * 5);
++t;
sleep(5);
}
TLOG_LOG(INFO,"now exit function:[%s]",__FUNCTION__);
}
Transaction
/**
*@brief Method used to demo Etcdv3 transaction interface
*@author [email protected]
*@date 2020/12/7, 上午1:15
*/
void PaeonyDemo::DemoEtcdv3Transaction() {
string key = "/paeony/TestTxn";
Compare compare;
compare.set_result(etcdserverpb::Compare::CompareResult::Compare_CompareResult_EQUAL);
compare.set_target(etcdserverpb::Compare::CompareTarget::Compare_CompareTarget_VERSION);
compare.set_key(key);
compare.set_version(0);
TransactionRequestPtrVec successTxnRequestsVec;
TransactionRequestPtr put1 =
make_shared(Etcdv3Client::s_ConstructPutRequest(key,"1"));
successTxnRequestsVec.push_back(put1);
TransactionRequestPtr range1 =
make_shared(Etcdv3Client::s_ConstructRangeRequest(key));
successTxnRequestsVec.push_back(range1);
TransactionRequestPtr put2 =
make_shared(Etcdv3Client::s_ConstructPutRequest(key + "1","2"));
successTxnRequestsVec.push_back(put2);
TransactionRequestPtr range2 =
make_shared(Etcdv3Client::s_ConstructRangeRequest(key + "1"));
successTxnRequestsVec.push_back(range2);
TransactionRequestPtrVec failureTxnRequestsVec;
TransactionRequestPtr put1_ =
make_shared(Etcdv3Client::s_ConstructPutRequest(key,"1_"));
failureTxnRequestsVec.push_back(put1_);
TransactionRequestPtr range1_ =
make_shared(Etcdv3Client::s_ConstructRangeRequest(key));
failureTxnRequestsVec.push_back(range1_);
TransactionRequestPtr put2_ =
make_shared(Etcdv3Client::s_ConstructPutRequest(key+"2","2_"));
failureTxnRequestsVec.push_back(put2_);
TransactionRequestPtr range2_ =
make_shared(Etcdv3Client::s_ConstructRangeRequest(key +"2"));
failureTxnRequestsVec.push_back(range2_);
Etcdv3ClientPtr client = std::make_shared(m_info.m_certificateContents,
m_info.m_user,
m_info.m_passwd,
m_info.m_allEndpoints,
time(NULL),
m_info.m_etctTestKey);
EtcdTxnResponse txnResponse =
client->Transaction(compare,successTxnRequestsVec,failureTxnRequestsVec,8,3,50);
if (txnResponse.IsOk()) {
TLOG_LOG(INFO, "Succeed in executing transaction operation whose txnResponse's succeeded is:[%s]",
(txnResponse.m_rpcResp.succeeded() ? "true":"false"));
}
else {
TLOG_LOG(ERROR,"Failed to execute transaction operation with errorCode:[%d] and errorMsg:[%s]",
txnResponse.GetErrorCode(), txnResponse.GetErrorMsg().c_str() );
}
}
Tulip-log的配置文件示例参考:src/paeony/paeony/core/examples/logger.conf
编译和安装
目前支持类Unix环境下编译安装,Paeony项目依赖的第三方库有:
- protobuf
- grpc
- openssl
- tulip-log
编译安装paeony之前先要安装以上4个依赖库。
具体编译和安装方法如下:
git clone https://github.com/apollo008/paeony.git paeony.git
cd paeony.git
vim CMakeLists.txt 修改第8行:
原来是:set(PAEONY_DEPEND_PREFIX_DIR /path/to/install/share)
将/path/to/install/share 替换为 安装上面4个目标依赖库的路径,比如${HOME}/local,注意确保该路径有写权限。
修改之后后续不要再改变该路径。
mkdir build-dir
cd build-dir
#安装依赖
cmake -DENABLE_BUILD_SHARE=ON ../src
执行完以上这一步即可完成依赖库。注意不需要再执行make 和make install了。
接下来安装paeony
#安装paeony
首先保持根目录下CMakeLists.txt第8行修改的内容不再改变;
cd build-dir
rm -rf *
cmake -DCMAKE_INSTALL_PREFIX=/path/to/install ../src
make -j10
make install
执行完以上,即可在/path/to/install目录下生成bin、lib、include3个目录。其中lib目录是libpaeony.so; bin目录下是hello_paeony可执行程序; include目录是paeony库的头文件。
注意:运行demo程序hello_paeony时,需要在当前目录放置tulip-log的日志配置文件logger.conf ,同时该目录下要已经创建好logs 目录供输出文件名滚动的日志文件。
其它
相关细节或其它未尽事宜,可联系 [email protected] 探讨咨询。