zookeeper概念

为什么使用ZooKeeper

       我们知道要写一个分布式应用是非常困难的,主要原因就是局部故障。一个消息通过网络在两个节点之间传递时,网络如果发生故障,发送方并不知道接收方是否接收到了这个消息。他可能在网络故障迁就收到了此消息,也可能没有收到,又或者可能接收方的进程死了。发送方了解情况的唯一方法就是再次连接发送方,并向他进行询问。这就是局部故障:根本不知道操作是否失败。因此,大部分分布式应用需要一个主控、协调控制器来管理物理分布的子进程。目前,大部分应用需要开发私有的协调程序,缺乏一个通用的机制。协调程序的反复编写浪费,且难以形成通用、伸缩性好的协调器。协调服务非常容易出错,并很难从故障中恢复。例如:协调服务很容易处于竞态1甚至死锁2。Zookeeper的设计目的,是为了减轻分布式应用程序所承担的协调任务。

       Zookeeper并不能阻止局部故障的发生,因为它们的本质是分布式系统。他当然也不会隐藏局部故障。ZooKeeper的目的就是提供一些工具集,用来建立安全处理局部故障的分布式应用。

ZooKeeper概述

       ZooKeeper是一个分布式小文件系统,并且被设计为高可用性。通过选举算法和集群复制可以避免单点故障,由于是文件系统,所以即使所有的ZooKeeper节点全部挂掉,数据也不会丢失,重启服务器之后,数据即可恢复。另外ZooKeeper的节点更新是原子的,也就是说更新不是成功就是失败。通过版本号,ZooKeeper实现了更新的乐观锁,当版本号不相符时,则表示待更新的节点已经被其他客户端提前更新了,而当前的整个更新操作将全部失败。当然所有的一切ZooKeeper已经为开发者提供了保障,我们需要做的只是调用API。与此同时,随着分布式应用的的不断深入,需要对集群管理逐步透明化监控集群和作业状态,可以充分利ZK的独有特性。

       ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。它的目标就是封装好复杂、易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

       注意:ZooKeeper性能上的特点决定了它能够用在大型的、分布式的系统当中。从可靠性方面来说,它并不会因为一个节点的错误而崩溃。除此之外,它严格的序列访问控制意味着复杂的控制原语可以应用在客户端上。ZooKeeper在一致性、可用性、容错性的保证,也是ZooKeeper的成功之处,它获得的一切成功都与它采用的协议——Zab协议是密不可分的,这些内容将会在后面介绍。

       ZooKeeper一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心,服务生产者将自己提供的服务注册到ZooKeeper中心,服务的消费者在进行服务调用的时候先到ZooKeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。

zookeeper的特性

顺序一致性

  • 从同一个客户端发起的事务请求,最终会严格按照顺序被应用到zookeeper中

原子性

  • 所有的事务请求的处理结果在整个集群中的所有机器上的应用情况是一致的,也就是说,要么整个集群中的所有机器都成功应用了某一事务、要么全都不应用

可靠性

  • 一旦服务器成功应用了某一个事务数据,并且对客户端做了响应,那么这个数据在整个集群中一定是同步并且保留下来的

实时性

  • 一旦一个事务被成功应用,客户端就能够立即从服务器端读取到事务变更后的最新数据状态;(zookeeper仅仅保证在一定时间内,近实时)

集群中的角色

在ZooKeeper集群当中,集群中的服务器角色有两种Leader和Learner,Learner角色又分为Observer和Follower,具体功能如下:

  • Leader (领导者) :为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。
  • Follower(跟随者):为客户端提供读服务,如果是写服务则转发给Leader。在选举过程中参与投票。
  • Observe(观察者):为客户端提供读服务器,如果是写服务则转发给Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能此角色于zookeeper3.3系列新增的角色。
  • client(客户端)连接zookeeper服务器的使用着,请求的发起者。独立于zookeeper服务器集群之外的角色

下面通过一张图系统架构图了解一下各个角色所处的位置。

ZooKeeper的架构图中我们需要了解和掌握的主要有:

  1. ZooKeeper分为服务器端(Server) 和客户端(Client),客户端可以连接到整个 ZooKeeper服务的任意服务器上(除非 leaderServes 参数被显式设置, leader 不允许接受客户端连接)。
  2. 客户端使用并维护一个 TCP 连接,通过这个连接发送请求、接受响应、获取观察的事件以及发送心跳。如果这个 TCP 连接中断,客户端将自动尝试连接到另外的 ZooKeeper服务器。客户端第一次连接到 ZooKeeper服务时,接受这个连接的 ZooKeeper服务器会为这个客户端建立一个会话。当这个客户端连接到另外的服务器时,这个会话会被新的服务器重新建立。
  3. 上图中每一个Server代表一个安装ZooKeeper服务的机器,即是整个提供ZooKeeper服务的集群(或者是由伪集群组成);
  4. 组成ZooKeeper服务的服务器必须彼此了解。 它们维护一个内存中的状态图像,以及持久存储中的事务日志和快照, 只要大多数服务器可用,ZooKeeper服务就可用;
  5. ZooKeeper启动时,将从实例中选举一个leader,leader 负责处理数据更新等操作,一个更新操作成功的标志是当且仅当大多数Server在内存中成功修改数据。每个Server在内存中存储了一份数据。
  6. ZooKeeper是可以集群复制的,集群间通过Zab协议(Zookeeper Atomic Broadcast)来保持数据的一致性;
  7. Zab协议包含两个阶段:leader选举阶段和Atomic Broadcast阶段。

Zookeeper具有高可用性能的主要原因有以下几点:

  1. Zookeeper集群的任意一个服务端节点都可以直接响应客户端的读请求,并且可以通过增加节点进行横向扩展。这是其吞吐量高的主要原因。
  2. Zookeeper将全量数据存储于内存中,从内存中读取数据不需要进行磁盘IO,速度要快得多。
  3. Zookeeper放松了对分布式数据的强一致性要求,即不保证数据实时一致,允许分布式数据经过一个时间窗口达到最终一致,这也在一定程度上提高了其吞吐量。

zoo.cfg配置文件分析

  • tickTime=2000     zookeeper中最小的时间单位长度 (ms)

  • initLimit=10         Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader允许F在 initLimit 时间内完成这个工作。通常情况下,我们不用太在意这个参数的设置。如果ZK集群的数据量确实很大了,F在启动的时候,从Leader上同步数据的时间也会相应变长,因此在这种情况下,有必要适当调大这个参数了。(No Java system property)

  • syncLimit=5        在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。如果L发出心跳包在syncLimit之后,还没有从F那里收到响应,那么就认为这个F已经不在线了。注意:不要把这个参数设置得过大,否则可能会掩盖一些问题。

  • dataDir=/tmp/zookeeper         表示zookeeper服务器存储快照文件的目录

  • dataLogDir          表示配置 zookeeper事务日志的存储路径,默认指定在dataDir目录下

  • clientPort            表示客户端和服务端建立连接的端口号: 2181