集群开源软件赏:ZooKeeper

本篇是开源软件最后一篇,接下来的一周将推送语言相关或项目管理相关内容。敬请期待。以下正文:

所谓集群系统,是指由多个进程和服务器合作组成完成一定功能的系统。之所以要由多个节点(进程或服务器)组成,其中一个重要目标是:容灾。但是,一大堆服务器要能协同工作,必须要有一个负责组织整个集群的中心,这个中心由于具有唯一性,所以往往都会是一个单点。这个时候问题来了,这个单点如果故障了,整个集群都可能瘫痪,是命门所在。因此,为了让集群中心不再成为单点,Google开发了ZooKeeper这款著名的开源软件。

ZooKeeper是什么

ZooKeeper是Apache Hadoop下的一个子项目,其官网是:http://zookeeper.apache.org

它是Google chubby的开源实现,基于著名的Paxos算法。有兴趣的可以去搜索一下这个算法。ZooKeeper主要的功能是充当集群的中心点,维护集群中各节点的配置信息、提供名字服务、实现分布式同步等。而这些功能的核心,就是一个分布式的最终一致性存储系统。集群的共享信息,都放到ZooKeeper上,这样集群中任何的节点都能互相协作了。

ZooKeeper本身可以由多个进程组成一个服务,这样就不构成单点风险。这些进程之中,会有一个充当“领导者”(leader)的角色,为整个集群提供一致性保证。在ZooKeeper服务中存放的数据,都是在进程的内存中保存的。同时还可以提供事物日子和持久化的快照存储功能。客户端通过tcp和ZooKeeper的服务进程建立连接,如果连接的这个ZooKeeper进程挂掉了,可以在ZooKeeper算法的指导下连接到另外一台。由于ZooKeeper的数据是在多个服务器进程上复制的,所以特别适合读写比在10:1的场景。

ZooKeeper存储的数据模型,给人的感觉就类似一个目录树,和文件系统非常像,都是一个树状的结构,以字符串和反斜杠隔开表达。用户可以在“根”节点上建立多个任意名字的子节点,然后再在这些子节点下建立其他子节点,或者只是在子节点下存放一些数据。这些数据都是序列化的字节数组类型,可以用来存放任何信息。

ZooKeeper的内部在处理请求的时候,读和写是分开的:

  1. 写数据的请求,会通过一个请求处理器,然后转交给原子广播系统,把请求内容“真实”的同步到所有节点上,更新各节点的复制数据库后,再返回结果
  2. 读请求比较简单,直接从节点的复制数据库那里读取结果就完成了。

ZooKeeper会在内存中保留上述树状数据的完整版,并且把写操作记录到磁盘,以便用于恢复。写操作会先把记录写到磁盘,再更新内存中的状态。每个ZooKeeper服务进程都可以提供服务。客户端可以通过任何一个具体的服务进程来提交读写请求。读操作可能通过就近的任何一个数据库副本来完成,写操作则通过一致性协议进行处理。也就是说客户端并不是一直都只连接着一个ZooKeeper服务进程的,在进行读写不同操作的时候,是连接不同的服务进程的。那么,写操作到底是会给哪个服务进程处理了?实际上是全部转发给某一个特点的服务进程,这个进程叫“领导者”(leader),这个进程写入数据后,其他的进程将会跟随这个进程的数据做同步,所以叫“跟随者”(follower)。因此ZooKeeper服务进程中,会有两种角色,有的进程负责领导,其他进程负责跟随,这样来保证数据的最终一致性。因此我们可以看到,ZooKeeper为了保证数据在各个服务节点上的一致性,是采用复制数据和更新广播的方式来做的,因此读会比写效率高的多。

由于ZooKeeper这种安全的数据同步方案,所以它可以提供非常高的可靠性保证:

  1. 一致性:客户端无论连接到哪个服务器,展示的都是同一个视图。
  2. 顺序性:客户端的update请求,会根据他们发出的顺序进行处理。
  3. 原子性:对节点的更新操作,要么成功,要么失败。
  4. 高可用性:在2n+1台机器组成的集群中,即使有n台失效,仍然不影响系统整体可用性。
  5. 高效性:由于采用内存镜像,降低了读的延迟。
  6. 最终一致性:经过一段时间,客户端看到的数据最终是一致的。

ZooKeeper的API看起来和文件系统类似,提供了C和JAVA语言的接口,他们包括:

  1. create(path,data,acl,falg)创建节点
  2. delete(path,version)删除节点,子节点不为空不能删除
  3. setData(path,data,version)更新节点的内容
  4. getData(path,watch)读取节点,设置watch节点变化时会通知
  5. exists(path,watch)判断节点是否存在
  6. getchildren(path,watch)获取子节点
  7. setACL(path,acl,version)设置Acl
  8. getACL(path)获得Acl

可以看到,基本的操作和文件系统差别不大,比较特别的是可以设置“监视器”(watch),这可以在集群节点数据有变化的时候,相关的客户端进程及时的收到通知。这种主动通知的方法,比起客户端轮询(不断的刷新)状态来的更高效和及时。因此是ZooKeeper上使用最多的功能之一。监听的具体用法是:

1. 所有的读操作都可以设置监听:getData(), getChildren(), exists()

2. 监听的定义:当被监听的数据被修改时,一个监听事件会发给置了监听的客户端。监听只触发一次。

3. 只触发一次

a) 数据改变只会给客户端发送一个监听事件。如果数据再次改变,不会再发送监听事件,除非客户端设置另外一个监听。

4. 监听数据的设置

a) getData()和exists()是为当前结点设置监听

b) getChildren()是为孩子结点设置监听

c) setData()会触发对当前结点的监听

d) Create()会触发当前结点及孩子结点

e) Delete()会触发当前结点及孩子结点

5. 如果客户端与服务器断开,期间被监听数据发生变化,重连后监听依然会被触发。

6. 有一种情况会错失监听消息:监听一个结点是否存在,但这个结点还没有创建。如果在断开状态,这个结点被创建并且被删除。

7. 一个结点是先监听事件,才能看到新数据

但是监听也有一些需要注意的问题:

  1. 因为监听只触发一次,并且在获得事件和发送下一个监听之前是一个延迟的,所以不能够可靠地监听到数据的每一次变化。要注意处理这种情况:结点在得到监听事件,并再次设置监听之间改变多次。也就是说,你在得到监听事件之后,和再次调用getData(path,watch)之前的数据变化是不会知道的。如果你只是想维持知道最新的数据状态,而不是每次的变化过程,这个就没什么问题。
  2. 一个监听事件,或者功能上下文对,只会被触发一次。如:在同一个结点上注册了exists()和getData()的监听,如果这个文件被删除,只会收到一个删除的通知。所以你最好规划好哪些节点监听数据变化,哪些节点监听是否存在。
  3. 当你与服务器断开(如服务器故障),在连接重建之前你得不到任何监听事件。也就是事件不会重放,你最好在重新连接之后立刻读取一次最新的数据。

最后列举一下官方提供的性能数据:

实测的数据:

一个集群承载每秒2万的写操作,其实是比较低的,所以要非常注意不要用ZooKeeper承担太大的写操作功能,比如不要让他承担NoSQL或者Memcache的功能。

ZooKeeper可以做什么

最常见的用法是管理集群。做法是,在ZooKeeper上建立一个EPHEMERAL类型的目录节点,然后每个 Server在它们创建目录节点的父目录节点上调用getChildren(String path, boolean watch) 方法并设置 watch 为 true,由于是 EPHEMERAL目录节点,当创建它的Server 死去,这个目录节点也随之被删除,所以Children 将会变化,这时getChildren上的Watch 将会被调用,所以其它Server 就知道已经有某台Server 死去了。这样每个集群中的服务进程,都能通过ZooKeeper及时的知道现在集群中都有哪些进程“活着”。当然啦,如果你新加了进程进来,一样会让目录节点产生变化(新建了子节点),这样其他的服务进程也都知道了。

第二个用法是分布式锁:有时候我们的集群系统,不同进程对同一个资源会有锁的需求,比如我们的集群系统只有一个发短信的端口,不能一起去向这个端口投递消息,这样就需要一把“锁”,当一个进程在发短信的时候,其他所有想发短信的进程都被锁住而不能发。

第三个用法很常见,就是用来做配置管理。Config文件一直是服务器软件的重要数据,在集群系统中,有很多数据是需要多个进程共享的。传统的做法常常是把配置文件写在服务器磁盘上,当配置需要修改的时候,把文件批量拷贝到各个服务器上,然后发送一些信号给服务进程,让其主动重新读这个配置,然后生效。这个过程繁琐而且容易出错,因为批量拷贝文件,批量发送信号,都可能中途出错。而如果把配置信息放在ZooKeeper上,一旦配置有修改,watch机制就会触发所有的进程刷新配置。这样我们只要修改ZooKeeper上的配置信息,整个配置刷新过程就自动完成了。有时候我们需要同步的信息不止是静态的配置信息,还会包括一些业务数据信息,只要写操作不太频繁,ZooKeeper都是能胜任的。

第四个用法是做名字服务,用来存储“名字”-“地址”的映射表。在RPC系统和SOA架构的集群中,为了让集群的服务能够根据运行情况,动态的进程负载均衡或者容灾,往往在请求集群中服务的时候,不会写死一个物理的IP地址,而是先用服务请求的“名字”来查询哪个IP+端口地址上的进程能提供服务,然后再连接过去。这种情况下,ZooKeeper就可以用来承担名字查找服务的任务。由于集群中注册服务的操作远远少于查询服务地址的操作,所以很适合用ZooKeeper完成。

ZooKeeper使用技巧

  1. 常见的ZooKeeper错误是什么? ZOPERATIONTIMEOUT/ZCONNECTIONLOSS/ZSESSIONEXPIRED。前两种重试就可以了,后一种表示通信严重错误。

2. ZooKeeper性能和那些指标有关

长短连接方式>已有连接个数>读写节点大小>读写节点深度>服务器上节点个数。

3. ZooKeeper最大并发读能达到怎样的性能?

12000连接并发读1K数据。

4. ZooKeeper最大并发写的性能如何?

10000连接并发写1byte数据。5600连接并发写2k数据。

5. Leader失效对ZooKeeper有什么影响?

导致服务暂停1~2秒(官方称200ms)

6. ZooKeeper单一节点允许写入的数据大小是多少?

标配1MB

7. 单个ZooKeeper服务器挂掉是否影响服务?

不影响,已经连接到这个服务器上的客户端连接会被转移,并受到一个连接丢失的警告。

8. ZooKeeper瓶颈在哪里?

瓶颈是leader节点。除了只读操作,其他操作都要经过leader。因此它的CPU是其他节点的4~5倍,网络流量也是4~5倍。应该禁止大于1MB的数据写入节点,否则leader服务器的带宽很容易耗尽。由于是JAVA构造的服务,所以leader的GC(垃圾回收)操作会很频繁。

9. 如果遇到timeout/连接失败怎么办?

重试2~3次,ZooKeeper作为集群系统大多数情况下都能恢复。

10. 如果遇到删除节点失败/创建节点失败怎么办?

这是逻辑代码问题,一般原因是子节点非空(ZooKeeper可没有rm –rf的功能)和父节点不存在(一步建立多个层次节点树是不允许的)导致的。

11. 持续遇到连接丢失怎么办?

ZooKeeper集群有严重问题,或者并发太多连接ZooKeeper已经无法接受了。

ZooKeeper作为一个严谨的最终一致性数据系统,接口使用非常简单,安装部署也很方便,是构建集群服务中心点的最好选择。只是使用的时候注意不要滥用其功能,要严格控制节点中数据的大小,以及写操作数量。读操作也应该控制不宜太多,比如不适合用来做公开dns服务器,如果真的需要对外提供大量的读操作,可以在外面以复制缓冲的形式建立多个缓存服务器,开发起来也是很简单的。

感谢大家的阅读,如觉得此文对你有那么一丁点的作用,麻烦动动手指转发或分享至朋友圈。如有不同意见,欢迎后台留言探讨。

原文发布于微信公众号 - 韩大(handa1740168)

原文发表时间:2016-01-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏微服务生态

为什么说Kafka使用磁盘比内存快

学习过[跟我学Kafka源码之LogManager分析]的同学一定会问为什么Kafka大量使用了磁盘作为传统意义的缓存。

692
来自专栏IT技术精选文摘

RPC原理及实现

1 简介 RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明...

4789
来自专栏玄魂工作室

怎样学Python 第二十二课 Python网络编程基础

欢迎大家回来! 在上一篇文章中,我们介绍了如何导入模块以及如何使用它们。 在本文中,我们将接触对Python黑客来说至关重要的模块:socket。 实质上,套接...

3316
来自专栏about云

kafka权威指南 第二章第6节 Kafka集群配置与调优

问题导读: 1 Kafka集群有什么优势? 2 集群中部署多少个节点合适? 3 集群针对系统如何调优? Kafka集群 对于本地的开发工作或者概念性的...

3626
来自专栏Golang语言社区

如何优化服务器的性能

一、通常服务器的性能会卡在三个地方: cpu 网络IO 磁盘IO 二、在优化性能的时候,首先要判断性能的瓶颈在上述的哪个地方。然后对症下药,按照下面的方法来优化...

3359
来自专栏Java架构师学习

面对缓存,出现这些问题你要如何思考!

缓存可以说是无处不在,比如 PC 电脑中的内存、CPU 中的二级缓存、HTTP 协议中的缓存控制、CDN 加速技术都是使用了缓存的思想来解决性能问题。 Java...

35010
来自专栏抠抠空间

python并发编程之多进程(理论)

一、什么是进程 进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu。 二、进程与程序的区别 程序:仅仅是一堆代 进程:是指打开程序运行的过程 三、...

3427
来自专栏CSDN技术头条

浏览器缓存机制剖析

“缓存一直是前端优化的主战场,利用好缓存就成功了一半。本篇从HTTP请求和响应的头域入手,让你对浏览器缓存有个整体的概念。最终你会发现强缓存,协商缓存 和 启发...

2116
来自专栏ImportSource

Redis性能问题排查解决手册

阅读目录: 性能相关的数据指标 内存使用率 命令处理总数 延迟时间 内存碎片率 回收key 总结 性能相关的数据指标 ? 通过Redis-cli命令行界面访问...

4806
来自专栏技术博文

HTTP协议详解

引言 HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善...

4419

扫码关注云+社区