这里主要分析zookeeper client API的实现方式,以python kazoo的实现代码为蓝本进行逻辑分析.

一.代码框架及介绍

  API分为同步模式和异步模式.同步模式是在异步模式的基础上通过一些等待,循环等方式进行实现的.

  主要实现逻辑如下:

     基本模式就是建立两个线程,一个线程负责发送请求和接收响应.一个负责根据响应执行对应注册的watcher.

     大部分语言的实现都是同步模式通过异步模式实现的.在不同的语言里具体有差异.

  kazoo的框架实现在client,connection,threading,serlallzation这几个主要的类包中.

  client在kazoo项目的根目录,connection,serlallzation在项目的protocol目录,threading在handler目录.

  client是主要类.所有zookeeper 接口均在这里实现.

  connection是核心实现,所有请求和返回的逻辑处理均在这里处理.

  serlallzation是请求封装的实现.将所有请求封装成二进制数据在这里实现.

  threading是线程实现的核心.建立线程,以及启动线程和处理线程均在这里实现.

  https://github.com/python-zk/kazoo/blob/master/kazoo/client.py详细代码可以看这里.

二.详细逻辑的实现.

  1.client的实现

    client是将底层逻辑连接和封装起来的地方,存储公共数据的地方.

    client的代码实现基本是构造各种基础变量,对象,队列,记录当前socket状态,thread状态.重置各种状态.入口方法,和启动,停止等功能.

    简单分析下client下create方法.create方法基本就是检查参数,然后将参数传给create_async(对应的异步方法)方法,create_async将参数通过serlallzation包里的create类封装成request对象.并将这个对象和新的async_object对象传递给入口函数_call._call函数将request对象和async_object对象放到_queue的队列里.之后这个队列由connection里的实现去发送.其他所有zookeeper api的方法都是类是create方法.都是使用同名的async方法通过调用_call来实现.

    connection的对象,threading对象都是在client __init__里初始化的.启动线程是在client的start函数里调用threading.start和connection.start实现.

  2.connection的实现

    这里实现了整个client核心通信的部分.connection通过接收client对象参数,来使用各种client中初始化的公共数据和对象.在connection里核心函数是_connect_attempt函数.这个函数实现了数据交换和通信的核心部分.

    _connect_attempt函数主要部分如下.

            read_timeout, connect_timeout = self._connect(host, port)  #这里是从连接开始的部分贴的,并未将完整的函数贴上.需要完整的请看github
read_timeout = read_timeout / 1000.0 #_connect函数做了主要的握手链接的动作,以及一些错误处理.
connect_timeout = connect_timeout / 1000.0
retry.reset()
self._xid = 0
self.ping_outstanding.clear()
with self._socket_error_handling():
while not close_connection: #这里开始检查是否有主动stop的行为.在connection.stop函数里将这个标志置为真了.
# Watch for something to read or send
jitter_time = random.randint(0, 40) / 100.0
# Ensure our timeout is positive
timeout = max([read_timeout / 2.0 - jitter_time,
jitter_time])
s = self.handler.select([self._socket, self._read_sock],
[], [], timeout)[0] #通过select 来做数据检查等动作. if not s: #这里开始检查select的结果.结果为假的时候即为timeout这个时候发送心跳.
if self.ping_outstanding.is_set():
self.ping_outstanding.clear()
raise ConnectionDropped(
"outstanding heartbeat ping not received")
self._send_ping(connect_timeout) #心跳的主要处理在这里.
elif s[0] == self._socket: #如果结果为可读socket.则进行数据读操作.
response = self._read_socket(read_timeout) #_read_socket 函数里做了返回数据检查.根据返回数据做不同响应.
close_connection = response == CLOSE_RESPONSE #这里检查是否是server发过来的关闭连接的响应.
else:
self._send_request(read_timeout, connect_timeout) #_send_request就是发送请求的部分.client里将请求放入_queue队列里.
self.logger.info('Closing connection to %s:%s', host, port) #就是_send_request函数在消费_queue这个对列的里数据.
client._session_callback(KeeperState.CLOSED)
return STOP_CONNECTING

    _connect_attempt函数有被封装在_connect_loop函数里.这个函数又被zk_loop调用.zk_loop最后在connection.start函数里被装入由threading.spawn函数里创建的线程中一直运行.

    _read_socket函数是触发事件的核心函数.代码如下

    def _read_socket(self, read_timeout):
"""Called when there's something to read on the socket"""
client = self.client header, buffer, offset = self._read_header(read_timeout)
if header.xid == PING_XID:
self.logger.log(BLATHER, 'Received Ping')
self.ping_outstanding.clear()
elif header.xid == AUTH_XID:
self.logger.log(BLATHER, 'Received AUTH') request, async_object, xid = client._pending.popleft()
if header.err:
async_object.set_exception(AuthFailedError())
client._session_callback(KeeperState.AUTH_FAILED)
else:
async_object.set(True)
elif header.xid == WATCH_XID: #在这里通过返回xid判断是否是事件通知.
self._read_watch_event(buffer, offset) #在_read_watch_event函数里将用户设置的函数从client._datawatch队列里放进事件等
else: #待的进程里去.然后由等待事件的进程来执行.用户设置的事件是在_read_sponse函数中被装
self.logger.log(BLATHER, 'Reading for header %r', header) #入对应的列表里的. return self._read_response(header, buffer, offset)

    _read_watch_event函数通过将watch放入handler的事件的对列中去.代码如下.

    def _read_watch_event(self, buffer, offset):
client = self.client
watch, offset = Watch.deserialize(buffer, offset)
path = watch.path self.logger.debug('Received EVENT: %s', watch) watchers = [] if watch.type in (CREATED_EVENT, CHANGED_EVENT): #在这里判断事件类型.并从不同的类型队列中放入watchers列表里.
watchers.extend(client._data_watchers.pop(path, []))
elif watch.type == DELETED_EVENT:
watchers.extend(client._data_watchers.pop(path, []))
watchers.extend(client._child_watchers.pop(path, []))
elif watch.type == CHILD_EVENT:
watchers.extend(client._child_watchers.pop(path, []))
else:
self.logger.warn('Received unknown event %r', watch.type)
return # Strip the chroot if needed
path = client.unchroot(path)
ev = WatchedEvent(EVENT_TYPE_MAP[watch.type], client._state, path) #这个对象是事件函数的参数. # Last check to ignore watches if we've been stopped
if client._stopped.is_set():
return # Dump the watchers to the watch thread
for watch in watchers:
client.handler.dispatch_callback(Callback('watch', watch, (ev,))) #然后通过这个方法将需要触发的事件放入事件线程中监听的队列里.
#从这里可以看出watch函数有一个参数.就是watchedEvent的对象.

    再看看handler.dispatch_callback是如何处理的.代码如下:

def dispatch_callback(self, callback):
"""Dispatch to the callback object
The callback is put on separate queues to run depending on the
type as documented for the :class:`SequentialThreadingHandler`.
"""
self.callback_queue.put(lambda: callback.func(*callback.args)) #可以看见这个函数非常简单.关键就这一句话.就是通过lambda封装成一句话的函数,然后把这个对象放到callback_queue队列里.这个队列在事件线程中循环监听.

    看看事件监听线程是如何做的.代码如下.

def _create_thread_worker(self, queue):  #这个函数在handler.start里被调用.事件的队列也由调用函数传递进来.
def _thread_worker(): # pragma: nocover
while True:
try:
func = queue.get() #这里将事件从队列里拿出来.然后在下面执行.
try:
if func is _STOP:
break
func() #这里执行事件.
except Exception:
log.exception("Exception in worker queue thread")
finally:
queue.task_done()
except self.queue_empty:
continue
t = self.spawn(_thread_worker)
return t
def start(self):
"""Start the worker threads."""
with self._state_change:
if self._running:
return # Spawn our worker threads, we have
# - A callback worker for watch events to be called
# - A completion worker for completion events to be called
for queue in (self.completion_queue, self.callback_queue): #可以看见在这里将callback_queue对列里的对象传递给了_create_thread_worker函数.然后由函数执行.
w = self._create_thread_worker(queue)
self._workers.append(w)
self._running = True
python2atexit.register(self.stop)

    到这里为止,整个事件被触发到装载执行都已经非常清晰了.接下来看看如何被从最初的位置被放进类型队列里的.

            watcher = getattr(request, 'watcher', None)  #这是_read_response的最后一部分代码.可以看见是通过判断request的类型来判断需要触发的事件类型是字节点类型还是数据类型.
if not client._stopped.is_set() and watcher:
if isinstance(request, GetChildren):
client._child_watchers[request.path].add(watcher)
else:
client._data_watchers[request.path].add(watcher)

    基本上kazoo的实现核心逻辑就是这样.通过client将所有共享数据初始化.然后把用户的请求封装成接口.在接口中将请求放入发送队列.然后有connection包类将队列里的请求发送出去.发送出去之后等待服务器响应,并根据响应做不同动作.并且接收到用户这个请求的正常响应之后.将用户注册的事件装入对应的类型队列中.然后等待事件通知到达的时候将事件从类型队列中拿出来放入一直等待执行事件的线程队列中.然后由事件线程执行事件.

    在client的__init__函数里可以清晰的看到这些底层逻辑可以另外提供.只需按照接口名称进行实现即可.

    再详细的细节部分,可以自行参考对应的github

zookeeper client API实现(python kazoo 的实现)的更多相关文章

  1. zookeeper C client API 和zkpython 的安装

    1 zookeeper C API 安装 yum install -y ant 在解压的zookeeper包中执行: ant compile_jute 进入src/c 安装:yum -y instal ...

  2. Zookeeper C API 指南一(转)

    Zookeeper 监视(Watches) 简介 Zookeeper C API 的声明和描述在 include/zookeeper.h 中可以找到,另外大部分的 Zookeeper C API 常量 ...

  3. HBASE的读写以及client API

    一:读写思想 1.系统表 hbase:namespace 存储hbase中所有的namespace的信息 hbase:meta rowkey:hbase中所有表的region的名称 column:re ...

  4. zookeeper集群的python代码测试

    上一篇已经讲解了如何安装zookeeper的python客户端,接下来是我在网上搜到的例子,举例应用环境是: 1.当有两个或者多个服务运行,并且同意时间只有一个服务接受请求(工作),其他服务待命. 2 ...

  5. 9. 使用ZooKeeper Java API编程

    ZooKeeper是用Java开发的,3.4.6版本的Java API文档可以在http://zookeeper.apache.org/doc/r3.4.6/api/index.html上找到. Ti ...

  6. 073 HBASE的读写以及client API

    一:读写思想 1.系统表 hbase:namespace 存储hbase中所有的namespace的信息 hbase:meta rowkey:hbase中所有表的region的名称 column:re ...

  7. Zookeeper系列三:Zookeeper客户端的使用(Zookeeper原生API如何进行调用、ZKClient、Curator)和Zookeeper会话

    一.Zookeeper原生API如何进行调用 准备工作: 首先在新建一个maven项目ZK-Demo,然后在pom.xml里面引入zk的依赖 <dependency> <groupI ...

  8. Elasticsearch Java Rest Client API 整理总结 (一)——Document API

    目录 引言 概述 High REST Client 起步 兼容性 Java Doc 地址 Maven 配置 依赖 初始化 文档 API Index API GET API Exists API Del ...

  9. cloudstack api调用python

    通过python调用cloudstack接口,cloudstack 接口通过  admin   key来判断是否有访问有权访问 #!/usr/bin/env python import urllib2 ...

随机推荐

  1. 超高速指数模糊算法的实现和优化(10000*10000在100ms左右实现)。

    今天我们来花点时间再次谈谈一个模糊算法,一个超级简单但是又超级牛逼的算法,无论在效果上还是速度上都可以和Boxblur, stackblur或者是Gaussblur想媲美,效果上,比Boxblur来的 ...

  2. 调停者(Mediator)模式

    调停者模式是对象的行为模式.调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显引用.从而使它们可以较松散地耦合.当这些对象中的某些对象之间的相互作用发生改变时,不会立即影响到其他的一些 ...

  3. 使用Java语言开发微信公众平台(七)——音乐消息的回复

    在上一节课程中,我们学习了图片消息的回复功能.根据微信公众平台的消息类型显示,微信共支持文本.图片.语音.视频.音乐.图文等6种消息类型的回复: 其中,我们已经实现了文本.图文.图片等消息的回复处理, ...

  4. 解决window.navigator.geolocation.getCurrentPosition在IOS10系统中无法进行地理定位问题

    昨天接到用户通知说在点击"看场地"时无法获取地理位置信息. 在接到通知时,首先想到的是排查机型问题.由于客户多为IOS用户,所以最先看的是在安卓是有没有此问题的发生,调查结果为安卓 ...

  5. ACCESS数据库增强器需求及介绍

    目前版本:ver1.0.0.2 现已支持cs文件浏览,高亮显示 针对如下图所示的access数据库,我想导出access数据库的所有或者部分表的表结构,还想对表进行封装,封装如下所示. using S ...

  6. MySQL数据库使用mysqldump导出数据详解

    mysqldump是mysql用于转存储数据库的实用程序.它主要产生一个SQL脚本,其中包含从头重新创建数据库所必需的命令CREATE TABLE INSERT等.接下来通过本文给大家介绍MySQL数 ...

  7. javaWeb学习总结(3)- Servlet总结(servlet的主要接口、类)

    Servlet总结01——servlet的主要接口.类 (一)servlet类 Servlet主要类.接口的结构如下图所示: 要编写一个Servlet需要实现javax.servlet.Servlet ...

  8. Lua学习(2)——表达式

    1. lua算术操作符lua支持的算数操作符: + - * /除 ^指数 %取模 -符号 2. lua关系操作符 <小于 >大于 <= >= == ~=不等于 3. 逻辑操作符 ...

  9. (数字IC)低功耗设计入门(三)——系统与架构级

    前面讲解了使用EDA工具(主要是power compiler)进行功耗分析的流程,这里我们将介绍在数字IC中进行低功耗设计的方法,同时也结合EDA工具(主要是Design Compiler)如何实现. ...

  10. nrm是什么?以及nrm的安装与命令

    nrm的作用与安装使用 一.nrm是什么? 这是官方的原话: 开发的npm registry 管理工具 nrm, 能够查看和切换当前使用的registry, 最近NPM经常 down 掉, 这个还是很 ...