zookeeper客户端

 这个系列主要分析ZooKeeper的源代码来看如何解决分布式系统的一些问题,比如:
如何构建Master-Slave集群
如何实现CAP中的最终一致性和分区容忍性
如何在分布式系统中进行数据同步
如何在集群中选主
如何设计心跳监控
如何实现高可用的持久化存储,比如事务日志文件,快照等
如何实现高性能的网络编程
如何设计分布式系统的客户端,比如实现负载均衡,失效转移
 Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理
 
zookeeper 客户端:
    Client中在终端输入指令后,会被封装成一个Request请求,通过submitRequest,进一步被封装成Packet包,提交给SendThread处理。
    SendThread通过doTransport将Packet发送给Server,并通过readResponse获取结果,解析成一个Event,再将Event加入EventThread的队列中等待执行。
    EventThread通过processEvent消费队列中的Event事件。
    (1) Client端发送Request(封装成Packet)请求到Zookeeper 
    (2) Zookeeper处理Request并将该请求放入Outgoing Queue(顾名思义,外出队列,就是让Zookeeper服务器处理的队列), 
    (3) Zookeeper端处理Outgoing Queue,并将该事件移到Pending Queue中 
    (4) Zookeeper端消费Pending Queue,并调用finishPacket(),生成Event 
    (5) EventThread线程消费Event事件,并且处理Watche

1,ZooKeeperMain中主要由两部分构成:connectToZK(cl.getOption("server")):构造一个ZooKeeper对象,同ZooKeeperServer进行建立通信连接。
    2,while ((line = (String)readLine.invoke(console, getPrompt())) != null) {
    executeLine(line);通过反射调用jline.ConsoleReader类,对终端输入进行读取,然后通过解析单行命令,调用ZooKeeper接口。
ZooKeeper 类:类中,系统新建了两个线程sendThread = new SendThread(clientCnxnSocket);SendThread 负责将ZooKeeper的请求信息封装成一个Packet,发送给 Server ,并维持同Server的心跳
    eventThread = new EventThread();EventThread负责解析通过通过SendThread得到的Response,之后发送给Watcher::processEvent进行详细的事件处理。
3,SendThread 的主要作用除了将Packet包发送给Server之外,还负责维持Client和Server之间的心跳,确保 session 存活
    SendThread是一个线程类:
    1. 创建同 Server 之间的 socket 链接 :ZooKeeper通过获取ZOOKEEPER_CLIENT_CNXN_SOCKET变量构造了一个ClientCnxnSocket对象,默认情况下是ClientCnxnSocketNIO类                                    
    2. 判断链接是否超时 :在SendThread::run中,可以看到针对链接是否建立分别有readTimeout和connetTimeout 两种超时时间,一旦发现链接超时,则抛出异常,终止 SendThread。
    3. 定时发送心跳任务 
    4. 将ZooKeeper指令发送给Server
ZooKeeper Client 的源码很简单,拥有三个独立线程分别对命令进行处理,分发和响应操作,在保证各个线程相互独立的基础上,尽可能避免了多线程操作中出现锁的情况。

    在入口方法中,单机启动使用ZooKeeperServerMain,最终调用ZookeeperServer的startup()方法来RequestProcessor;
    集群启动时调用QuorumPeer的start方法,接着也是调用ZookeeperServer的startup()方法来RequestProcessor,最后调用选举算法选出leader。
zookeeper 服务端:
    1.简单:zookeeper运行分布式进行通过一个共享的层次命名空间来进行协作,该命名空间的组织类似于标准的文件系统。
    命名空间包括数据注册器(称之为znode),在zookeeper看来,这类似于文件和目录。
    与典型的文件系统设计用来存储不同的是,zookeeper数据是存放在内存中,这意味着zookeeper可以实现很高的吞吐量和低延迟。
    ZooKeeper 实现在高性能,高可用性,严格有序的访问方面有很大的优势。在性能方面的优势使它可以应用在大型的的分布式系统。
    在可靠性方面,避免单点故障。严格的顺序访问使它在客户端可以实现复杂的同步原语。
    2. 可复制:类似于分布式进程的协作,zookeeper本身很容易在一组主机(称之为集合)中实现复制
        组成ZooKeeper服务的一组服务器都必须知道对方的。它们保存了内存映像的状态,以及在持久存储中的事务日志和快照。只要大部分的服务器可用,ZooKeeper服务将可用。
        客户端连接到一台ZooKeeper服务器。客户端维护一个TCP连接,通过它发送请求,得到响应,得到监视事件,并发送心跳。如果TCP连接到服务器中断,客户端可以连接到不同的服务器。
    3. 有序:ZooKeeper给每次更新使用数字打标记,它反映了所有zookeeper事务的顺序。随后的操作可以使用这些顺序来实现更高级别的抽象,如同步原语。
    4.快速:它特别快,在“读为主”的工作中,ZooKeeper 应用程序运行在数千台机器,它在读远比写更多的时候(在10:1的比例)表现的最好。
数据模型与层次命名空间
    ZooKeeper提供的名称空间更像是一个标准的文件系统。一个名字是一个由一个(或)分隔的路径元素的序列。zookeeper名称空间的每个节点由路径来标示。
节点和临时节点
  不像标准的文件系统,在ZooKeeper 命名空间中每个节点都有与它相关的数据以及子节点。它就像这样一个文件系统,它允许一个文件也可以是一个目录。(zookeeper是用来储存协作数据:状态信息,配置,位置信息等,因此,存储在每个节点的数据通常是很小的,在字节到千字节范围。)我们使用术语znode来表明我们谈论的是zookeeper数据节点。
  znodes保存一个数据结构,该数据结构包括数据变化的版本号和时间戳,ACL的变化,这些信息允许缓存验证和协作更新。一个znode的数据的每次变化,版本号的增加。例如,每当客户检索数据时,它也接收到数据的版本。
  在一个命名空间中的每个节点存储的数据的读写都是原子性的。读获取一个Znode所有的数据字节;写替换所有的数据。每个节点都有一个访问控制列表(ACL),限制谁可以做什么。
  zookeeper也有临时节点的概念。这些znodes只要创建znode的会话是活跃的,它就存在的。当会话结束时,这些znode被删除。
条件更新与监控
    ZooKeeper支持监控的概念。客户端可以在一个znode上设置一个监控。当znode发生变化时会触发或者移除监控。
    当监控触发时,客户端接收到一个报文,表明znode发生了变化。若客户端和一个zookeeper服务器的连接损坏时,客户端接收到一个本地通知。        
保障
    ZooKeeper非常快速和简单. 虽然它的目标是为建设更为复杂的服务,例如同步,它提供了一系列的保证。这些是:
    顺序一致性----客户端的更新将被应用于它们被发送的命令中。
    原子性-- - 更新要么成功要么失败,不存在部分成功或者部分失败.
    单系统映像 ---- 不管连接到哪台服务器,客户端看到相同的服务视图.
    可靠性---- 一旦一个更新发生,直到下次一个客户端重新了更新,否则从更新的时间后都会保持。
    及时性--- - 在一定时间范围内保证系统的客户视图是最新的.    
简单api
    zookeeper设计目标之一是提供一个简单的编程接口,因此,它只支持下面这些操作:
    create:在节点树上某个位置上创建一个新的节点。
    delete删除一个节点
    exists测试某位置的节点是否存在
    get data从一个节点读取数据
    set data向一个节点写入数据
    get children检索一个节点的一组子节点
    sync等待数据传播至一致。    
replicated database是一个包含整个数据数的内存数据库. 为了可复原,更新被写到磁盘上,写操作在应用到内存数据库之前,先序列化到磁盘。
    每个zookeeper服务器给所有的客户端提供服务。客户端恰恰连接到一个服务器来提交请求。读请求由每个服务器数据库的本地复制提供服务。
    写请求改变了服务的状态,由request processor来处理。
    作为通信协议的一部分,所有客户端的写请求由一个单独的服务器处理,这个服务器是zookeeper的leader服务器,
    其余的zookeeper服务器叫做follower,follower从leader接收消息并达成消息传输。消息层在失败后替换leader并同步到连接到leader所有的follower。
    ZooKeeper使用自定义的原子消息协议. 因消息层是原子性的, ZooKeeper 可以保证本地复制不会冲突. 
    当leader接收到一个写请求,当写操作应用到系统时,leader计算出系统的状态,并转化成一个捕捉新状态的事务.        
ZOOMAIN 是启动程序的入口:
    initializeAndRun(String[] args)主要分3部分:
    1. 解析配置文件,默认的配置文件为上一级目录 parse(String path) 
    2. 启动安排清除任务
    3,启动zookeeper 服务器:1, 启动时先从内存数据库中恢复数据loadDataBase();启动NettyServerCnxnFactory绑定服务;startLeaderElection() 选举算法
Zookeeper-持久化    
    Zookeeper虽然是内存数据库,但为了保证高可靠性,其同时提供了持久化功能,通过快照和事务日志将数据保存在磁盘中.

事务日志
    1,每个执行的事务都会写入到事务日志中,其存储位置由dataLogDir配置,当未配置dataLogDir时,使用dataDir作为存储目录,
    由于事务日志的写入速度较为影响Zookeeper的性能,可以将dataLogDir单独配置到一块磁盘上
    2,由于事务日志要不断的写入,会触发底层磁盘I/O为文件开辟新的磁盘块,为了减少分配新磁盘块对写入的影响,
    Zookeeper使用预分配策略,默认每次分配新文件或扩容时,一次分配64MB
    3,扩容事务日志文件时机:初始化事务日志文件时为其分配64MB,当写入事务日志的过程中,发现剩余可写入空间小于4KB时,进行扩容,依然是为事务日志文件增加64MB
    ,4,生成新事务日志文件时机:即使当前事务日志文件可写空间较少,也只会进行扩容,不会生成新的事务日志文件.在经过snapCount次事务后,会生成快照文件,
    但同时将当前事务日志的输出流置null,这样下次写事务日志时自动创建新的事务日志文件
    5为了便于快速根据zxid找到存储该zxid对应事务的事务日志文件,事务日志文件的命名是有意义的,事务日志文件的命名为log.{zxid},后缀是该日志文件存储的第一个事务的zxid
快照
    生成快照文件时机:经过snapCount次事务后,会生成快照文件
    和事务日志文件一样,快照文件的命名也是有意义的,命名为snapShot.{zxid},后缀时该快照文件生成时已执行的最新的事务的zxid,即[1,zxid]的所有事务已应用到DataTree
相关类
    TxnLog:负责处理事务日志
    SnapShot:负责处理快照
    FileTxnSnapLog:组合TxnLog和SnapShot,是Zookeeper上层服务器和底层数据存储之间的对接层
    FileTxnSnapLog可以完成数据恢复,持久化,日志截断等功能,下面则依次介绍何时执行这些操作以及如何执行.
数据恢复总流程    
    在QuorumPeerMain启动ZookeeperServer的过程中,需要从磁盘中恢复数据,恢复数据共有两个步骤
    从快照中恢复DataTree,返回通过快照恢复的数据的最大zxid,从快照恢复数据,最多获取100个最新的快照文件,
    但若最新的快照文件通过正确性校验,则只解析最新的一个文件;若100个快照文件都是无效的,则认为无法从快照中恢复数据.
    从事务日志中获取大于zxid的所有日志,将其应用到步骤1中初步恢复的DataTree中,快照文件是延迟创建的(快照文件创建过程见下文持久化部分).
    所以在快照执行过程中,有可能将其后的事务的运行结果也持久化到快照中.因此,在快照恢复时,NONODE/NODEEXISTS就可能发生,此时可忽略此类错误.
持久化

Zookeeper启动时创建请求处理链处理客户端请求,单机模式下请求处理链为:PrepRequestProcessor->SyncRequestProcessor->FinalRequestProcessor.其中,SyncRequestProcessor主要完成两个工作

将事务请求记录到事务日志文件中去

    为了提高事务日志持久化的性能,Zookeeper使用批处理策略,并不是每一个request都立即持久化到磁盘中,而且持久化到磁盘的优先级较低.
    只有当没有待处理的request或者积攒了1000个待刷新的request时,才会执行flush()
    触发Zookeeper进行数据快照
    为了防止集群中所有机器在同一时刻进行数据快照,对是否进行数据快照增加随机因素
    进行数据快照时同时将当前事务日志的输出流置null,这样下次写事务日志时创建新的事务日志文件
    启动一个线程并行执行快照任务,不会阻塞正常的处理流程
    若上一次快照任务尚未完成,则此次快照任务不会执行
所谓整个集群是否可用,隐含的一个意思就是整个集群还能够选举出一个"Leader"。ZooKeeper默认设置的是采用Majority Qunroms的方式来支持Leader选举。在ZooKeeper中Quorums有2个作用:
    1. 集群中最少的节点数用来选举Leader保证集群可用
    2. 通知客户端数据已经安全保存前集群中最少数量的节点数已经保存了该数据。
    一旦这些节点保存了该数据,客户端将被通知已经安全保存了,可以继续其他任务。而集群中剩余的节点将会最终也保存了该数据
Split-Brain问题说的是1个集群如果发生了网络故障,很可能出现1个集群分成了两部分,而这两个部分都不知道对方是否存活,不知道到底是网络问题还是直接机器down了,所以这两部分都要选举1个Leader,而一旦两部分都选出了Leader, 并且网络又恢复了,那么就会出现两个Brain的情况,整个集群的行为不一致了。
    所以集群要防止出现Split-Brain的问题出现,Quoroms是一种方式,即只有集群中超过半数节点投票才能选举出Leader。
    ZooKeeper默认采用了这种方式。更广义地解决Split-Brain的问题,一般有3种方式
    1.? Quorums
    2. 采用Redundant communications,冗余通信的方式,集群中采用多种通信方式,防止一种通信方式失效导致集群中的节点无法通信。
    3. Fencing, 共享资源的方式,比如能看到共享资源就表示在集群中,能够获得共享资源的锁的就是Leader,看不到共享资源的,就不在集群中
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值