zookeeper client API实现(python kazoo 的实现)

时间:2021-01-17 22:42:37

这里主要分析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