了解zookeeper与阅读api源码

作者: | 更新日期:

其实zookeeper 能做什么只需要看看他的基本功能就行了,在这个基本功能能干什么,就看我们的想象力有多丰富了。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

一、背景

zookeeper 多年前就很火了,说可以用来当做名字服务、管理配置、集群管理、服务发现、负载均衡、容灾、远程锁等等。
其实zookeeper 能做什么只需要看看他的基本功能就行了,在这个基本功能能干什么,就看我们的想象力有多丰富了。
当然,能够做什么是一回事,线上服务能不能使用是另一回事了,并不是可以做到就一定要使用,我们还需要看到支持某一功能时对于的弱点,值不值的这样选择。
就像有人说汇编语言很强大,可以做这个可以做哪个,但是我们实际线上服务一般不会使用汇编语言。 所以,我们先看看zookeeper 到底提供了什么基本功能吧。

二、功能

zookeeper有三个基本功能,如下:

1.储存的是一个树形结构的数据,树的叶子节点可以储存数据。
2.当某个节点的子节点变更时,连在这个节点的CLIENT可以实时监听到变化。
3.CLIENT对节点操作时,是原子操作。

在这三个功能的基础上,我们可以实现很多东西。

1. 文件系统

由于zookeeper的储存是个树形结构,刚好和目录显示,我们就可以使用zookeeper来当做文件系统。
叶子节点的值最好不要储存文件信息,而是储存成文件的路径或其他信息辅助信息。

2. 名字服务

名字服务比较抽象,而且很容易和配置服务或者服务发现冲突,因为他们的本质是一样的,只是定位不同而已。
这里一般指的时哪些数据库的配置,比如mysql数据库的连接信息、redis数据库的连接信息等。

3. 配置管理

这里的配置一般是一些杂项配置,比如服务的超时时间、服务的某些开关等等。

4. 远程锁

由于zookeeper的操作是原子的,可以使用zookeeper来当做分布式锁了。
就行redis的操作是原子的,我们可以使用redis来做分布式锁一样,虽然实现方式不一样,但是本质是一样的。

5. 集群管理

集群管理和名字服务很类似,都是获得后台服务的连接信息。
但是对于集群,这个连接信息是动态的。
后台服务可用时,会主动连接zookeeper中心,然后客户端就可以发现后台服务有哪些可用的机器了。
这里的关键点在于后台服务某台机器挂了或者新增了机器,客户端可以感知到机器的变化。

6. 服务发现 负载均衡 容灾

服务发现、负载均衡、容灾放在一起说吧。
容灾、服务发现和前面的集群管理一样,不过是换了一个名字,都是机器挂了能自动剔除,机器好了能自动加回来。

负载均衡这个就有难度了,默认使用zookeeper是不能做到负载均衡的。因为服务负载过高了,zookeeper是感知不到的。
所以整理需要其他辅助信息,比如每个服务端收集系统信息计算出一个负载值,客户端根据每台机器的负载值计算出该使用哪台机器。
当然,有时候负载不高,但是耗时较高怎么办?网络抖动怎么办?
所以使用zookeeper来做负载均衡是在有点强人所难了,通过各种辅助信息可以做到,但是代价比较大,耦合也会比较严重。

既然zookeeper不能用来当做负载均衡,自然也不能用来服务发现、容灾、集群管理、名字发现了。
因为负载均衡其实已经包含了那些所有的功能,本质都是决策出一个可用的后台机器然后使用。

那不用zookeeper用什么呢? 之前刚好看过一个开源的负载均衡组件Tseer(点击可查看),推荐使用那个。
当有,现在由于大数据横行,大家都使用这个,那不考虑负载均衡这个问题的话,使用zookeeper也可以接收。

三、用途

其实,zookeeper的关键用途还是在于一致性上,即原子操作。
数据原子更新后,也会分配一个唯一的递增的id来标示。

我这有个项目,主要使用这个唯一递增ID来实现了数据唯一版本号的功能。
当然,长远看这个迟早会遇到瓶颈的,因为我们是所有数据共用一个节点,这样所有数据的更新其实就是串行化了。
每次更新都需要协商,网络操作来回几次,少的话几毫秒,多的话十几毫秒也有可能,这里介绍平均5毫秒吧。
那每秒就只能有200个数据更新,一分钟只能有1.2W个数据更新,这就是上限了。

这样看来,zookeeper在那些轻量级的场景可以使用,到了更新量大或对服务可靠性要求较高时,就显得心有余而力不足了。

四、api源码

zookeeper提供了c语言的api,这里进行简单的阅读与解析。

1. 初始化

初始化函数是 zookeeper_init 。
这个函数做得事情也很简单,申请各种内存,检查参数合法性。
对于zookeeper的中心地址,一般会有多个,api对地址进行了随机打乱,然后先使用第一个地址。

一切检查完之后,会执行adaptor_init开两个线程进程,一个用于IO操作,并且封装了zookeeper内部的逻辑,一个用于处理业务数据。

2. 创建节点

创建函数有zoo_create和zoo_acreate。
zoo_create函数实际还是调用zoo_acreate函数,不过加了一个本地锁。
zoo_acreate函数做得事情就是请求包序列化,发包,然后在锁上等待直到处理数据线程发来通知。

3. 写数据

写数据调用zoo_set函数和zoo_set2函数。
这两个函数最终都是调用zoo_aset函数,即同步操作。
而zoo_aset的操作又和zoo_acreate类似,序列化数据,发包,锁等待,等到处理数据通知。

4.读数据

读数据函数zoo_get调用监听读函数zoo_wge。
监听读函数zoo_wget函数同步读函数zoo_awget。
而同步读函数做得事情和上面的写一样,这里不再重复叙述。

5. 序列化

对于网络通信,一般会关心传输的数据协议,也就是序列化与反序列化。
我们这边常用称为打包和解包,一个意思。

这是一段协议的打包代码,看着很完善,每个结构前有开始标识与结束标识,每个数据都哟偶自己的tag与类型,这不就是标准的TLV协议嘛,可以无限扩展,好用。
但是细看了start_record函数和serialize_String函数,我笑了。
zookeeper为了降低传输的数据大小,那些tag都没有打进去,于是这个协议变成了LV协议。
对于LV协议是臭名昭著的,因为扩展性特别差,相当于约定了每一字节的含义,只可追增,不可删除。

6. 其他

这里还有两个关键的两个线程逻辑还没看,后面看了再补上相关记录吧。


本文首发于公众号:天空的代码世界,微信号:tiankonguse-code。

推荐阅读:

今天长按识别上面的二维码,在公众号中回复“ACM模板”,你将免费获得我大学耗时四年整理的《ACM算法模板》。
回复“算法的世界”,或点击阅读原文加入“tiankonguse的朋友们”,已有三百多个小伙伴加入。

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越