zookeeper client API实现(python kazoo 的实现)
这里主要分析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 的实现)的更多相关文章
- zookeeper C client API 和zkpython 的安装
1 zookeeper C API 安装 yum install -y ant 在解压的zookeeper包中执行: ant compile_jute 进入src/c 安装:yum -y instal ...
- Zookeeper C API 指南一(转)
Zookeeper 监视(Watches) 简介 Zookeeper C API 的声明和描述在 include/zookeeper.h 中可以找到,另外大部分的 Zookeeper C API 常量 ...
- HBASE的读写以及client API
一:读写思想 1.系统表 hbase:namespace 存储hbase中所有的namespace的信息 hbase:meta rowkey:hbase中所有表的region的名称 column:re ...
- zookeeper集群的python代码测试
上一篇已经讲解了如何安装zookeeper的python客户端,接下来是我在网上搜到的例子,举例应用环境是: 1.当有两个或者多个服务运行,并且同意时间只有一个服务接受请求(工作),其他服务待命. 2 ...
- 9. 使用ZooKeeper Java API编程
ZooKeeper是用Java开发的,3.4.6版本的Java API文档可以在http://zookeeper.apache.org/doc/r3.4.6/api/index.html上找到. Ti ...
- 073 HBASE的读写以及client API
一:读写思想 1.系统表 hbase:namespace 存储hbase中所有的namespace的信息 hbase:meta rowkey:hbase中所有表的region的名称 column:re ...
- Zookeeper系列三:Zookeeper客户端的使用(Zookeeper原生API如何进行调用、ZKClient、Curator)和Zookeeper会话
一.Zookeeper原生API如何进行调用 准备工作: 首先在新建一个maven项目ZK-Demo,然后在pom.xml里面引入zk的依赖 <dependency> <groupI ...
- Elasticsearch Java Rest Client API 整理总结 (一)——Document API
目录 引言 概述 High REST Client 起步 兼容性 Java Doc 地址 Maven 配置 依赖 初始化 文档 API Index API GET API Exists API Del ...
- cloudstack api调用python
通过python调用cloudstack接口,cloudstack 接口通过 admin key来判断是否有访问有权访问 #!/usr/bin/env python import urllib2 ...
随机推荐
- Python学习(一) —— matplotlib绘制三维轨迹图
在研究SLAM时常常需要对其输出的位姿进行复现以检测算法效果,在ubuntu系统中使用Python可以很好的完成相关的工作. 一. Ubuntu下Python的使用 在Ubuntu下使用Python有 ...
- 命令模式(Command Pattern)
命令模式属于对象的行为模式.命令模式又称为行动(Action)模式或交易(Transaction)模式.命令模式把一个请求或者操作封装到一个对象中.命令模式允许系统使用不同的请求把客户端参数化,对请求 ...
- Scala基础 - 下划线使用指南
下划线这个符号几乎贯穿了任何一本Scala编程书籍,并且在不同的场景下具有不同的含义,绕晕了不少初学者.正因如此,下划线这个特殊符号无形中增加Scala的入门难度.本文希望帮助初学者踏平这个小山坡. ...
- Linux主机通过代理服务器进行网络连接
公司的不允许访问外网,但是访问外网需要配置代理,window配置均好配置,在Linux的命令行底下,一般的程序都是使用http_proxy和ftp_proxy这两个环境变量来获得代理设置的.1.在/e ...
- java关键字transient与volatile小结
本文转自:http://heaven-arch.iteye.com/blog/1160693 transient和volatile两个关键字一个用于对象序列化,一个用于线程同步,都是Java中比较高阶 ...
- Elasticsearch与Solr
公司之前有个用Lucene实现的伪分布式项目,实时性很差,后期数据量逐渐增大的时候,数据同步一次需要十几小时.当时项目重构考虑到的是Solr和ES,我参与的是Solr技术的预研.因为项目实时性要求很高 ...
- iOS安全攻防之越狱设备检测
iOS 越狱(iOS Jailbreaking),是用于获取苹果公司便携装置操作系统iOS最高权限的一种技术手段,用户使用这种技术及软件可以获取到 iOS 的最高权限,甚至可能可以进一步解开运营商对手 ...
- JqueryEasyUI-从入门到精通-第一天
PS:本教程暂时不提供源码,自己动手写写会记得更牢固哦 Parser(解析器) 对象的属性和方法: 使用: 效果:
- PHP初体验
PHP初体验 提笔写初体验总不知道从何说起,直接聊PHP中的函数.PHP网络技术.数据库操作.PHP模板等感觉又不是初体验.最后还是决定从PHP的面向对象.PHP的魔术方法.PHP的反射.PHP中的异 ...
- java基础-四种方法引用
实例 直接三角形,通过两边算第三边,目的是为了如何使用这几种方法引用.代码中多有些不合适,尽情原谅. 静态方法引用 接口的参数列表与类中的具体实现方法的参数列表一样,返回值一致. 调用 //静态引用 ...