一、安装以及相关配置
bash
//安装
sudo apt update
sudo apt install wget curl -y
wget https://dlcdn.apache.org/zookeeper/zookeeper-3.8.5/apache-zookeeper-3.8.5-bin.tar.gz
//然后要配置java环境
# 1. 安装JDK8(ZooKeeper 3.8.5推荐)
sudo apt update && sudo apt install openjdk-8-jdk -y
# 2. 配置全局JAVA_HOME(确保ZooKeeper后台进程能读取)
echo "export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64" | sudo tee -a /etc/profile
echo "export PATH=\$JAVA_HOME/bin:\$PATH" | sudo tee -a /etc/profile
source /etc/profile
# 3. 验证Java环境
java -version # 输出jdk1.8.x即成功
解压完后,
1、进入conf目录
2、将zoo_sample.cfg改为zoo.cfg
3、进入zoo.cfg
4、将dataDir修改一个路径,因为/tmp是临时文件
5、再进入bin目录,执行./zkServer.sh start
就成功了
二、znode节点和ZK客户端常用命令
1、znode节点存储格式


2、心跳消息
若客户端/服务端未在超时前发送心跳,ZooKeeper 会销毁其临时节点
3、客户端常用命令
执行./zkCli.sh
常用的命令有ls,get,create,set,delete


三、zk的watcher机制
允许客户端向服务器注册监听特定节点(znode)的变化。当被监听的节点发生改变时(如数据更新、节点删除、创建等),ZooKeeper 服务器会主动向客户端发送通知。
四、应用实践
1、zookeeper.h
cpp
#pragma once
#include<semaphore.h>
#include<zookeeper/zookeeper.h>
#include<string>
class ZkClient
{
public:
ZkClient();
~ZkClient();
//启动连接zkserver
void Start();
//在zkserver上根据path创建znode节点
void Create(const char* path, const char* data, int datalen, int state=0);//默认0永久性
//根据参数指定的path,获取数据,并把数据保存在data中
std::string GetData(const char* path);
private:
// zk客户端句柄
zhandle_t* m_zhandle;
};
2、zookeeperutil.cc
cpp
#include"zookeeperutil.h"
#include"mprpcapplication.h"
#include<semaphore.h>
#include<iostream>
//全局的watcher观察器 zkserver给zkclient的通知
void global_watcher(zhandle_t *zh,int type,int state,const char *path,void *watcherCtx)
{
if(type==ZOO_SESSION_EVENT)//回调的消息类型是和会话相关的消息类型
{
if(state==ZOO_CONNECTED_STATE)//zkclient和zkserver链接成功
{
sem_t *sem=(sem_t*)zoo_get_context(zh);
sem_post(sem);//信号量资源加一
}
}
}
ZkClient::ZkClient():m_zhandle(nullptr)
{}
ZkClient::~ZkClient()
{
if(m_zhandle!=nullptr)
{
zookeeper_close(m_zhandle);//关闭句柄释放资源
}
}
//zkclinet启动链接zkserver
void ZkClient::Start()
{
//加载zk的IP和端口号,默认为2181
std::string host=MprpcApplication::GetInstance().GetConfig().Load("zookeeperip");
std::string port=MprpcApplication::GetInstance().GetConfig().Load("zookeeperport");
std::string connstr=host+":"+port;
//调用原生API,端口与IP,回调函数,会话超时时间
/*
zookeeper_mt:多线程版本
zookeeper的API客户端程序提供了三个线程
API调用线程
网络I/O线程:专门在一个线程里处理网络I/O
watcher回调线程
*/
m_zhandle=zookeeper_init(connstr.c_str(),global_watcher,30000,nullptr,nullptr,0);
if(nullptr==m_zhandle)
{
std::cout<<"zookeeper_init error!"<<std::endl;
exit(EXIT_FAILURE);
}
sem_t sem;
sem_init(&sem,0,0);//初始化资源为0
zoo_set_context(m_zhandle,&sem);//设置上下文,添加额外信息
sem_wait(&sem);
std::cout<<"zookeeper_init success!"<<std::endl;
}
//在zkserver上根据指定的path创建znode节点
void ZkClient::Create(const char *path,const char *data,int datalen,int state)
{
char path_buffer[128];
int bufferlen=sizeof(path_buffer);
int flag;
//检查该节点是否存在
flag=zoo_exists(m_zhandle,path,0,nullptr);
if(ZNONODE==flag)//该节点并不存在
{
//创建指定path的znode节点
flag=zoo_create(m_zhandle,path,data,datalen,&ZOO_OPEN_ACL_UNSAFE,state,path_buffer,bufferlen);
if(flag==ZOK)
{
std::cout<<"znode create success... path:"<<path<<std::endl;
}
else
{
std::cout<<"flag:"<<flag<<std::endl;
std::cout<<"znode create error... path:"<<path<<std::endl;
exit(EXIT_FAILURE);
}
}
}
//传入参数指定的znode节点路径,获取znode节点的值
std::string ZkClient::GetData(const char *path)
{
char buffer[64];
int bufferlen=sizeof(buffer);
int flag=zoo_get(m_zhandle,path,0,buffer,&bufferlen,nullptr);//获取信息与返回值
if(flag!=ZOK)//如果获取失败
{
std::cout<<"get znode error... path:"<<path<<std::endl;
return "";
}
else
{
//获取成功
return buffer;
}
}
3、rpcprovider.cc修改
添加
cpp
//把当前rpc节点上要发布的服务全部注册到zk上,让客户端可以从zk上找到这个rpc节点
//session timeout 1/3的timeout时间,会向zk发送ping
ZkClient zkclient;
zkclient.Start();
for(auto &sp : m_serviceMap){
std::string service_path = "/"+sp.first;
zkclient.Create(service_path.c_str(),nullptr,0);
for(auto &mp : sp.second.m_methodMap){
std::string method_path = service_path+"/"+mp.first;
char method_path_data[128] = {0};
sprintf(method_path_data,"%s:%d",ip.c_str(),port);
zkclient.Create(method_path.c_str(),method_path_data,strlen(method_path_data),ZOO_EPHEMERAL);
}
}
4、mprpcchannel.cc修改
将原来直接从配置文件读的ip和port优化,从zk获取
cpp
//设置服务端的ip和port
// std::string ip = MprpcApplication::GetInstance().GetConfig().Load("rpcserverip");
// uint16_t port = atoi(MprpcApplication::GetInstance().GetConfig().Load("rpcserverport").c_str());
ZkClient zkClient;
zkClient.Start();
std::string method_path = "/"+service_name + "/" + method_name;
std::string host_data = zkClient.GetData(method_path.c_str());
if(host_data.empty()){
controller->SetFailed("zkClient get data failed!");
return;
}
int idx = host_data.find(":");
if(idx == -1){
controller->SetFailed("zkClient get data failed!");
return;
}
std::string ip = host_data.substr(0,idx);
uint16_t port = atoi(host_data.substr(idx+1,host_data.size()-idx).c_str());
五、编译脚本
bash
#!/bin/bash
set -e
rm -rf `pwd`/build/*
cd `pwd`/build &&
cmake .. &&
make
cd ..
cp -r `pwd`/src/include `pwd`/lib