这里主要分析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. JVM方法调用

    当我们站在JVM实现的角度去看方法调用的时候,我们自然会想到一种分类: 1.编译代码的时候就知道是哪个方法,永远不会产生歧义,例如静态方法,private方法,构造方法,super方法. 2.运行时才 ...

  2. URI结构

    [scheme:][//host:port][path][?query][#fragment] path:从端口后第一个/开始,可以有多个,每个用/连接. query:从第一个?开始,至行尾或#结束. ...

  3. 关于html表格单元格宽度的计算规则

    * { margin: 0; padding: 0 } body { background: #fafafa } ul,li { list-style: none } h1 { margin: 20p ...

  4. nginx+tomcat+session共享(转)

    1 起因   最近对新开发的web系统进行了压力测试,发现tomcat默认配置下压到600人的并发登录首页响应速度就有比较严重的影响,一轮出现2000多个的 500和502错误.我把登录的时间统计做了 ...

  5. 开涛spring3(4.4) - 资源 之 4.4 Resource通配符路径

    4.4.1  使用路径通配符加载Resource 前面介绍的资源路径都是非常简单的一个路径匹配一个资源,Spring还提供了一种更强大的Ant模式通配符匹配,从能一个路径匹配一批资源. Ant路径通配 ...

  6. jQuery选择器中的空格问题

    前几天就遇到过这样的问题,明明我用的是('tr :even').css('background','#ccc')想改变表格中行的背景色,反复试了还是没改变.还问了度娘还是没找到原因所在(当时问题描述的 ...

  7. Java微服务框架

    Java的微服务框架dobbo.spring boot.redkale.spring cloud 消息中间件RabbitMQ.Kafka.RocketMQ

  8. Js实现京东无延迟菜单效果(demo)

    一个端午节,外面人山人海,又那么热,我认为宅在家里看看慕课网,充实自己来的实际... 这是一个js实现京东无延迟菜单效果,感觉很好,分享给大家... 1.开发基本的菜单结构 2.开发普通的二级菜单效果 ...

  9. $(obj).index(this)与$(this).index()异同讲解

    $(this).index()在使用jQuery时出镜率非常高,在编写选项卡及轮播图等特效时经常用到,但$(obj).index(this)似乎有点陌生. 为便于理解,以下分两个使用场景加以分析. 场 ...

  10. Java之反射代码演示说明

    还不存在的类–即我们需要使用反射来使用的类 Person类: package com.qf.demo4; public class Person { private String name; publ ...