zookeeper总结

ZK简介

zk背景

zookeeper实际上是yahoo开发的,用于分布式中一致性处理的框架。最初其作为研发Hadoop时的副产品。由于分布式系统中一致性处理较为困难,其他的分布式系统没有必要费劲重复造轮子,故随后的分布式系统中大量应用了zookeeper,以至于zookeeper成为了各种分布式系统的基础组件。著名的hadoop、kafka、dubbo 都是基于zookeeper而构建。

ZooKeeper是一个开源的。为分布式应用提供分布式协调的服务。它公开了一组简单的api,分布式应用程序可以基于这些api实现更高级别的服务,包括同步、维护配置、组和命名。它的设计易于编程,它使用一个遵循文件系统中常见的目录树结构的数据模型。它在Java环境中运行。

ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。

ZK原理

Zookeeper 的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。 

为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。

ZooKeeper提供了什么?

  • 文件系统
  • 通知机制

Zookeeper维护一个类似文件系统的数据结构:

每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。

ZK配置文件说明

ZK配置文件再conf文件夹下,可以修改的参数及含义如下:

  • tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。
  • initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒
  • syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10秒
  • dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
  • clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
  • server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。

zk基础架构

数据模型

ZooKeeper采用树形层次结构,在ZooKeeper中每个命名空间(Namespace)被称为ZNode,你可以这样理解,每个ZNode包含一个路径和与之相关的元数据,以及继承自该节点的孩子列表。与传统文件系统不同的是,ZooKeeper中的数据保存在内存中,实现了分布式同步服务的高吞吐和低延迟。 

znode结构 

 Znode包含文件和目录两种特性,既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分。每个Znode由3部分组成:

  1. stat:维护Znode的状态信息;
  2. data:该Znode关联的数据;
  3. acl:访问控制列表,用于控制Znode的访问权限(读写、创建、删除等);

stat存储的状态信息:

cZxid:创建Znode的事务ID;
ctime:Znode的创建时间;
mZxid:最后一次修改Znode的事务ID;
mtime:最近一次修改Znode的时间;
pZxid: Znode添加或删除子节点操作的事务ID;
cversion:Znode子节点版本;
dataversion:Znode的数据版本;
aclversion:Znode ACL的版本;
ephemeralOwner:临时节点所有者的session id,如果此节点为持久节点,则值为0;
dataLength:Znode数据长度;
numChildren:Znode子节点个数;

通过get znode获取值如下:

事务ID:能够改变zookeeper服务器状态的操作称为事务操作,一般包括数据节点创建和删除、数据节点内容更新和客户端会话创建与失效等操作。对于每一个事务操作,zookeeper都会为其分配一个全局唯一的事务ID,用Zxid来表示,通常是一个64位数字。每一个Zxid对应一次更新操作,从这些Zxid中可以间接地识别zookeeper处理这些更新操作请求的全局顺序。

znode基本操作:

create: 创建Znode (父Znode 必须已经存在);
delete: 删除Znode(该节点必须不包含任何子Znode );
exists: 测试Znode是否存在,如果存在则获取Znode状态信息;
getACL/setACL: 获取/设置Znode ACL权限;
getChildren: 获取子Znode的列表;
getData/setData: 获取/设置Znode data;
sync: 同步client和zookeeper的znode信息;

 节点类型

持久节点(PERSISTENT): 节点创建后一直存在,只能被客户端显式删除;
持久连续节点(PERSISTENT_SEQUENTIAL):同持久节点,且该节点创建子节点时,自动为子节点的命名末尾添加递增编号,用于记录下每个节点创建的先后顺序;
临时节点(EPHEMERAL):生命周期和客户端的会话绑定,客户端断开连接后自动删除节点;
临时顺序节点(EPHEMERAL_SEQUENTIAL):同临时节点,且节点命名末尾自动添加递增编号;
容器节点(CONTAINER):如果节点中最后一个子Znode被删除,将会触发删除该Znode;
持久定时节点(PERSISTENT_WITH_TTL):客户端断开连接后不会自动删除Znode,如果该Znode没有子Znode且在给定TTL时间内无修改,该Znode将会被删除;
持久顺序定时节点(PERSISTENT_SEQUENTIAL_WITH_TTL):同PERSISTENT_WITH_TTL,且Znode命名末尾自动添加递增编号;

注:

  • 临时节点:生命周期依赖于创建它们的会话,一旦会话结束,临时节点将被删除(也可以在会话未结束时手动删除)。这一特性也决定了临时节点不能包含子节点。
  • 永久节点:节点的生命周期不依赖于会话,只能被客户端显式删除。
  • 连续节点:一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。Znode命名结尾添加一个递增的计数,这个计数对于此节点的父节点来说是唯一的,它的格式为"%10d"(10位数字,没有数值的数位用0补充,例如"0000000001")。当计数值大于232-1时,计数器将溢出。连续节点主要用于同步和锁。

 

ACL

ACL(Access Control List) : ZooKeeper作为一个分布式协调框架,器内部存储的都是一些关乎分布式系统运行时状态的元数据,尤其是一些涉及分布式锁、Master选举和分布式协调等应用场景的数据,会直接影响基于ZooKeeper进行构建的分布式系统的运行状态。因此,ZooKeeper提供了一套完善的ACL权限控制机制来保障数据的安全。

一个有效的ACL信息应包括:

  • scheme(权限模式)
  • id(授权对象)
  • permission(权限)

Scheme

权限模式用来确定权限验证过程中使用的检验策略,ZooKeeper中包括以下四种权限模式:

  • IP: IP模式通过IP地址来进行权限控制,例如配置了“ip:192.168.0.110”,即标签权限控制都是针对这个IP地址的(IP模式也支持按照网段的方式进行配置);
  • Digest:以"username:password"形式的权限标识来精选权限配置,便于区分不同应用来进行权限控制;
  • World:数据节点的访问权限对所有用户开放,即所有用户都可以在不进行任何权限校验的情况下操作ZooKeeper上的数据,它只有一个权限标识,即"world:anyone";
  • Super: 超级用户,可以对ZooKeeper上任意的数据节点进行任何操作;

备注:ZooKeeper 除以上4种默认权限模式外,还提供了特殊的权限控制插件体系,允许开发人员通过指定方式对ZooKeeper的权限精选扩展。

ID

授权对象指的是权限赋予的用户或一个指定实体,例如IP地址或是机器等。在不同的权限模式下,授权对象是不同的。

Permission

权限就是指那些通过权限检查后可以被允许执行的操作,所有对数据的操作权限分为以下五类:

CREATE(c): 数据节点的创建权限,允许授权对象在该数据节点下创建子节点;
DELETE(d): 子节点的删除权限,允许授权对象删除该数据节点的子节点;
READ(r): 数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等;
WRITE(w): 数据节点的更新权限, 允许授权对象对该数据节点进行更新操作;
ADMIN(a): 数据节点的管理权限,允许授权对象对该数据节点进行ACL相关的设置操作;

设置ACL

在数据节点创建时进行ACL权限的设置:

create [-s] [-e] path data acl
create -e /zk-book init digest:foo: MiGs3Eiy1pP4rvH1Q1N1wbP+oUF8=:cdrwa
//-e:临时节点 -s:连续节点

对已经创建的数据节点进行ACL权限的设置和查看: 

set path acl
set /zk-book init digest:foo: MiGs3Eiy1pP4rvH1Q1N1wbP+oUF8=:cdrwa
getAcl path
getAcl /zk-book

会话(session)

Session 指的是 ZooKeeper 服务器与客户端会话。在 ZooKeeper 中,一个客户端连接是指客户端和服务器之间的一个 TCP 长连接。客户端启动的时候,首先会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的Watch事件通知。Session的sessionTimeout值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在sessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。

在为客户端创建会话之前,服务端首先会为每个客户端都分配一个sessionID。由于 sessionID 是 Zookeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一。

ZooKeeper将所有的会话都分配在不同的区块中(分桶管理),分配的原则是每个会话的“下次超时时间点”(ExpirationTime,会话最近一次可能超时的时间点),将ExpirationTime相同的会话放在同一区块中进行管理。ExpirationTime计算公式如下:

long now = Time.currentElapsedTime();
//timeout=SessionTimeout,该会话设置的超时时间
 Long newExpiryTime = roundToNextInterval(now + timeout);  
//expirationInterval=tickTime,默认2000毫秒
  private long roundToNextInterval(long time) {
        return (time / expirationInterval + 1) * expirationInterval;
    }

每一次会话激活过程(心跳检测)都会将会话从老的区块中取出,放入next_ExpirationTime对应的新区块中,会话激活的过程就是一次会话迁移。

监听器

ZooKeeper允许客户端向服务端注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
典型应用场景:定义一个一对多的订阅关系,能够让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态变化时,会通知所有订阅者,使他们能够做出相应的处理。

接口类Watcher用于表示一个标准的事件处理器,其定义了事件通知的逻辑,包含KeeperState和EventType两个枚举类。

  •  KeeperState:通知状态
  • EventType:事件类型

注:同一个事件类型在不同的通知状态中代表的含义有所不同。

回调方法

process()方法是Watcher接口中的一个回调方法,当ZooKeeper想客户端发送一个Watcher事件通知后,客户端就会对相应的process()方法进行回调,从而实现对事件的处理。process()方法的定义如下:

abstract public void process(WatchedEvent event);

WatchedEvent包含了每一个事件的三个基本属性:

public class WatchedEvent {
  final private KeeperState keeperState;   //通知状态
  final private EventType eventType;   //事件类型
  private String path;  //节点路径

  ...

    /**
     *  Convert WatchedEvent to type that can be sent over network
     */
    public WatcherEvent getWrapper() {
        return new WatcherEvent(eventType.getIntValue(), 
                                keeperState.getIntValue(), 
                                path);
    }
}

WatchedEvent与WatcherEvent的区别:
WatchedEvent类路径:org.apache.zookeeper.WatchedEvent;
WatcherEvent类路径:org.apache.zookeeper.proto.WatcherEvent;

public class WatcherEvent implements Record{
  private int type;
  private int state;
  private String path;

  public void serialize(OutputArchive a_, String tag) throws IOException{ ... }
  public void deserialize(InputArchive a_, String tag) throws IOException{ ... }
}

WatchedEvent是一个逻辑事件,用于服务端和客户端程序执行过程中所需的逻辑对象,而WatcherEvent因为实现了序列化接口,因此可以用于网络传输。服务端在生成WatchedEvent事件之后,会调用getWrapper方法将自己包装成一个可序列化的WatcherEvent事件,以便通过网络传输到客户端。客户端在接收到服务端的这个事件对象后,首先会将WatcherEvent事件还原成一个WatchedEvent事件,并传递给process方法处理。

Zookeeper 客户端和服务端是通过 socket 进行通信的,由于网络存在故障,所以监视事件很有可能不会成功地到达客户端,监视事件是异步发送至监视者的,Zookeeper 本身提供了保序性(ordering guarantee):即客户端只有首先看到了监视事件后,才会感知到它所设置监视的 znode 发生了变化(a client will never see a change for which it has set a watch until it first sees the watch event). 网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序。

这意味着znode 节点本身具有不同的改变方式。你也可以想象 Zookeeper 维护了两条监视链表:数据监视和子节点监视(data watches and child watches) getData() and exists() 设置数据监视,getChildren() 设置子节点监视。或者,你也可以想象 Zookeeper 设置的不同监视返回不同的数据,getData() 和 exists() 返回 znode节点的相关信息,而 getChildren() 返回子节点列表。因此, setData()会触发设置在某一节点上所设置的数据监视(假定数据设置成功),而一次成功的 create()操作则会出发当前节点上所设置的数据监视以及父节点的子节点监视。一次成功的 delete()操作将会触发当前节点的数据监视和子节点监视事件,同时也会触发该节点父节点的child watch。

Watch事件类型:

  • ZOO_CREATED_EVENT:节点创建事件,需要watch一个不存在的节点,当节点被创建时触发,此watch通过zoo_exists()设置
  • ZOO_DELETED_EVENT:节点删除事件,此watch通过zoo_exists()或zoo_get()设置
  • ZOO_CHANGED_EVENT:节点数据改变事件,此watch通过zoo_exists()或zoo_get()设置
  • ZOO_CHILD_EVENT:子节点列表改变事件,此watch通过zoo_get_children()或zoo_get_children2()设置
  • ZOO_SESSION_EVENT:会话失效事件,客户端与服务端断开或重连时触发
  • ZOO_NOTWATCHING_EVENT:watch移除事件,服务端出于某些原因不再为客户端watch节点时触发

 

选举策略

ZAB协议

ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。

6.3 ZAB 协议两种基本的模式:崩溃恢复和消息广播

ZAB协议包括两种基本的模式,分别是 崩溃恢复和消息广播。当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致

当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进人消息广播模式了。 当一台同样遵守ZAB协议的服务器启动后加人到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加人的服务器就会自觉地进人数据恢复模式:找到Leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。正如上文介绍中所说的,ZooKeeper设计成只允许唯一的一个Leader服务器来进行事务请求的处理。Leader服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议;而如果集群中的其他机器接收到客户端的事务请求,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。

Paxos核心思想:

当多数Server写成功,则任务数据写成功如果有3个Server,则两个写成功即可;如果有4或5个Server,则三个写成功即可。•Server数目一般为奇数(3、5、7)如果有3个Server,则最多允许1个Server挂掉;如果有4个Server,则同样最多允许1个Server挂掉由此,我们看出3台服务器和4台服务器的的容灾能力是一样的,所以为了节省服务器资源,一般我们采用奇数个数,作为服务器部署个数。

ZooKeeper服务器角色

 

  • Leader: 事务请求的唯一调度和处理者,保证集群事务处理的顺序性; ② 集群内部各服务器的调度者;
  • Follower:处理客户端非事务请求,转发事务请求给Leader服务器;②参与事务请求Proposal的投票;③参与Leader选举投票;
  • Observer:处理客户端非事务请求,转发事务请求给Leader服务器; ②参与事务请求Proposal的投票;

为什么需要Observer: 

Zookeeper需保证高可用和强一致性; 为了支持更多的客户端,需要增加更多Server; Server增多,投票阶段延迟增大,影响性能; 权衡伸缩性和高吞吐率,引入Observer; Observer不参与投票Observers接受客户端的连接,并将写请求转发给leader节点; 加入更多Observer节点,提高伸缩性,同时不影响吞吐率。

服务器状态:

  • LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有Leader,需要进入Leader选举流程;
  • FOLLOWING:跟随者状态,表明当前服务器角色是Follower;
  • LEADING:领导者状态,表明当前服务器角色是Leader;
  • OBSERVING:观察者状态,表明当前服务器角色是Observer;

选举过程

当超过一台ZooKeeper服务器启动,且服务器之间已经能够进行互相通信,每台服务器都试图找到一个Leader时,便需要进入Leader选举流程。ZooKeeper集群正常运行过程中,一旦选举出了Leader,那么所有服务器的集群角色一般不会发生变化(即使集群中有非Leader角色的服务器挂了或者有新机器加入到集群)。但是当Leader服务器挂了,那么整个集群将无法对外提供服务,直到新一轮的Leader选举完毕。服务器启动时期的Leader选举与运行期间的Leader选举过程基本一致。

选举流程如下:

1. 自增选举轮次
FastLeaderElection.logicalclock用于标识当前Leader的选举次数,ZooKeeper规定了所有有效的投票都必须在同一轮次中。ZooKeeper在开始新一轮的投票(调用FastLeaderElection.lookForLeader方法)时,会首先对logicalclock进行自增操作。

2. 初始化选票

this.id = id;     //唯一标识一台ZooKeeper服务器(sid),与myid值一致(server.id)
this.zxid = zxid;    //事务ID,用来唯一标识一次服务器状态的变更
this.electionEpoch = -1;    //当前服务器的选举轮次
this.peerEpoch = peerEpoch;    //被推举的服务器的选举轮次
this.state = ServerState.LOOKING;

 3. 发送初始化投票

每台ZooKeeper服务器都会发起第一次投票(投给自己),然后将初始化投票放入sendqueue队列中。

LinkedBlockingQueue<ToSend> sendqueue:选票发送队列,用于保存待发送的选票;
LinkedBlockingQueue<Notification> recvqueue:选票接收队列,用于保存接收到的外部选票;
WorkerSender ws:选票发送器,后台线程;
WorkerReceiver wr:选票接收器,后台线程;

4. 接收外部投票
每台ZooKeeper服务器都会不断从recvqueue队列中获取外部投票,如果服务器发现无法获取任何外部投票,那么就会立即确认是否和集群中其他服务器保持有效连接。

5. 判断选举轮次
发送完初始化选票后,开始处理外部投票(其他服务器发送的投票)。ZooKeeper规定了所有有效的投票都必须在同一选举轮次中,在处理外部投票时,会根据选举轮次进行不同的处理。 

外部投票的选举轮次大于内部投票
立即跟新自己的选举轮次(logicalclock),并且清空所有已经收到的投票,然后使用初始化的选票来进行PK是否变更内部投票(服务器自身当前的投票),最终将内部投票发送出去。
外部投票的选举轮次小于内部投票
忽略该外部投票,不做任何处理,继续处理下一个外部投票。
外部投票的选举轮次与内部投票一致
开始进行选票PK。

6. 选票PK
确定当前服务器是否需要变更投票(FastLeaderElection.totalOrderPredicate)

规则1:如果外部投票被推举的Leader服务器选举轮次大于内部投票(newEpoch > curEpoch),需要变更投票;
规则2:如果如果选举轮次一致,则比较两者ZXID(newZxid > curZxid),如果外部投票的ZXID大于内部投票,则需要变更投票;
规则3:如果两者的ZXID也一致,则比较两者的SID,如果外部投票的SID大于内部投票,则需要变更投票;

7. 变更投票
选票PK后,如果确定了外部投票所推举的服务器更适合成为Leader,那么就需要变更投票——使用外部投票的选票信息来覆盖内部投票。变更完成后,需要将变更后的内部投票再次发送出去。

8. 选票归档并统计
无论是否进行投票变更,都会将刚刚处理的那份外部投票放入”投票集合“recvset(Map<Long, Vote> recvset = new HashMap<Long, Vote>();)进行归档(recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));)。归档后开始本次投票统计,如果集群中有过半(n/2+1)服务器认可了当前的投票termPredicate(recvset, new Vote(proposedLeader, proposedZxid, logicalclock.get(), proposedEpoch)),则更新服务器状态并终止投票。

9. 更新服务器状态
统计投票后,如果确定有过半(n/2+1)服务器认可了当前的投票,则需要更新服务器状态。

集群高可用

分布式与数据复制

Zookeeper作为一个集群提供一致的数据服务,自然,它要在所有机器间做数据复制。数据复制的好处:

1、容错:一个节点出错,不致于让整个系统停止工作,别的节点可以接管它的工作;

2、提高系统的扩展能力 :把负载分布到多个节点上,或者增加节点来提高系统的负载能力;

3、提高性能:让客户端本地访问就近的节点,提高用户访问速度。

从客户端读写访问的透明度来看,数据复制集群系统分下面两种:

1、写主(WriteMaster) :对数据的修改提交给指定的节点。读无此限制,可以读取任何一个节点。这种情况下客户端需要对读与写进行区别,俗称读写分离

2、写任意(Write Any):对数据的修改可提交给任意的节点,跟读一样。这种情况下,客户端对集群节点的角色与变化透明。

对zookeeper来说,它采用的方式是写任意。通过增加机器,它的读吞吐能力和响应能力扩展性非常好,而写,随着机器的增多吞吐能力肯定下降(这也是它建立observer的原因),而响应能力则取决于具体实现方式,是延迟复制保持最终一致性,还是立即复制快速响应。

Zookeeper 的读写机制

Zookeeper是一个由多个server组成的集群;一个leader,多个follower;每个server保存一份数据副本;全局数据一致;分布式读写;更新请求转发,由leader实施

数据一致性

更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行(ZXID保证);数据更新原子性,一次数据更新要么成功,要么失败;全局唯一数据视图,client无论连接到哪个server,数据视图都是一致的(全局一致性);实时性,在一定事件范围内,client能读到最新数据。

应用场景

数据发布/订阅

数据发布/订阅系统,即所谓的配置中心,发布者将数据发布到ZooKeeper的一个或多个节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
发布/订阅系统一般有两种设计模式,分别是推(Push)模式和拉(Pull)模式。在推模式中,服务端主动将数据更新发送给所有订阅的客户端;而拉模式则是有客户端主动发起请求来获取最新数据,通常客户端都采用定时进行轮询拉取的方式。而ZooKeeper采用的是推拉相结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据。

命名服务

通过调用ZooKeeper节点创建的API接口可以创建一个顺序节点,并且在API返回值中会返回这个节点的完整名字。在ZooKeeper中,每一个数据节点都能维护一份子节点的顺序序列,当客户端对其创建一个顺序子节点的时候,ZooKeeper会自动以后缀的形式在其子节点上添加一个序号。

分布式协调/通知

ZooKeeper的Watcher注册与异步通知机制,能够很好地实现分布式环境下不同机器,甚至是不同系统之间的协调与通知,从而实现对数据变更的实时处理。基于ZooKeeper实现分布式协调与通知功能,通常的做法是不同的客户端都对ZooKeeper上同一个数据节点进行Watcher注册,监听数据节点的变化(包括数据节点本身及其子节点),如果数据节点发生变化,所有订阅的客户端都能够接收到相应的Watcher通知,并做出相应的处理。

 

 Master选举

ZooKeeper的强一致性能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即ZooKeeper将会保证客户端无法重复创建一个已经存在的数据节点。也就是说,如果同时有多个客户端请求创建同一个节点,那么最终一定只有一个客户端请求能够创建成功,那么这个客户端所在的机器就成为了Master。同时,其他没有在ZooKeeper上成功创建节点的客户端,都会注册一个子节点变更的Watcher,用于监控当前Master机器是否存活,一旦发现当前的Master挂了,其余的客户端将会重新进行Master选举。

分布式锁

分布式锁:控制分布式系统之间同步访问共享资源的一种方式,如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干扰,以保证一致性。
排他锁:所有客户端都会试图通过调用create接口创建临时子节点,ZooKeeper会保证所有的客户端中最终只有一个客户端能够创建成功,那么就可以认为该客户端获取了锁。同时,所有没有获取到锁的客户端需要注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。当获取锁的客户端机器宕机或政策执行完业务逻辑后,客户端会主动删除自己创建的临时节点。其他客户端再次重新发起获取锁。
共享锁:所有客户端在需要获取共享锁时创建一个临时顺序节点(节点名区分读写请求)。根据共享锁的定义,不同的事务都可以同时对同一个数据对象进行读取操作,而更新操作必须在当前没有任何读取操作的情况下进行。基于以上原则,则可以通过ZooKeeper的节点来确定分布式读写顺序。

共享锁具体策略:需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不是那么它就调用 exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。 

 

 

分布式锁参考文档:https://baijiahao.baidu.com/s?id=1610572906386264645&wfr=spider&for=pc

其他分布式系统中的应用场景

HDFS-NameNode: Active/Standby选举 < /hadoop-ha>;
YARN-ResourceManager: Active/Standby选举< /yarn-leader-election>;
Hive-HiveServer2: HA高可用配置< /hiveserver2>;
HBase:Master Active/Standby选举,基础元数据管理 < /hbase-unsecure>;
kafka: 元数据管理,消费索引维护 < /brokers, /consumers>;

参考文档:

https://www.jianshu.com/p/99d1ef8efebc

https://blog.youkuaiyun.com/java_66666/article/details/81015302

http://www.cnblogs.com/felixzh/p/5869212.html

https://blog.youkuaiyun.com/u010080215/article/details/68945416

https://www.jianshu.com/p/b5f99fdb1957

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值