/ZookeeperTree

Zookeeper相关技术研究

Primary LanguageJava

ZookeeperTree

Zookeeper相关技术研究

Zookeeper集群部署

zookeeper基础
    1)Api概述
    2)znode的不同类型
    3)监视与通知
	
zookeeper架构
    1)zookeeper仲裁
    2)会话
   Zookeeper从文件系统API得到启发,提供一组简单的API,使得开发人员可以实现通用的协作任务,
包括选举主节点,管理组内成员关系,管理元数据等。
   Zookeeper并不直接暴露原语,取而代之,它暴露了由一小部分调用方法组成的类似文件系统的API
,以便允许应用实现自己的原语。
Znode的不同类型
   1)持久节点和零时节点
      Znode节点可以使持久的,也可以是零时的,持久的znode只能通过调用delete来进行删除,
      零时的znode与之相反,当创建该节点的客户端崩溃或者关闭了与Zookeeper的连接时,这个
      节点就会被删除。
 
      持久的znode是一种非常有用的znode,可以通过持久类型的znode为应用保存一些数据,即使
      znode的创建者不再属于应用系统,数据也可以保存下来而不丢失。

   Znode节点分类
      1)持久的
      2)零时的
      3)持久有序的
      5)零时有序的
监视与通知

    客户端向Zookeeper注册需要接受通知的znode,通过对znode设置监视点来接受通知,监视点是
    一个单次触发的操作,也就是说监视点会触发一个通知,为了接受更多通知,客户端必须在每次
    通知后设置一个新的监视点。

    Zookeeper可以定义不同类型的通知,这依赖于设置监视点对应的通知类型。客户端可以设置多种
    监视点,如监控znode的数据变化,监控znode子节点的变化,监控znode的创建与删除,为了设置
    监视点,可以使用任何API中的调用来读取Zookeeper的状态。在调用这些API时,传入一个
    watcher对象或使用默认的watcher.
版本
    每个znode都有一个版本号,它随着每次数据变化而递增。两个API操作可以有条件的执行:
       setData, delete  这两个调用以版本号作为传入参数。只有当转入参数的版本号与服务器
    上的版本号一致时调用才会成功。
Zookeeper的架构

   Zookeeper仲裁模式(多节点的集群模式)
   在仲裁模式下,Zookeeper复制集群中的所有服务器的数据树。但如果一个客户端等待每个服务器完
成数据保存后再继续,延迟问题将无法接受。在公共管理领域,法定人数是指进行一项投票所需的立法
者的最小数量。而在Zookeeper集群中,则是指为了使Zookeeper工作必须有效运行的服务器的最小值。
这个数字也是服务器告知客户端安全保存数据前,需要保存客户端数据的服务器的最小个数。
会话
   在对Zookeeper集合执行任何请求前,一个客户端必须先与服务端建立会话。客户端提交
给Zookeeper的所有操作均关联正在一个会话上,当一个会话因为某种原因而终止时,在这个会话期间
建立的零时节点就会消失。

   当客户端通过一个特定语言套件来创建一个Zookeeper句柄时,它就会通过服务器建立一个会话。
客户端初始连接到集合中某一个服务器或一个独立的服务器。客户端通过TCP协议域服务器进行连接并
通信,但当会话无法与当前连接的服务器继续通信时,会话就可能转移到另一个服务器上。Zookeeper
客户端透明的转移一个会话到不同的服务器。

   会话提供了顺序保障,这就意味着同一个会话中的请求会以FIFO顺序执行。但是在多个会话之间不
保序。
会话的状态和声明周期

   1:Connecting
   2: Connected
   3: Closed
   5: NOT_Connected

   创建会话时,你需要设置会话超时时间这个重要的参数,这个参数设置了Zookeeper服务器允许会话
被声明为超时之前存在的时间。如果经过时间t之后服务接收不到这个会话的任何消息,服务就会声明会话
过期。而在客户端,如果经过2/3t时间后,Zookeeper客户端开始寻找其他的服务器,而此时还有t/3的
时间去寻找。

   当尝试连接到一个不同的服务器时,非常重要的是,这个服务器的Zookeeper状态要与最后连接的
服务器的Zookeeper状态保持最新。客户端不能连接到这样的服务器:
   它未发现更新而客户端已经发现的更新。
   Zookeeper通过在服务器中排序更新操作来决定状态是否最新。Zookeeper确保每一个变化相对于
所有其他已执行的更新是完全有序的。
   在Zookeeper实现中,系统根据每一个更新建立的顺序来分配给事务标识符。
通过创建零时节点的方式,使用Zookeeper实现分布式锁。
群首选举和外部资源

   Zookeeper为所有客户端提供了系统的一致性视图,只要客户端与Zookeeper进行任何交互操作,
Zookeeper都会保持同步。然而Zookeeper无法保护与外部设备的交互操作,这种缺乏保护的特殊问题
的说明,在实际环境中也经常被发现,常常用于主机过载的情况下。

   当运行客户端进程的主机发生过载,就会开始发生交换,系统颠覆或因已经超负载的主机资源的竞争
而导致的进程延迟,这些都会影响与Zookeeper交互的及时性。
   一方面,Zookeeper无法及时地与Zookeeper服务器发送心跳消息,导致Zookeeper的会话超时。
   另一方面,主机上本地线程的调度会导致不可预知的调度:一个应用线程认为会话仍处于活动状态,
并持有主节点,即使Zookeeper线程有机会运行时才会通知会话已经超时。
角色
   1) 群首
   2)追随者
   3)观察者
      观察者不会参与决策哪些请求可被接受的过程,只是观察决策的结果。

   Zookeeper服务器会在本地处理只读请求,加入一个服务器接收到客户端的getData请求,服务器读
取该状态信息,并将这些信息返回给客户端。因为服务器会在本地处理请求,所以Zookeeper在处理以
只读为主要负载时,性能会很高。我们还可以增加更多的服务器到Zookeeper集群中,这样就可以处理
更多的请求,大幅提高整理处理能力。

   那些会改变Zookeeper状态的客户端请求(create, delete, setData)将会被转发给群首,群首
执行相应的请求,并形成状态的更新,我们称为事务。

   请求的事务包含了两个重要字段:
       1:节点中心的数据字段值
       2:该节点的版本号。

   当处理该事务时,服务端将会使用十五中的数据信息替换节点中原来的数据信息,并会用事务中的
版本号更新该节点,而不是增加版本号的值。

   一个事务为一个单位,也就是说所有的变更处理需要以原子方式执行。
   同时一个事务还有幂等性,也就是说,我们可以对同一个事务执行两次,我们得到的结果还是一样的
。
   当群首产生了一个事务,就会为该事务分配一个标识符,我们称之为Zookeeper会话ID(zxid),通
过Zxid对事务进行标识,就可以按照群首所指定的顺序在各个服务器中按序执行。服务器之间在进行新的
群首选举时也会交换zxid信息,这样就可以知道哪个服务器接收了更多的事务,并可以同步他们之间的
状态信息。
   zxid为一个long64位的整数,分为两部分:
       时间戳
       计数器
群首选举策略

     群首为集群中的服务器选择出来的一个服务器,并会一直被集群所认可。设置群首的目的是为了
对客户端发起的ZooKeeper状态变更请求进行排序,包括create, setData, delete操作,群首为
每个请求转换为一个事务,将这些事务发送给追随者,确保集群按照群首确定的顺序接收并处理这些
事务。
     法定数量是能够交错在一起,以避免我们所说的脑裂问题。

     每个服务器启动之后进入LOOKING状态,开始选举一个新的群首或者查找已经存在的群首,如果群
  首已经存在,其他服务器就会通知这个新启动的服务器,告知哪个服务器时群首,与此同时,新的服务
  器会与群首建立连接,以确保自己的状态与群首保持一致。

     如果所有的服务器均处于LOOKING状态,这些服务器之间就会进行通信来选举出一个群首,通过信
  息交换对群首选举达成共识的选择,在本次选举过程中胜出的服务器就进入LEADING状态,而集群中
  的其他服务器进入FOLLOWING状态。

     对群首的选举的信息,我们称之为群首选举通知消息。该协议非常简单,当一个服务器进
  入LOOKING状态,就会发送向集群中每个服务器发送一个通知消息,该消息包括该服务器的投票信息,
  投票中包含服务器标识符(sid)和最近执行的事务的zxid信息。
     
     当一个服务器接收到一个投票信息,该服务器将会根据以下规则修改自己的投票信息
     1)将接收的voteid和votezxid作为一个标识符,并获取接收方当前的投票中的zxid,
        用myZxid和mySid标识接收方服务器自己的值。
     2)如果votezxid > myzxid或者  votezxid = myzxid且voteid > mysid
        则保留当前的投票信息
     3)否则,修改自己的投票信息,将votezxid赋值给myZxid,将voteId赋值给mySid

     简而言之,只有最新的服务器将赢得选举,因为其拥有最近一次的zxid,如果多个服务器拥有
  最大的zxid,其中的sid值最大的服务器将赢得选举。
Zab:状态更新的广播协议
     在接收到一个写请求操作后,追随者会将请求转发给群首,群首将探索性的执行该请求,并将执行
  结果以事务的方式对状态更新进行广播。一个事务中包含服务器需要执行变更的确切操作,当事务提交
  时,服务求就会将这些变耿反馈到数据树上,其中数据树为Zookeeper用于保存状态信息的数据结构。
     之后我们需要面对的问题便是服务器如何确认一个事务是否已经提交,由此引入了我们所采用的
  Zab协议:Zookeeper原子广播协议。假设现在我们有一个活动的群首服务器,并拥有仲裁数量的追随
  者支持该群首的管理权,通过该协议提交一个事务非常简单,类似于一个两阶段提交。
     1:群首向所有追随者发送一个PROPOSAL消息p.
其他微服务的注册中心
       
      1)zookeeper
      2) etcd
      3) redis
      5) Eureka
Zookeeper使用场景

     1)数据发布与订阅

       发布与订阅即所谓的配置管理,顾名思义就是将数据发布到zk节点上,供订阅者动态获取数
       据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,地址列表等就非常适合使用。

       1. 索引信息和集群中机器节点状态存放在zk的一些指定节点,供各个客户端订阅使用。
       2. 系统日志(经过处理后的)存储,这些日志通常2-3天后被清除。
       3. 应用中用到的一些配置信息集中管理,在应用启动的时候主动来获取一次,并且在节点
          上注册一个Watcher,以后每次配置有更新,实时通知到应用,获取最新配置信息。
       4. 业务逻辑中需要用到的一些全局变量,比如一些消息中间件的消息队列通常有个offset,
          这个offset存放在zk上,这样集群中每个发送者都能知道当前的发送进度。
       5. 系统中有些信息需要动态获取,并且还会存在人工手动去修改这个信息。以前通常是暴露
          出接口,例如JMX接口,有了zk后,只要将这些信息存放到zk节点上即可。

     2)Name Service

       这个主要是作为分布式命名服务,通过调用zk的create node api,能够很容易创建一个全
       局唯一的path,这个path就可以作为一个名称。

     3)分布通知/协调

       1)ZooKeeper 中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之
       间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对 ZK上同一个
       znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统
       update了znode,那么另一个系统能 够收到通知,并作出相应处理。

        1. 另一种心跳检测机制:检测系统和被检测系统之间并不直接关联起来,而是通过zk上某
        个节点关联,大大减少系统耦合。
        2. 另一种系统调度模式:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送
        系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改 了ZK上某些节点
        的状态,而zk就把这些变化通知给他们注册Watcher的客户端,即推送系统,于是,作出相应的推送任务。

        3. 另一种工作汇报模式:一些类似于任务分发系统,子任务启动后,到zk来注册一个临
           时节点,并且定时将自己的进度进行汇报(将进度写回这个临时节点),这样任务管理者
           就能够实时知道任务进度。

        总之,使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合。

      5)分布式锁
        分布式锁,这个主要得益于ZooKeeper为我们保证了数据的强一致性,即用户只要完全相信
        每时每刻,zk集群中任意节点(一个zk server)上的相同znode的数据是一定是相同的。
        锁服务可以分为两类,一个是保持独占,另一个是控制时序。

        所谓保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。
        通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有
        客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。

        控制时序,就是所有视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时
        序了。做法和上面基本类似,只是这里 /distribute_lock 已经预先存在,客户端在它下
        面创建临时有序节点(这个可以通过节点的属性控制:
        CreateMode.EPHEMERAL_SEQUENTIAL来指 定)。Zk的父节点(/distribute_lock)维
        持一份sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局时序。

      6)集群管理
        1. 集群机器监 控:这通常用于那种对集群中机器状态,机器在线率有较高要求的场景,能
        够快速对集群中机器变化作出响应。这样的场景中,往往有一个监控系统,实时检测集群机
        器是否存活。过去的做法通常是:监控系统通过某种手段(比如ping)定时检测每个机器,
        或者每个机器自己定时向监控系统汇报“我还活着”。 这种做法可行,但是存在两个比较明
        显的问题:1. 集群中机器有变动的时候,牵连修改的东西比较多。2. 有一定的延时。

        利用ZooKeeper有两个特性,就可以实时另一种集群机器存活性监控系统:
            a. 客户端在节点 x 上注册一个Watcher,那么如果 x 的子节点变化了,会通知该
               客户端。b. 创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,
               那么该节点就会消失。

            例如,监控系统在 /clusterServers 节点上注册一个Watcher,以后每动态加机
            器,那么就往 /clusterServers 下创建一个 EPHEMERAL类型的节点:
            /clusterServers/{hostname}. 这样,监控系统就能够实时知道机器的增减情况,
            至于后续处理就是监控系统的业务了。

        2. Master选举则是zookeeper中最为经典的使用场景了。
           在分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的
           计算,网络I/O处理),往往只需要让整个集群中的某一台机器进行执行, 其余机器
           可以共享这个结果,这样可以大大减少重复劳动,提高性能,于是这个master选举便是
           这种场景下的碰到的主要问题。

           利用ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一
           性,即:同时有多个客户端请求创建 /currentMaster 节点,最终一定只有一个客
           户端请求能够创建成功。

           利用这个特性,就能很轻易的在分布式环境中进行集群选取了。

           另外,这种场景演化一下,就是动态Master选举。这就要用到 EPHEMERAL_SEQUENTIAL类型节点的特性了。

           上 文中提到,所有客户端创建请求,最终只有一个能够创建成功。在这里稍微变化下,
           就是允许所有请求都能够创建成功,但是得有个创建顺序,于是所有的请求最终 在ZK上
           创建结果的一种可能情况是这样: 
                /currentMaster/{sessionId}-1 , 
                /currentMaster/{sessionId}-2 , 
                /currentMaster/{sessionId}-3 ….. 
           每次选取序列号最小的那个机器作为Master,如果这个机器挂了,由于他创建的节点会
           马上小时,那么之后最小的那个机器就是Master了。

           1. 在搜索系统中,如果集群中每个机器都生成一份全量索引,不仅耗时,而且不能保证
           彼此之间索引数据一致。因此让集群中的Master来进行全量索引的生成, 然后同步到集
           群中其它机器。2. 另外,Master选举的容灾措施是,可以随时进行手动指定master,
           就是说应用在zk在无法获取master信息时,可以通过比如http方式,向 一个地方获取master。

     7)分布式队列

       队列方面,我目前感觉有两种,一种是常规的先进先出队列,另一种是要等到队列成员聚齐之
       后的才统一按序执行。对于第二种先进先出队列,和分布式锁服务中的控制时序场景基本原
       理一致,这里不再赘述。

       第二种队列其实是在FIFO队列的基础上作了一个增强。通常可以在 /queue 这个znode下预
       先建立一个/queue/num 节点,并且赋值为n(或者直接给/queue赋值n),表示队列大小,
       之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行 了。
       这种用法的典型场景是,分布式环境中,一个大任务Task A,需要在很多子任务完成(或条
       件就绪)情况下才能进行。这个时候,凡是其中一个子任务完成(就绪),那么就
       去 /taskList 下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),
       当 /taskList 发现自己下面的子节点满足指定个数,就可以进行下一步按序进行处理了。