RPC分布式网络通信框架(三)—— 服务配置中心Zookeeper模块

文章目录

  • 一、使用Zookeeper的意义
  • 二、Zookeeper基础
    • 1 文件系统
    • 2 通知机制
    • 3 原生zkclient API存在的问题
    • 4 服务配置中心Zookeeper模块
  • 三、Zk类实现
    • ==Start方法==
    • 创建节点、get节点值方法
  • 四、框架应用
    • rpc提供端框架
    • rpc调用端(客户端)框架
  • 总结


一、使用Zookeeper的意义

分布式系统存在的问题:
为了支持高并发,每个客户端都保存了一份服务提供者的列表。但是如果列表有更新,想要得到最新的URL列表(rpc服务的ip和端口号),必须要手动更新配置文件,很不方便。
如图所示,实例3挂掉了,但是列表并没有得到更新。
在这里插入图片描述
故需要动态的更新URL列表,由此引入Zookeeper服务配置中心。

二、Zookeeper基础

zookeeper是为分布式应用提供一致性协调服务的中间件

本质:类似linux的文件系统
调用者真实运行的情况下其实并不知道自已想要调用的函数在哪台机器上(不知道ip和端口)
所以调用者需要在调用前先去服务配置中心去问一下想调用API所在机器上的ip和端口。
同时他还提供全局分布式锁,起到协调控制管理各个分布式节点的功能。

1 文件系统

Zookeeper提供一个多层级的节点命名空间(节点称为znode)。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存放数据,目录节点不能存放数据。
Zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M。

一个节点可以存储1Mb的数据
在这里插入图片描述

znode节点操作指令:

  • ls 罗列节点
  • get 查看节点
  • create 创建节点
  • set 修改节点的值
  • delete 删除节点(要先删子节点)

2 通知机制

client端会对某个znode建立一个watcher事件,当该znode发生变化时,这些client会收到zk的通知,然后client可以根据znode变化来做出业务上的改变等。

zk的watcher机制
客户端通过watcher机制监听zk节点(变化)
客户端维护的map,键是节点的名字,值是节点的内容(ip+端口)
通过通知和回调机制,由zk主动向客户端汇报节点的变化(节点死掉、新节点加入)

3 原生zkclient API存在的问题

1、设置监听的watcher是一次性的
2、znode 节点只存储简单的byte字节数组(如果想存对象,需要转换对象生成字节数组)
注意:
原生zkclient会自动发送心跳消息(维护session),源码会在1/3的Timeout时间发送ping心跳。
抓包验证:
sudo tcpdump -I lo port 2181 // 抓2181这个端口的所有包

4 服务配置中心Zookeeper模块

所以,需要一个项目注册节点中心配置,维护session会话相当于检测tcp连接的心跳消息,以此来确定链接是否断开。
项目中的由每个服务创建的节点为临时性节点
总结:每个rpc服务器端都会向zookeeper服务注册配置中心 传入 ip + port + 服务名字,客户端进程查询获得ip+port
在这里插入图片描述

三、Zk类实现

封装的ZkClient客户端类头文件

#pragma onceclass ZkClient
{
public:ZkClient();~ZkClient();void Start();  // zkclient启动连接zkservervoid Create(const char *path, const char *data, int datalen, int state=0); // 在zkserver上根据指定的path创建znode节点std::string GetData(const char *path);  // 根据参数指定的znode节点路径,或者znode节点的值
private:zhandle_t *m_zhandle;  // zk的客户端句柄
};

构造、析构实现:

ZkClient::ZkClient() : m_zhandle(nullptr)
{
}ZkClient::~ZkClient()
{if (m_zhandle != nullptr){zookeeper_close(m_zhandle); // 关闭句柄,释放资源  MySQL_Conn}
}

Start方法

首先从配置文件中读取zookeeper客户端ip和port。

std::string host = MprpcApplication::GetInstance().GetConfig().Load("zookeeperip");
std::string port = MprpcApplication::GetInstance().GetConfig().Load("zookeeperport");
std::string connstr = host + ":" + port;

使用zookeeper_mt的多线程版本

zk的客户端提供了三个线程
1、API调用线程:zookeeper_init,直接导致下面两个线程的开辟
2、网络I/O收发线程:pthread_create(底层为poll,且会在1/3的Timeout时间发送ping心跳保持与zkserver的通信)
3、watcher回调线程:pthread_create当zkclient接收zkserver的响应后,zkserver给zkclient通知
zookeeper是异步连接过程,需要绑定一个全局回调函数global_watcher(新线程连接)

m_zhandle = zookeeper_init(connstr.c_str(), global_watcher, 30000, nullptr, nullptr, 0);
之后检查创建的m_zhandle是否为空指针

输入:(127.0.0.1: 2181, 回调函数, session超时时间30s, null, null, 0)

注:

  • zk的端口号2181。
  • 上述代码只是成功创建句柄资源,并不代表zkserver的连接成功与否。
  • 全局回调函数global_watcher决定是否连接成功。

全局的watcher观察器:

// 全局的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);}}
}

直到收到state == ZOO_CONNECTED_STATE消息才算连接成功。这时sem信号量置1。
也就说sem的值是由全局观察者在连接状态变为已连接时通过调用sem_post()或类似的函数来增加的。

Start方法中等待信号量sem为1,连接成功,打印信息。

sem_t sem;
sem_init(&sem, 0, 0);
zoo_set_context(m_zhandle, &sem);sem_wait(&sem);  // 阻塞,直到sem为1
std::cout << "zookeeper_init success!" << std::endl;

创建节点、get节点值方法

void ZkClient::Create(const char *path, const char *data, int datalen, int state)
{char path_buffer[128];int bufferlen = sizeof(path_buffer);int flag;// 先判断path表示的znode节点是否存在,如果存在,就不再重复创建了flag = zoo_exists(m_zhandle, path, 0, nullptr);if (ZNONODE == flag) // 表示path的znode节点不存在{// 创建指定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);}}
}// 根据指定的path,获取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;}
}

四、框架应用

rpc提供端框架

首先调用Start方法。
之后将当前rpc节点上要发布的服务全部注册到zk上,让rpc调用端可以从zk上发现服务。

  • 先创建永久父节点/UserServiceRpc
  • 再根据提供端维护的rpc方法map表,创建临时子节点/UserServiceRpc/Login(Login方法…),也就是将要发布的服务全部注册到zk上。
// session timeout   30s     zkclient 网络I/O线程  1/3 * timeout 时间发送ping消息
ZkClient zkCli;
zkCli.Start();
// service_name为永久性节点    method_name为临时性节点
for (auto &sp : m_serviceMap) 
{// /service_name   /UserServiceRpcstd::string service_path = "/" + sp.first;zkCli.Create(service_path.c_str(), nullptr, 0);for (auto &mp : sp.second.m_methodMap){// /service_name/method_name   /UserServiceRpc/Login 存储当前这个rpc服务节点主机的ip和portstd::string method_path = service_path + "/" + mp.first;char method_path_data[128] = {0};sprintf(method_path_data, "%s:%d", ip.c_str(), port);// ZOO_EPHEMERAL表示znode是一个临时性节点zkCli.Create(method_path.c_str(), method_path_data, strlen(method_path_data), ZOO_EPHEMERAL);}
}

rpc调用端(客户端)框架

首先,同样是调用Start方法:

ZkClient zkCli;
zkCli.Start();

然后,CallMethod中通过zk获取ip:port。也就是说,通过要调用方法的名称(Login)在zk的节点中寻找对应的ip和port。

//  /UserServiceRpc/Login
std::string method_path = "/" + service_name + "/" + method_name;
// 127.0.0.1:8000
std::string host_data = zkCli.GetData(method_path.c_str());

因为读取的地址是host_data = 127.0.0.1:8000,所以将其分离:

if (host_data == "")
{controller->SetFailed(method_path + " is not exist!");return;
}
int idx = host_data.find(":");
if (idx == -1)
{controller->SetFailed(method_path + " address is invalid!");return;
}
std::string ip = host_data.substr(0, idx);
uint16_t port = atoi(host_data.substr(idx+1, host_data.size()-idx).c_str()); 

总结

Zookeeper功能如下:

  • master节点选举, 主节点down掉后, 从节点就会接手工作, 并且保证这个节点是唯一的,这也就是所谓首脑模式,从而保证我们集群是高可用的
  • 统一配置文件管理, 即只需要部署一台服务器, 则可以把相同的配置文件同步更新到其他所有服务器, 此操作在云计算中用的特别多(例如修改了redis统一配置)
  • 数据发布与订阅, 类似消息队列MQ
  • 分布式锁,分布式环境中不同进程之间争夺资源,类似于多进程中的锁
  • 集群管理, 保证集群中数据的强一致性

服务配置中心用法:

  • 每个rpc服务器端都会向zookeeper服务注册配置中心 传入 ip + port + 服务名字 客户端进程查询获得ip+port

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/681.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flutter开发实战-生日等日期选择器DatePicker

flutter开发实战-生日等日期选择器DatePicker 在开发遇到设置生日等信息需要选择日期&#xff0c;这里用到的是CupertinoDatePicker iOS效果的日期、时间选择器 一、效果图 运行后效果图如下 二、代码实现 我们需要调用底部弹窗显示 //显示底部弹窗static void bottomShe…

Python实现PSO粒子群优化算法优化BP神经网络回归模型(BP神经网络回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 PSO是粒子群优化算法&#xff08;Particle Swarm Optimization&#xff09;的英文缩写&#xff0c;是一…

H3C-Cloud Lab实验-三层交换机实验

实验拓扑图&#xff1a; 实验需求&#xff1a; 1. 按照图示为 PC2 和 PC3 配置 IP 地址和网关 2. PC2 属于 Vlan10&#xff0c;PC3 属于 Vlan20&#xff0c;在三层交换机上配置 Vlanif 三层接口实现 Vlan10 和 Vlan20 三层互通 3. PC2 和 PC3 可以互通 实验步骤&#xff1a…

WEB:Web_php_include(多解)

背景知识 文件包含 file://协议 php://协议 data://协议 了解strstr函数 题目 <?php show_source(__FILE__); echo $_GET[hello]; /*可以构造payload*/ $page$_GET[page]; while (strstr($page, "php://")) { /*strstr()函数*/$pagestr_replace("php://&q…

Django实现接口自动化平台(十一)项目模块Projects序列化器及视图【持续更新中】

相关文章&#xff1a; Django实现接口自动化平台&#xff08;十&#xff09;自定义action names【持续更新中】_做测试的喵酱的博客-CSDN博客 本章是项目的一个分解&#xff0c;查看本章内容时&#xff0c;要结合整体项目代码来看&#xff1a; python django vue httprunner …

在外Windows远程连接MongoDB数据库【无公网IP】

文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 转载自cpolar极点云文章&#xff1a;公网远程…

论文阅读-2:基于深度学习的大尺度遥感图像建筑物分割研究

一、该网络中采用了上下文信息捕获模块。通过扩大感受野&#xff0c;在保留细节信息的同时&#xff0c;在中心部分进行多尺度特征的融合&#xff0c;缓解了传统算法中细节信息丢失的问题&#xff1b;通过自适应地融合局部语义特征&#xff0c;该网络在空间特征和通道特征之间建…

docker服务启动过程分析

How docker.service start&#xff1f; just by ref 我们先了解docker的各个核心组件的介绍 runc&#xff1a;runc实现了容器的底层功能&#xff0c;例如创建、运行等。runc通过调用内核接口为容器创建和管理cgroup、namespace等Linux内核功能&#xff0c;来实现容器的核心特…

PyTorch预训练和微调:以VGG16为例

文章目录 预训练和微调代码测试结果参考来源 预训练和微调代码 数据集&#xff1a;CIFAR10 CIFAR-10数据集由10类32x32的彩色图片组成&#xff0c;一共包含60000张图片&#xff0c;每一类包含6000图片。其中50000张图片作为训练集&#xff0c;10000张图片作为测试集。数据集介…

SolidUI AI生成可视化,0.1.0版本模块划分以及源码讲解

1.背景 随着文本生成图像的语言模型兴起&#xff0c;SolidUI想帮人们快速构建可视化工具&#xff0c;可视化内容包括2D,3D,3D场景&#xff0c;从而快速构三维数据演示场景。SolidUI 是一个创新的项目&#xff0c;旨在将自然语言处理&#xff08;NLP&#xff09;与计算机图形学相…

【微信小程序-uniapp】CustomDialog 居中弹窗组件

1. 效果图 2. 组件完整代码 <template><uni-popup :ref="ref" type="center" @change

Ubuntu下配置Redis哨兵集群

目录 准备实例和配置 启动哨兵集群 测试配置 搭建一个三节点形成的Sentinel集群&#xff0c;来监管Redis主从集群。 三个sentinel哨兵实例信息如下&#xff1a; 节点IPPORTs1192.168.22.13527001s2192.168.22.13527002s3192.168.22.13527003 准备实例和配置 要在同一台虚…

组合式API

文章目录 前言了解组合式API简单类型 ref封装对象类型 user.name子组件数组类型 reactive封装 组合式 API 基础练习基础练习优化 前言 Vue 3 的组合式 API&#xff08;Composition API&#xff09;是一组函数和语法糖&#xff0c;用于更灵活和可组合地组织 Vue 组件的代码逻辑…

leetcode 538. 把二叉搜索树转换为累加树

2023.7.16 这道题利用中序遍历&#xff08;右中左&#xff09;的操作不断修改节点的值即刻&#xff0c;直接看代码&#xff1a; class Solution { public:TreeNode* convertBST(TreeNode* root) {stack<TreeNode*> stk;//前面的累加值int pre_value 0;TreeNode* cur r…

review回文子串

给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。 class Solution {List<List<String>> lists new ArrayList<>(); // 用于存储所有可能…

【数据挖掘】时间序列教程【二】

2.4 示例:颗粒物浓度 在本章中,我们将使用美国环境保护署的一些空气污染数据作为运行样本。该数据集由 2 年和 5 年空气动力学直径小于或等于 3.2017 \(mu\)g/m\(^2018\) 的颗粒物组成。 我们将特别关注来自两个特定监视器的数据,一个在加利福尼亚州弗雷斯诺,另一个在密…

图片文字对齐 图片文字居中对齐

方法一: 用 vertical-align: middle; <view class="container"><view class="search"><image src="../../images/icon/search.png" alt="" /><text class="tex">搜索</text></view>&…

透彻!127.0.0.1和0.0.0.0之间的区别总算听明白了!

参考视频&#xff1a;透彻&#xff01;127.0.0.1和0.0.0.0之间的区别总算听明白了&#xff01;_哔哩哔哩_bilibili 0.0.0.0不是一个ip地址&#xff0c;而是一个通配符&#xff0c;通配当前主机上面所有的网卡&#xff08;包括虚拟网卡&#xff09;。

深度学习环境安装|PyCharm,Anaconda,PyTorch,CUDA,cuDNN等

本文参考了许多优秀博主的博客&#xff0c;大部分安装步骤可在其他博客中找到&#xff0c;鉴于我本人第一次安装后&#xff0c;时隔半年&#xff0c;我忘记了当时安装的许多细节和版本信息&#xff0c;所以再一次报错时&#xff0c;重装花费了大量时间。因此&#xff0c;我觉得…

Profibus DP主站转Modbus TCP网关profibus从站地址范围

远创智控YC-DPM-TCP网关。这款产品在Profibus总线侧实现了主站功能&#xff0c;在以太网侧实现了ModbusTcp服务器功能&#xff0c;为我们的工业自动化网络带来了全新的可能。 远创智控YC-DPM-TCP网关是如何实现这些功能的呢&#xff1f;首先&#xff0c;让我们来看看它的Profib…