迄今为止最好理解的ZooKeeper入门文章
Posted Java3y
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了迄今为止最好理解的ZooKeeper入门文章相关的知识,希望对你有一定的参考价值。
边界的产生与突破
不觉间孩子已经上小学了,前段时间还参加了一次家长会,那就以学校和开会来说吧,这大家都很熟悉。
如果一个班要想开班会,那随时开都行,不需要提前安排与通知,因为一个班级从内部看就是一个整体,在班级内,同学之间以及与老师之间都可以随意交流,没有任何隔阂与阻碍。
一个班级从外部看就是一个独立的个体,因为班级与班级之间是完全独立的,因此一个班级的学生和老师都不会随便跑到其它班级去。 这是因为存在着一个边界,即班级边界。
正是这个班级边界把班级隔开了,边界之内的事情,如班会,可以随便开展,因为它和边界之外的一切都无关。 但是一旦涉及到边界之外,也就是跨边界,那么问题就产生了。
比如学校要开一个全体班级大会,肯定会提前安排好时间地点,以及各个班级在操场上的排列顺序,还要提前进行相应的通知。
为什么一个班的班会可以随时随地进行,而全体班级大会就要提前安排与通知呢? 就是因为它跨了班级边界,是一个跨边界问题。
而且班级与班级之间互相独立,互相不太熟悉,可能沟通起来也不容易,因此需要提前安排好。
那如何通知呢? 可以让班级之间互相通知,如一班通知二班,二班通知三班等等。 也可以由一个独立于所有班级之外的人,如教务处或学生处的人,来依次通知所有班级。
这两种通知方法在现实中都有使用,所有没有绝对的好与坏之分,视情况而定即可。
读者需要明确这两种方法代表了处理此类问题的两种方式,一种是独立个体之间互相直接交流来解决,一种是需要第三方介入来协调解决。
这里可以得出一个结论,边界的产生是一种自然现象,而且通常边界不会被打碎或消失,但是可以通过其它手段让边界两边的事物进行交流协商,这顶多是算是一种“突破”吧。
计算机相关的边界产生与突破
上一小节的描述非常简单,相信所有人都能明白。 接着就来说说和计算机相关的边界。 其实有很多,我们就说一两种吧。
操作系统里面有内核空间和用户空间,它们之间是有边界的,但是它们之间依然是可以交流的,因为操作系统的开发者已经做好了交流的方式方法。
每个应用程序通常都是一个进程,由于应用之间通常差别较大,而且还有一些其它方面的考虑,如安全问题,所以进程之间是有边界的,即进程边界。
操作系统是按进程分配资源的,因此一个进程内部的线程共享这些资源。 由于进程边界的存在,这些资源不能被别的进程使用。 所以进程就像是一个班级。
由于不同进行之间通常不需要交流,就像班级之间通常也不怎么交流一样,所以默认情况下进程之间无法交流,这与操作系统的内核和用户空间是不同的。
但总归有特殊情况吧,如果进程间需要交流怎么办? 那只能由开发人员自己想办法,如通过Socket,来实现。 这种情况在中间件里很常见,如nginx就涉及多个进程。
因为中间件的开发者一般都是牛X的人,他们能够搞定。 但问题是绝大多数开发人员都是搞业务开发的,他们受能力、时间或金钱限制,往往做不出来生产级别的交流方法。
可是有时候业务人员开发的应用程序的进程之间也是需要交流的,就像要开全体班级大会那样,我们可以类比着来寻求解决方案。
我们可以让进程之间直接互相交流,就像班级之间互相通知那样,这一方面对开发人员要求高且费时费力,另一方面是当进程多了之后,它们之间的直接交流就变成了一张网,会很乱。
为了说明这一点,我们看个简单示例。 假如张三、李四、王五是同事,周五下午下班时互相穿错了衣服,遗憾的是晚上回到家后才发现。 他们都想在第二天,就是周六,换过来。
张三需要去找李四,李四需要去找王五,王五又需要去找张三,假设他们都住的相距较远,这会是一个颇为复杂的问题。 那么如果有20个人都互相穿错了衣服呢,这将会是一个更加复杂的问题。
可以看出,如果个体之间互相直接交流的话,随着个体数目的增多,将会变得无比混乱与复杂。 比较好的解决方法可能大家都想到了。
那就是约定一个合适的地方,如公司,张三、李四、王五都过去,互相交换完衣服后各自回家。 这种方法随着个体的增多效果会越来越好。
其实这种方法就是全体班级开会时的第二种通知方法,由一个第三方无关人员介入来协调处理,此时这个第三方就是教务处或学生处。
那么对于多个进程之间的互相交流的解决方法也是这样的,由一个第三方无关进程介入来协调处理,此时这个第三方就是ZooKeeper。
这种方法还有一个好处,就是在一定程度上降低了个体的复杂性与要求,以及由此产生的额外问题。
比如有的班级的班主任脾气不好或不好说话,没有其它班级的班主任愿意去通知他,此时由教务处人员去通知,就不会有这个问题。
对于进程来说,降低了对业务开发人员的要求,不需要具备完整的进程间通信相关知识,同时降低了进程本身的复杂度,不需要支持完整的进程间通信,可能只需支持客户端即可。
这种方式的另一个好处是可以被抽象出来做成一个独立的中间件供大家使用,ZooKeeper就是这样的。
所以从本质来说, ZooKeeper就是一个第三方,也称中间人,它搭建了一个平台,让所有其它进程通过它来进行间接的交流。
ZooKeeper的数据模型
计算机其实就是用来处理或存储数据的,运行在它上面的软件大都也是如此。 zookeeper作为多进程的协调者,肯定是跑不了了。
存储数据和摆放物品是一样的,不能随意乱扔,这样既占地方,又不好看,也难寻找。 所以必须得有一定的层次结构。 这就是计算机的专业课数据结构了。
最简单的数据结构就是数组或链表了。 它们被称为线性表,是一维的,具有线性关系,即前后顺序,优点是简单,缺点是功能不够强大。
然后就是树了,可以认为它是两维的,左右是兄弟关系,上下是父子关系,因此具有从属关系。 它是一个功能与复杂度兼顾的结构。 现实生活中的各类组织架构大都是树形的。
再复杂的就是图了,它是网状结构,可以认为是多维的,由于任何节点都可以连通,因此它表达一种多边关系。 虽功能强大但也很复杂。 现实中的铁路网和人际关系网大都是网状的。
当然,这是三大类数据结构,每一类中又可以分为很多种。 比如树就有很多种变体,虽然都叫树,但有的差别还是很大的。
ZooKeeper选择了树作为自己存储数据的结构,其实它和文件系统也非常相似,如下图:
谈到数据就离不开增、删、改、查,对应树来说, 增就是添加新的节点到树中,删就是从树中删除某个节点,改就是修改树中某个节点上存放的数据,查就是找到树中某个节点读取它上面存放的数据。
说白了就是树形表示的是一种结构,真正的数据是在节点上放着呢,叶子节点或非叶子节点都可以。
ZooKeeper应该具备的能力
我们从最常见的场景入手,从宏观上了解下zookeeper是如何使用的,以及它应该具备哪些能力。
场景一:
有两个应用程序进程A和B,A先处理数据,处理完后通知B,B再接着处理。我们应该如何利用zookeeper来完成这个呢?一起来分析一下。
首先,进程A连接上zookeeper,在上面创建一个节点来表示自己的存在,假设节点名称就叫foo吧。
然后在节点上设置一个数据叫 doing ,表示自己正在处理数据。过了一会处理完后,把节点上的数据更新为 done 。
这样进程A的工作就算完了。可是这怎么去影响到进程B呢?我们知道zookeeper完成的是 进程间的间接交流,即进程之间是不碰面的。 因此只能借助于这个树形里的节点。
进程B也要连上zookeeper,然后找到foo节点,看好它上面的数据是否由doing变成了done,如果是自己就开始处理数据,如果否那就继续等着。
问题是进程B不能自己老盯着foo节点啊,这样太累了,伤神,况且它还要做其它事情呢。那这个事情应该由谁来做呢?很显然是zookeeper嘛。
于是进程B就对zookeeper说,你给我盯着foo节点,什么时候变成done了通知我一声,我就开干了。
因此, zookeeper需要具有盯梢能力和通知其它进程的能力 。这在zookeeper中对应一个专业术语,叫 Watch 。
Watch的作用和用法与上面描述的一样。就是进程B找到foo节点,在上面放一个Watch就可以了。
这样zookeeper就知道进程B对foo节点比较关注,于是zookeeper就盯着foo节点,一有风吹草动,马上通知进程B。
备注:关于Watch有非常多的细节问题,这里就不谈了。
需要注意的是, 这个Watch是一次性的,即只能使用一次。也就是说,zookeeper通知过进程B之后,Watch就被用掉了,以后就不会再通知了。
如果进程B还需要被通知怎么办?很简单,那就在foo节点上 再放一个新的Watch即可。如此这般下去,就可以保证一直被通知了。
我想这个Watch之所以被设计成一次性的,就是zookeeper不想让自己太累。睁着一双大眼,盯的东西太多太久的话,确实很累。
另外,zookeeper在通知进程B的时候,是可以把foo节点存放的数据一并发送过去的。
细心的朋友可能已经发现,zookeeper可以主动向进程B发通知或推数据,说明zookeeper和进程B之间的连接需要被一直保持。
因为进程B的位置比较随意,本来就是业务进程嘛。一旦连接断开,就像断了线的风筝,zookeeper再也无法找到进程B了。
不过zookeeper的位置是固定的,一旦连接断掉后,进程B可以再次向zookeeper发起连接请求,如果断开的时间足够短的话,进程B应该还可以在zookeeper上找回自己曾经拥有的一切。
这就涉及到了会话,因此zookeeper还要有一定的会话延续能力,方便在断开时间不长的时候找回原来的会话。
因此zookeeper应该有, 监视节点、通知进程、保持长连接,会话延续 等这样的能力。
场景二:
有时为了高可用或高性能,通常会把一个应用程序运行多份。假如运行了四份,那就是四个进程,分别是A、B、C、D。
当一个调用过来时,发现A、B、C、D都可以调,那就根据配置的负载均衡策略选出一个调用即可。
假设D进程所在的机器不幸掉电了,其实就是D挂了,那么此时再来一个调用的话,会发现只有A、B、C可以调,D自动就不存在了。
这其实就是Dubbo功能的一部分,那该如何基于zookeeper实现呢?照例一起分析下吧。
由于zookeeper是基于树形的数据结构,所以还是要拿节点说事。当进程A启动时,需要连接上zookeeper,然后创建一个节点来代表自己。
节点名称和节点上存放的数据可以根据实际情况来定,至少要包括该进程运行的IP和端口信息。进程B、C、D也做同样的事情。
如果让进程A、B、C、D的节点都位于同一个父节点下面,这样当一个调用过来后,只要找到这个父节点,读出它的所有子节点,就得到了所有可调用的进程信息。
如果某一时刻,进程D挂掉了,那么父节点下面进程D对应的那个节点应该会自动被zookeeper删除。这在zookeeper里有个专业术语,叫 临时节点 ( Ephemeral Node )。那么与之对应的自然就是永久节点了。
其实工作过程是这样的,业务进程启动后与zookeeper建立连接,然后在zookeeper里创建临时节点并写入自己的相关信息。接着通过周期性的心跳和zookeeper保持住连接。
一旦业务进程挂掉,zookeeper将接受不到心跳了,那么在超过一定的时间后,zookeeper将会删除与之对应的临时节点,表示这个业务进程不再可用了。
Dubbo的做法是将接口名称和IP端口信息和我们设置的信息整合成一个类似URL的字符串,然后以这个字符串作为名称来创建临时节点。
临时节点不允许有孩子节点,只有永久节点才可以。
本文内容都非常简单,很容易理解,所以即使初次接触zookeeper的朋友,看到这里也算是入门了。