Zookeeper 之 server 逻辑

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zookeeper 之 server 逻辑相关的知识,希望对你有一定的参考价值。

参考技术A

首先我们先忽略一些不影响流程的东西,虽然他们有重要的抑或是不重要的,但是最重要的先把整个脉络理顺。能代表 zkServer 在运行状态的类为:NioserverCnxnFactory 或者 NettyServerCnxnFactory(我们以 NIOServerCnxnFactory 为例)、NIOServerCnxn、AcceptThread、SelectorThread、IOWorkRequest、ConnectionExpirerThread。

从上面可以看出,AcceptThread、SelectorThread、IOWorkRequest、ConnectionExpirerThread 这几个 thread 撑起了 zk 服务端的主流程,而真正底层进行 socket 通信的为 NIOServerCnxn。

一个 AcceptThread 可以接受新连接,并使用简单的循环机制将它们分配给 SelectorThread,以将它们分布在SelectorThreads 上。 它强制每个IP设置最大连接数,并尝试通过在重试之前短暂地休眠来应对文件描述符用尽的情况。

AcceptThread 线程启动时,一直会运行 select() 方法,其中最重要的时 doAccept() 方法,此方法接收连接并将连接放到 SelectorThread 线程 acceptedQueue 中。

SelectorThread 从 AcceptThread 接收新接受的连接,并负责选择连接中的 I/O 准备情况。 该线程是唯一对选择器执行任何非线程安全或可能阻止的调用的线程(注册新连接和读取/写入兴趣操作)。

将连接分配给 SelectorThread 是永久的,只有一个 SelectorThread 会与该连接进行交互。 有1 - N 个 SelectorThreads,在 SelectorThreads 之间平均分配连接。

如果存在工作线程池(WorkerService),则当连接具有 I/O 执行时,SelectorThread 会通过清除其兴趣操作将其从选择中删除,并安排 I/O 进行工作线程的处理。 工作完成后,将连接放置在就绪队列中,以恢复其感兴趣的操作并恢复选择。

如果没有辅助线程池(WorkerService),则 SelectorThread 将执行 I/O 操作。

总之,SelectorThread 就是把任务分配为 IOWorkRequest 操作。

IOWorkRequest 是一个小的包装器类,允许使用 WorkerService 在连接上运行 doIO() 调用。WorkerService 即上面提到的辅助线程池。

WorkerService 是用于运行任务的辅助线程池,并使用一个或多个 ExecutorServices 实现。 WorkerService 可以通过创建N个单独的单线程 ExecutorServices 来支持可分配线程,或者通过创建支持N个线程的单个 ExecutorService 来支持不可分配线程。

NIOServerCnxnFactory 使用不可分配的 WorkerService,因为套接字IO请求是顺序无关的,并且允许 ExecutorService 处理线程分配可提供最佳性能。

CommitProcessor 使用可分配的 WorkerService,因为对给定会话的请求必须按顺序处理。ExecutorService 提供队列管理和线程重新启动,因此即使在单个线程中也很有用。

总之,IOWorkRequest 在运行的时候,使用 NIOServerCnxn 进行 I/O 操作,NIOServerCnxn 代表这 client 到 server 的连接。

在处理 client 向 server 的 request 请求时,IOWorkRequest 会使用 NIOServerCnxn 的 doIO() 方法,这个方法使用 ZooKeeperServer 的 processPacket 方法,然后一系列的操作会把 request 送给请求链,一直到 FinalRequestProcessor,反正很复杂。

在 handWrite 那块是发送的逻辑,利用 socket 将 bytebuffer 发送出去。

该线程负责关闭陈旧的连接,以便未建立任何会话的连接正确到期。

从第四节可以看出,request 一直到 FinalRequestProcessor,在它调用自己的 processRequest 方法时,是先触发 watcher 通知再去注册。

首先是调用此方法中的 ProcessTxnResult rc = zks.processTxn(request) ,然后沿着这个调用链条一路下去,然后到 ZKDatabase 的 processTxn,再到 DataTree 的 processTxn,最后一路下去到 WatcherManager 的 triggerWatch 方法,此方法会删除 watcher,然后调用其 process 方法,而此方法的处理逻辑是在 NIOServerCnxn 中的(ServerCnxn 代表着 client 与 server 的一个连接)。方法如下:

可以看出它只是将 watcher 封装成 WatchedEvent,并放到发送队列等待发送。而实际上 watcher 的处理逻辑都是在客户端执行。

Zookeeper之基于Observer部署架构

Observers:在不伤害写性能的情况下扩展Zookeeper

虽然通过Client直接连接到Zookeeper集群的性能已经很好了,可是这样的架构假设要承受超大规模的Client,就必须添加Zookeeper集群的Server数量,随着Server的添加,Zookeeper集群的写性能必定下降。我们知道Zookeeper的Znode变更是要过半数投票通过,随着机器的添加,因为网络消耗等原因必定导致投票成本添加,从而导致写性能的下降。

Observer是一种新型的Zookeeper节点。能够帮助解决上述问题,提供Zookeeper的可扩展性。Observer不參与投票,仅仅是简单的接收投票结果。因此我们添加再多的Observer,也不会影响集群的写性能。除了这个区别,其它的和Follower基本上全然一样。

比如:Client都能够连接到他们,而且都能够发送读写请求给他们,收到写请求都会上报到Leader。

Observer有另外一个优势,由于它不參与投票,所以他们不属于Zookeeper集群的关键部位,即使他们Failed,或者从集群中断开,也不会影响集群的可用性。

依据Observer的特点。我们能够使用Observer做跨数据中心部署。假设把Leader和Follower分散到多个数据中心的话,由于数据中心之间的网络的延迟。势必会导致集群性能的大幅度下降。使用Observer的话,将Observer跨机房部署。而Leader和Follower部署在单独的数据中心,这样更新操作会在同一个数据中心来处理,并将数据发送的其它数据中心(包括Observer的),然后Client就能够在其它数据中心查询数据了。可是使用了Observer并不是就能全然消除数据中心之间的延迟,由于Observer还得接收Leader的同步结果合Observer有更新请求也必须转发到Leader,所以在网络延迟非常大的情况下还是会有影响的,它的优势就为了本地读请求的高速响应。


使用Observer

假设要使用Observer模式,能够在相应节点的配置文件加入例如以下配置:

peerType=observer

上面不过告诉Zookeeper该节点是Observer,其次。必须在配置文件指定哪些节点被指定为Observer,比如:

server.1:localhost:2181:3181:observer

眼下我的工作中就用到了Observer,大致的架构例如以下图:

技术分享





以上是关于Zookeeper 之 server 逻辑的主要内容,如果未能解决你的问题,请参考以下文章

docker 使用compose安装zookeeper集群

Zookeeper选举原理

zook主要有哪些功能

打怪升级之小白的大数据之旅(五十六)<Zookeeper内部原理>

filebeat-2-通过kafka队列链接logstash

干货分享微服务spring-cloud(8.服务治理和配置中心Spring-cloud-zooke)