关于zookeeper的具体介绍 优化的点可以在于zookeeper吗?
如何安装使用?
#include <zookeeper/zookeeper.h>
1、先配置java环境JDK,因为需要用java编译;
2、下载zk源码,解压;
3、重命名配置文件zoo_sample.cfg
4.启动zookeeper,检测是否成功启动,用zookeeper客户端连接下服务端
安装好之后如何用在C++客户端呢?zk是有C风格的API的,我们可以自定义类实现C++的。
首先要在定义类的时候包含zookeeper头文件,里面包含了zk常用的接口。然后链接时要指定链接zk_mt库(多客户端)
服务端调用代码:生成一个zk类,开启,发布节点(包括服务和方法,服务是永久节点,方法是临时节点,还会加ip+port)
//把当前rpc节点上要发布的服务全部注册到zk上面,让rpc client可以从zk上发现服务
ZookeeperClient zk_client;
zk_client.start();
//在配置中心中创建节点
for (auto &sp : service_map_)
{
string service_path = "/" + sp.first;
zk_client.create(service_path.c_str(), nullptr, 0);
for (auto &mp : sp.second.method_map_)
{
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时候临时性节点
zk_client.create(method_path.c_str(), method_path_data, strlen(method_path_data), ZOO_EPHEMERAL);
}
}
客户端:只要使用get_data方法就可以实现找到服务器的ip+port
//获取ip和port
ZookeeperClient zk_client;
zk_client.start();
string method_path = "/" + service_name + "/" + method_name;
string host_data = zk_client.get_data(method_path.c_str());
这里的zookeeper类如下:主要是定义了开始函数,创建节点函数和获取数据函数,当然还有一个zk句柄
#pragma once
#include <semaphore.h>
#include <zookeeper/zookeeper.h>
#include <string>
using namespace std;
class ZookeeperClient
{
public:
ZookeeperClient();
~ZookeeperClient();
//启动连接--》zkserver
void start();
//在zkserver 根据指定的path创建znode节点
void create(const char *path, const char *data, int datalen, int state = 0);
//根据参数指定的znode节点路径,获取znode节点的值
string get_data(const char *path);
private:
//zk的客户端句柄
zhandle_t *zhandle_;
};
其中构造函数就是给句柄初始化NULL,析构函数就是释放句柄 zookeeper_close(zhandle_);
启动zk
void ZookeeperClient::start()
{
string host = RpcApplication::get_instance().get_configure().find_load("zookeeper_ip");
string port = RpcApplication::get_instance().get_configure().find_load("zookeeper_port");
string con_str = host + ":" + port;
cout << con_str << endl;
zhandle_ = zookeeper_init(con_str.c_str(), global_watcher监视点函数,功能就是看看连接状态是不是成功,成功就获取句柄信号量,进行sem_post同步一下,信号量加1, 30000过期时间, nullptr, nullptr, 0);
if (zhandle_ == nullptr)
{
RPC_LOG_FATAL("zookeeper init error");
}
sem_t sem;
sem_init(&sem, 0, 0);
zoo_set_context(zhandle_, &sem); //设置信号量s
sem_wait(&sem); //这个相当于要么得到资源信号量减1,要么阻塞等待
RPC_LOG_INFO("zookeeper init success");
}
可以看到其实是调用了zk原有的接口zookeeper_init函数,生成句柄。然后有一些信号量同步操作,具体意义是什么还不清楚我。
创建节点函数也是同步检查path是否存在flag = zoo_exists(zhandle_, path, 0, nullptr);不存在调用zoo_create函数。获取数据也是用了zoo_get函数。
谈一下你对zk的理解:
zk是一个开源的分布式协调服务,为分布式提供一致性的软件。实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
我的项目没有涉及到zk集群的概念,只是数据的发布和订阅,利用create,getdata函数比较简单。
zk的好处是把一些复杂容易出错(尤其是强一致性的服务)的关键服务封装成简单的接口,使用简单。
zk主要组成成分是
文件系统:类似于普通文件系统,特别的是目录节点也可以存储数据,为了低延迟,一般数据长度有限1M左右,节点分为永久节点(除非手动删除,否则节点一直存在于 Zookeeper 上)和临时节点。临时节点是客户端建立的,一旦客户端断开连接,客户端就失效了。还有永久顺序和临时顺序,节点名后边会追加一个由父节点维护的自增整型数字。(四种节点类型)
通知机制;watcher机制。Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变。
(这里讲的是单个实例的,主要是文件系统和通知机制,下面会讲集群,也就是主节点和从节点以及观察者节点)
zk如何保证一致性的?
有序性是 zookeeper 中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为 zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个 zookeeper 最新的 zxid。
zk如何保证权限安全的
比如Linux,redis都是UGO模式,user/group/others。粗粒度
ACL访问控制列表权限机制,是一种细粒度的,更安全。具体表现在可以根据ip地址,或者账号密码,或者root等模式。每种模式的读写删除管理节点权限不一样。以及管理对象是一个实体。
zk实例有哪些角色?
leader 核心节点,处理请求,同步数据,调度(为什么要有主节点,可以避免重复计算)
follower 写请求转发给leader,参与投票
observer 不参与投票。为了增加提升集群的非事务处理能力
数据同步是用了ZAB协议。
leader宕机会重新选leader。
ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议。ZAB 协议包括两种基本的模式:崩溃恢复(leader宕机,超过半数节点宕机,重启系统)和消息广播。当集群中超过半数机器与该 Leader 服务器完成数据同步之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。
数据同步也分为全量同步,差异化同步,回滚再同步等方式,具体没研究过。
(ZAB和raft区别联系)
因为raft比zab出来晚点,可能raft 里面的有些东西会借鉴zab协议,本质上都是维护一个replicated log。核心都是日志复制,也就是数据同步。raft比zab实现要简单一些。
相同点:
1、采用quorum来确定整个系统的一致性(也就是对某一个值的认可),这个quorum一般实现是集群中半数以上的服务器,zookeeper里还提供了带权重的quorum实现.
2 写操作都是在leader上写
3、选哪个为Leader类似的,raft是term+log index,zab是quorum最新的作为leader。
4.都有心跳机制查看leader是否存活,以及超时时间。
不同点
zab用的是epoch和count的组合来唯一表示一个值, 而raft用的是term和index。
raft协议数据只有单向地从leader到follower(成为leader的条件之一就是拥有最新的log), 而zab的zookeeper实现中 ,一个prospective leader需要将自己的log更新为quorum里面最新的log,然后才好在synchronization阶段将quorum里的其他机器的log都同步到一致.
总结一下:都是为了分布式系统中一致性的协议。核心都是日志复制和leader选举机制。
不同点在于选leader标准不同,以及leader强势程度不同,raft强leader导致实现更简单。
另外,raft可以实现强一致性(线性一致性读),ZAB不知道能不能。
两阶段提交和三阶段提交
见分布式事务那一篇文章
数据发布订阅系统/配置中心/本项目注册发现中心
特点:数据量较小,配置可能会更新。
基于 Zookeeper 的实现方式
· 数据存储:将数据(配置信息)存储到 Zookeeper 上的一个数据节点
· 数据获取:应用在启动初始化节点从 Zookeeper 数据节点读取数据,并在该节点上注册一个数据变更 Watcher
· 数据变更:当变更数据时,更新 Zookeeper 对应节点数据,Zookeeper会将数据变更通知发到各客户端,客户端接到通知后重新读取变更后的数据即可。
zk实现分布式锁
1、zookeeper中规定,在同一时刻**,不能有多个客户端创建同一个节点**,我们可以利用这个特性实现分布式锁。zookeeper临时节点只在session生命周期存在,session一结束会自动销毁。
2、watcher机制,在代表锁资源的节点被删除,即可以触发watcher解除阻塞重新去获取锁,这也是zookeeper分布式锁较其他分布式锁方案的一大优势。
第一种基于临时节点。只有获得锁的A线程可以创建节点。其他的阻塞并设置监听,A结束后自动删除节点,其他线程重新竞争。
这种方案会产生惊群现象,影响性能。
第二种基于顺序临时节点,每个节点只监听上一个节点的信息,最小序号的获得锁,结束后删除自己,那么第二小的监听到了就去获得锁。
消息队列的作用
解耦、削峰、 异步(非必要逻辑异步运行,加快响应速度)
kafka
首先有个topic的概念,类似于表。
Partition 分区:一个topic下面有多个分区,这些分区会存储到不同的服务器上面,或者说,其实就是在不同的主机上建了不同的目录。多个分区多个线程,多个线程并行处理肯定会比单线程好得多。Topic 也是逻辑概念,而 Partition 就是分布式存储单元。
Replica 副本机制:Partition 为了保证数据安全,所以每个 Partition 可以设置多个副本。
而且其实每个副本都是有角色之分的,它们会选取一个副本作为 Leader,而其余的作为 Follower。
我们的生产者在发送数据的时候,是直接发送到 Leader Partition 里面,然后 Follower Partition 会去 Leader 那里自行同步数据,消费者消费数据的时候,也是从 Leader 那去消费数据的。
Consumer Group 消费者组:
我们在消费数据时会在代码里面指定一个 group.id,这个 id 代表的是消费组的名字。不同组可有唯一的一个消费者去消费同一主题的数据。
Kafka 也是主从式的架构,主节点就叫 Controller,其余的为从节点,Controller 是需要和 Zookeeper 进行配合管理整个 Kafka 集群。
broker分布式部署,就需要一个注册中心来进行统一管理。Zookeeper用一个专门节点保存Broker服务列表,也就是 /brokers/ids。broker在启动时,向Zookeeper发送注册请求,Zookeeper会在/brokers/ids下创建这个broker节点,如/brokers/ids/[0…N],并保存broker的IP地址和端口。
这个节点临时节点,一旦broker宕机,这个临时节点会被自动删除。
Zookeeper也会为topic分配一个单独节点,每个topic都会以/**brokers/topics/[topic_name]**的形式记录在Zookeeper。一个topic的消息会被保存到多个partition,这些partition跟broker的对应关系也需要保存Zookeeper。当分区的leader故障转移也需要zookeeper参与。
消费者组也会向Zookeeper进行注册,Zookeeper会为其分配节点来保存相关数据,节点路径为/consumers/{group_id}
Controller的选举工作依赖于Zookeeper,选举成功后,Zookeeper会创建一个/controller临时节点。
控制器用来监听分区变化(元数据变化,Leader故障,增减分区)、topic变化、broker变化。
kafka缺点就是:需要同时部署两个系统。而且分区增加时,Zookeeper集群压力变大,达到一定级别后,监听延迟增加,给Kafaka的工作带来了影响。并且单个控制节点如果节点故障,新的Controller选举成功后,会重新从Zookeeper拉取元数据进行初始化,并且需要通知其他所有的broker更新ActiveControllerId。老的Controller需要关闭监听、事件处理线程和定时任务。分区数非常多时,这个过程非常耗时,而且这个过程中Kafka集群是不能工作的。
(有新的采用多个控制节点,用raft协议保证他们的一致性)
Kafka 性能好在什么地方?
1、磁盘顺序写。随机写的话是在文件的某个位置修改数据,性能会较低。
2、零拷贝技术。下面是非零拷贝,数据要从内核状态拷贝到kafka进程空间,再拷贝到socket的缓存。耗时比较高。
Kafka 利用了 Linux 的 sendFile 技术(NIO),省去了进程切换和一次数据拷贝,让性能变得更好。内核的数据直接发送到网卡了。
零拷贝技术要多说一点。C++的vector也涉及到零拷贝,比如emplace_back是零拷贝,因为不用拷贝一个临时对象,采用的是右值引用?尤其是当涉及到很大的数据拷贝非常消耗时空资源
而这里网络中的零拷贝:
传统的:read首先DMA从磁盘拷贝到内核页缓存,再拷贝到用户空间。write先从用户拷贝到socket内核缓存,再拷贝到网卡;
mmap:零拷贝的一种。主要思想是把内核空间和用户态共享,就不用拷贝到用户态,上下文切换。
sendfile:Linux2.1内核开始引入了sendfile函数,用于将文件通过socket传送。主要优化是在内核socket缓冲区中记录当前要发生的数据在page buffer中的位置和偏移量。这样直接从page buf就可以发到网卡,没有拷贝的过程。
3、日志分段存储 Kafka 规定了一个分区内的 .log 文件最大为 1G,做这个限制目的是为了方便把 .log 加载到内存去操作。 其实redis对于主从复制时RDB文件的大小也会有限制,不然加载到内存很慢。
4、Kafka 的网络设计
加强版的 Reactor 网络线程模型。是一个三层的设计 prosessors-qequest-线程池。
acceptor不进行处理而是封装成channel给processors。然后消费者线程去消费这些 socketChannel 时,会获取一个个 Request 请求,里面包含了数据,然后线程池来读取。
所以如果我们需要对 Kafka 进行增强调优,增加 Processor 并增加线程池里面的处理线程,就可以达到效果。
ngix
是一个http代理和反向代理的web服务器,内存少15MB启动快高并发。还可以实现负载均衡和动静分离。
应用场景:
1、做http服务器,静态的网站,例如说:我们使用的OpenVPN。
2、反向代理实现负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用nginx做反向代理,并且多台服务器可以平均分担负载。它会让客户端感觉不到,依然输入原来的网址。
3、安全配置防火墙功能
处理请求的流程:
启动时读取配置文件得到需要监听的ip,port。然后主进程会创建好套接字(socket,bind,listen)
然后fork出很多子进程,竞争accept新的连接,这个时候客户端就可以和ngix建立三次握手了。某一个子进程会 accept 成功,得到这个建立好的连接的 Socket ,然后创建 nginx 对连接的封装
接着,设置读写事件处理函数,并添加读写事件来与客户端进行数据的交换。最后,Nginx 或客户端来主动关掉连接,到此,一个连接就寿终正寝了
高并发的原因:因为webserver是IO密集型的,主要是网络IO。所以也是用到了IO复用+异步非阻塞IO
也是用到了单线程+epoll和redis一样。
动静分离根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路
严格意义上,可以理解成使用nginx处理静态页面,tomcat或PHP处理动态页面。静态资源就是多次访问,源代码不变的(CSS,jpg、js)动态资源:当用户多次访问这个资源,资源的源代码可能会发送改变。
当然,因为现在七牛、阿里云等 CDN 服务已经很成熟,主流的做法,是把静态资源缓存到 CDN 服务中,从而提升访问速度。
相比本地的 Nginx 来说,CDN 服务器由于在国内有更多的节点,可以实现用户的就近访问。并且,CDN 服务可以提供更大的带宽,不像我们自己的应用服务,提供的带宽是有限的。