zookeeper 客户端_Spring Cloud(三):注册中心zookeeper站在客户端角度

点击蓝字关注我们

d821d754c7c2cad81f2275028622d82b.gif d821d754c7c2cad81f2275028622d82b.gif 大家好,我是杰哥,前两次文章主要讲述了关于Zookeeper的服务端机制。错过的小伙伴们,可以查看杰哥的Spring Cloud专栏的前两篇推送哦 那么今天,就跟着杰哥,转到客户端的角度,通过跟踪源码,进一步揭开zooKeeper的神秘面目吧~

一 概况

大致了解

首先,通过一个图,让大家对于zookeeper的工作流有一个大概的印象 a767eb0ce3136e483d3ea9847b9a1893.png 1)图中 包含客户端Client、SendThread、EventThread、Server以及Watcher五个重要角色 2)SendThread:负责将 ZooKeeper 的请求信息封装成一个 Packet ,发送给 Server ;并维持同Server的心跳 3) Server: 处理不同请求,返回response给EventThread 4) EventThread 负责解析通过 SendThread 得到的 Response ,最后发送给 Watcher 5) Watcher :通过调用pr ocessEvent 进行具体事件处理 好了,有了一个大致印象了 ,那下面的源码环节将会很easy了~

二 源码

具体探究

细心的你一定发现了,在zookeeper的客户端脚本 zkCli.sh中,我们发现,它实际上是通过加载org.apache.zookeeper.ZooKeeperMain启动的 b8e8fd2e621b0fb5f1dbe7031a805eee.png 那么,我们就先从ZooKeeperMain启动类进入zookeeper客户端源码的探索之旅吧~ 查看ZooKeeperMain的main()方法,看到该包含两个步骤: 9b363e4c865196a5e925967d7b838c3b.png
  1. 构造ZooKeeperMain对象并建立连接
  2. 读取终端输入并解析命令
来看看这两个步骤具体是如何处理的~

01. 构造对象 建立连接

1)进入ZookeeperMain() 10f44a49f92da3c52dc8be6522472c24.png 初始化命令参数各个可选项,调用connectToZK()方法连接到server端 2)进入方法conectToZK() 3b34e3554841f1c773a40bad17e29728.png 构造Zookeeper对象,建立连接 3)继续往下跟踪,进入Zookeeper(...) e34f837c696b961577e7e648fe71cafb.png 创建ClientCnxn对象,并调用了它的start()方法。 我们分别看看这两个步骤分别做了什么 4)先看看 这个对象初始化的时候都干了什么  bbe953dfb5612fc3395609affc59b808.png 我们看到,它分别初始化了很多参数,包括主机列表、连接超时时间、读取超时时间等。最后还初始化了客户端的2个核心线程:SendThread和EventThread 小插曲: 顺便说一句,这两个线程是zookeeper的重要角色,也将是我们今天的主角 5)然后呢,再看看: cnxn.s tart()方法 这个方法的作用就是:分别启动这两个线程 574e91f5fed2ed637faa555e0f2edb2d.png

02. 读取并解析命令

1)进入zookeeper的run()方法 92fef7ad4d132dd97e437d7aed11596e.png 我们看到,该方法通过反射调用 jline.ConsoleReader 类以及该类的addCompletor()方法,对终端输入进行读取。然后调用executeLine()方法,逐个执行单行命令 2)进入executeLine()方法 f175dbcb33d6e8ed1bcd17c1a6e783c5.png 我们看到该方法首先通过方法cl.parseCommand(line)方法对命令行进行解析,然后调用processCms(cl)方法执行各个命令 3)进入processCms(cl)方法 d09609baf19aea418af4f6640679ba2b.png 该方法再调用processZKCmd()方法,对于抛出的异常分别进行分类处理 4)进入processZKCmd()方法 56662694a40172cdac268d98bf7a1154.png 由于方法比较长,我们分为两部分来查看 a 第一部分,我们看到,对于quit、redo、history、printwatches以及connect方法,直接进行相应处理 5b0ae65b977b8b331a2a1c93721de6fe.png 到了第二部分的方法,包括对节点的各个操作,则需要在连接建立成功的情况下执行,具体如何执行,我们再来一探究竟~ 以 ZooKeeper.create() 为例 1) 上图中,如果指令为create,就会调用zk.create(..)方法 2)进入create(...)方法 我们看到create命令被封装成了一个 CreateRequest 对象request ,然后调用 submitRequest() 进行节点创建 bdc2f732da28bcf33b4a2ef782a07416.png 3) 进入 submitRequest(...)14fd36e2f1a410135c2905c59a06e247.png 我们看到zookeeper通过调用queuePacket(...)方法将 Request 封装成一个 Packet 包 4)进入queuePacket(...)方法 db25252f60aafb4d444f63204ddfae5c.png 将packet加入 SendThread的 outgoingQueue 队列中,等待执行。并唤醒selector 接下来,在SendThread.run()的while循环中,ZooKeeper将会通过doTransport()将存放在outgoingQueue中的Packet包发送给 Server

03. SendThread

我们在前面有提到,SendThread 的主要作用是:
  • Packet包发送给Server
  • 维持Client和Server之间的心跳,确保 session 存活
现在让我们从源码出发,看看 SendThread 究竟做了哪些工作 SendThread 是一个线程类,因此我们进入其 run() 方法,看看它的启动流程 1)先整体来看,run()方法是通过一个while循环,进行具体任务处理 94489882985edf452b685a36b1eb7b70.png fec569d8919feda8e30c58d6ffb7dc93.png 若状态为关闭或者权限验证失败,则关闭socket连接,并由eventThread处理关闭连接事件 a 与server建立连接 ca41003be443afb7f2e5df106b87cb34.png b 判断超时 75ce8b2594801a8c4b5d3a6a905def2a.png 可以看到会分别判断 readTimeoutconnetTimeout 两个超时时间,一旦发现链接超时,则抛出异常,终止 SendThread c 发送心跳 在没有超时且为连接状态的情况下,若已经达到心跳间隔时间,或者在最大时间间隔MAX_SEND_PING_INTERVAL内还没有发送packet。会再次发送心跳数据,避免访问超时

0dc3ffbde96ca145043ad1291632becc.png

d 发送指令 8708088ad93798f61ad74880597a9adf.png 整体来看, SendThread 的主要任务即为:
  • 创建同 Server 之间的 socket 链接
  • 判断链接是否超时
  • 定时发送心跳任务
  • 将ZooKeeper指令发送给Server
我们主要来看看建立连接的过程与发送指令的过程    2) 与 Server 的长链接 a 进入 startConnect() 通过调用抽象类 ClientCnxnSocket 的connect()方法进行socket连接,该抽象类的默认实现是 ClientCnxnSocketNIO 类。 2408c455163064a753d04cd2f29d3298.png b 在 ClientCnxnSocketNIO.connect() 中我们可以看到,与Server之间创建了一个socket链接,并调用registerAndConnect()方法注册并连接到主机地址上 c7dc2ffea694ee91dedcea849dee2a49.png c 进入registerAndConnect(...)方法。 2e0e50ef3b55a880b1333e3f9a1c7ae9.png 可以看到,zookeeper会首先将sock注册到selector,然后调用sock.connect()连接服务器,判断当前是否是初次连接。若是,则进入初始化连接primeConnection()方法 d 进入primeConnection()方法 首先设置首次连接为false,然后初始化sessionId,并建立连接 33dbdc20574585d85e664bf056b365f6.png e 接下来,将该连接事件组合成packet对象,并添加到发送队列中 需要注意的是,连接事件的requestHeader(请求头)为null 1d0c6c6bfd1578e97ca853ad87bb7d41.png f  设置为可读可写 调用clientCnxnSocket.enableReadWriteOnly()开启监听事件的读写功能

0f4cfceece18e5ed71a0d98cb4a2c931.png

那么到现在为止,已成功完成连接。接下来就要执行doTransport() 了~ 3)   发送 ZooKeeper 指令 a 进入doTransPort() aaa0d19a8c720c2a1d135889acce784c.png 该方法,首先会确保连接成功建立,调用doIO()方法进行处理,然后调用 findSendablePacket(...) 方法将连接事件的packet放到 outgoingQueue 头部 e03a805a8339ffef7f4af8bcf89a073e.png b 进入 doIO() 方法 该方法会分别判断key值是否是可读、或者可写的,分别进行读、写事件的处理 4f6cc39353b459370125198ba4a0c3f6.png c readable() 先来看看对读操作的处理 9ff2e85901c14f8c76b161766df7b4d5.png 调用 readResponse() 将其加到 eventTread 中

d 进入readResonse()方法

小插曲-Tips:  zookeeper的消息分为三种:
  • ping 消息:XID=-2

  • auth认证消息:XID=-4

  • 订阅的消息:XID=-1

订阅的消息,也就是节点变化的通知消息。比如子节点变化、节点内容变化 45e8574170609717709db099cd6bc7c8.png 我们看到readResonse()方法获取到这类消息,通过eventThread.queueEvent() 将消息推入事件队列waitingEvents,等待后续处理 028ef1d226c699c1e17cd4956727cd1d.png e writable() 进入第二部分, zookeeper对于写操作 的处理

ecc7ad83b8f34532fffe2bb5d56d0580.png

锁定 outgoningQueue 进行如下处理:将事件封装成packet对象,设置事件的xid,若!p.bb.hasRemaining()为true,表示该事件已发送成功,那么删除outgoingQueue中的事件,并将该事件添加到pendingQueue中,等待后续处理

04. EventThread

进入EventThread的run()方法 ce12951535a080538fbb3e760c57b173.png 我们看到该方法对获取到的事件通过方法processEvent()方法进行处理 因此我们就主要来看看processEvent()方法的逻辑 0bb5e15811009956263126cb001196f8.png 我们看到,该方法首先会判断事件是否是WatcherSetEventPair的实例 若是,则依次调用 watcher.process(pair.event)进行处理 否则就会以异步回调方式处理。根据 p.response() 判断为哪种响应类型,执行响应的回调方法 processResult() 好了~ zooKeeper客户端的源码还是比较简单的吧,分析到这里,也基本搞清楚了它的具体处理流程 根据以上的分析,我们就可以把最开始的zookeeper的工作流再细化一点,变成下面这个样子: a379571d8d43768c5dca5c96dd25a38c.png 图中细化到可以看到SendThread中处理过程包含的outgoingQueue和pendingQueue,并且SendThread和EventThread是通过Clientcnxn来控制处理的 处理流程为: 1)Client发起request给Zookeeper类 2)Zookeeper类将处理该request,并将其放入outgoingQueue(发送队列) 3)Zookeeper Server端处理发送队列中的该事件,并将该事件放到待处理队列PendingQueue中 4)由EventThreadt消费该pendingQueue中的该事件 5)分发给不同的watcher 进行事件的处理

三 总结

总而言之

也就是说,Client中在终端输入指令后,首先会被封装成一个 Request 请求。然后通过 submitRequest ,进一步被封装成 Packet 包,提交给 SendThread 处理 SendThread再 通过 doTransport()Packet 发送给Server,并通过 readResponse 获取结果,解析成一个 Event ,再将 Event 加入 EventThread 的队列中等待执行 EventThread 通过 processEvent 消费队列中的 Event 事件 是不是更深入理解啦~ 那么到现在为止,我们的注册中心章节之-zookeeper篇到这里就结束啦~之前还讲了Eureka篇,接下来将会继续出品console篇和nacos篇,敬请期待哦~ 嗯,就这样。每天学习一点,时间会见证你的强大~ 下期预告: Spring Cloud(四):注册中心-选择zookeeper还是Eureka? 362a6a9512d9240ef47b1801adbcaca9.png

往期精彩回顾

958c416186a6f0116a860bd3bcfa7835.png Spring Cloud(二):在实战中深入zookeeper服务端机制 Spring Cloud(一):我与导师的对话:你真的了解zookeeper吗? Spring Boot(十):注册中心Eureka-客户端视角 Spring Boot(九):注册中心Eureka-服务端视角 Spring Boot(八):Spring Boot的监控法宝:Actuator Spring Boot(七):你不能不知道的Mybatis缓存机制! Spring Boot(六):那些好用的数据库连接池们 Spring Boot(五):春眠不觉晓,Mybatis知多少 Spring Boot(四):让人又爱又恨的JPA Spring Boot(三):操作数据库-Spring JDBC SpringBoot(二):第一个Spring Boot项目 SpringBoot(一):特性概览 个人微信,欢迎添加交流~ e88c6e5ffef9fc75e70b43752c5e1e6b.png 也欢迎大家关注们的公众号,一起持续性学习吧~ 9a370643cac6ff46bafd905ce596b6d2.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值