zookeeper简介
Apache zookeeper是一个高性能的分布式协调中间件。设计思想只要源于google chubby,是chubby的一个开源实现,google chubby是一个用来解决分布式一致性的组件,同时也是一个粗粒度的分布式锁服务组件,但是由于google chubby不开源,所以雅虎组织开发了zookeeper用来解决分布式一致性问题。
什么是分布式一致性?
简单来说,就是在一个分布式系统中,所有节点都想要提交事务请求, 但是所有请求中只有一个请求能被通过,并且得到所有节点都同意这个结果,这个就是典型的拜占庭将军的问题。
拜占庭将军问题的本质是说,由于网络通信的不可靠行,可能存在数据丢失,或者网络延迟,如何在这样的环境性达到一致性的问题,许多人提出个各种各样的协议,比如大名鼎鼎的Paxos,就是在不可靠的网络环境下,按照paxos协议就可以达到某种提议的一致。
这个跟google chubby有什么关系呢?
Google chubby
首先谈一下google的GFS(Google File System)分布式文件系统,比如五个节点的File Server,必须通过选举的方式决定哪一个节点为master server,并且每一个server节点都要同意这个选举。
GFS就是利用chubby来解决这个问题的
google chubby的原理:分布式环境下,所有是server通过chubby协议到chubby server上生成一个文件,当然,最后只有一个节点获得创建文件的权利,并且将自己的地址写入到文件中,这个节点就是作为master节点,其它的server通过读取chubby server上的文件来确定谁是master。
分布式锁服务
从另外一个角度看,Chubby提供了一个粗粒度的分布式锁服务,在Chubby Server上创建文件就是提供了锁的服务,server像Chubby server发送创建文件的请求就是抢占锁的功能,文件创建成功就是成功抢占了锁。
由于google chubby没有开源,所以雅虎公司基于google chubby的思想,自己研发了一套类似的分布式协调组件zookeeper,后来把zookeeper捐献给了Apache
所以,zookeeper并不是作为注册中心来设计,它是作为分布式锁来设计实现的,注册中心只是zookeeper应用的一个部分。
zookeeper的设计猜想
基于zookeeper本身的设计目标,用来解决分布式协调的组件,需要满足哪些功能呢?
-
防止单点故障
首先,在分布式环境下,任何的服务都不能以单点的方式存在,所以为了解决单点故障问题,通常的解决方案是引入集群,需要满足以下几个点:
1、集群中要有主节点和从节点
2、各个节点中的数据必须保持一致,当主节点挂掉之后,必须有新的节点顶替作为新的主节点,并保证数据不能丢失
3、当主节点挂掉之后,系统会自动通过程序判断推选出新的节点作为主节点 -
Leader角色
1、完成事务操作的唯一调度和处理者,保证事务操作的顺序性
2、完成内部各个服务器之间的调度 -
Follower角色
1、处理非事务请求,并将事务请求转发给Leader
2、参与事务请求Proposal的投票(需要半数以上节点的投票才能通知leader commit数据,由leader发起,要求follower参与投票)
3、参加leader选举 -
数据同步
在高可用的集群中,每个节点都能够接受到请求,要满足数据的一致性,必须有一个leader节点来协调各个节点之间的数据同步问题,而且,事务操作和非事务操作必须分开,设想一下,如果事务和非事务操作,不分开,每个节点都能够进行事务操作,那么这个集群同步数据的开销会非常大,所以必须由leader统一处理和调度事务处理,follower只能处理非事务请求,对于数据变更,由一个节点来维护,使得集群数据同步处理简化。同时,也可以保证leader将数据分发到其它节点,保证数据的一致性
leader节点如何保证各个节点的数据一致性,并且是强一致性。leader节点可以明确自己处理事务请求的成功或者失败,但在分布式环境下,无法直接获取其它分布式节点的操作结果。所以当一个事务处理涉及到跨节点的时候,就需要分布式事务,分布式数据一致性协议有2PC和和3PC协议
2PC协议
当一个事务操作需要跨越多分布式节点的时候,为了保持事务处理的ACID特性,就需要一个调度者(TM)来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点被称为AP。TM决定AP的行为,并最终决定这些AP是否要把事务进行真正的提交;因为整个事务分为两阶段提交,所以叫做2PC。
提交事务阶段(投票)
- 1、事务询问 协调者向各个参与者发起事务询问,询问事务是否可以提交,并等待参与者响应 2、处理事务
各个参与者处理事务,并将undo和redo信息记录到事务日志中,尽量把提交过程中耗时的步骤和准备提前完成,保证后面100%完成事务提交操作
3、各个参与者像协调者反馈事务询问的响应
各个参与者如果执行事务成功,则返回yes给协调者,表示事务可以执行,如果执行事务失败,则返回no给协调者,表示事务不可以执行。这阶段的操作有点类似于协调者组织所有参与者进行一次投票操作,因此P2C的第一个阶段也叫“投票阶段”,即各个参与者都要表明是否可以进行事务提交操作
执行事务提交
- 在这个阶段,协调者会最终根据参与者的投票来决定是不是进行事务提交,这个阶段只有两种情况:执行事务,中断事务
集群角色
- 一共有三个角色,分别为Leader节点、Fllower节点、Observer节点。
数据模型
- zookeeper的视图结构与标准的文件系统非常类似,每一个节点都成为ZNode(DataNode),是zookeeper的最小单元。每一个znode上都可以保存数据以及挂在子节点。构成一个层次化的属性结构。
节点包括以下6种状态:
1、持久化节点(PERSISTENT):创建后会一直存在zookeeper服务器上,直到主动删除。
2、持久有序节点(PERSISTENT_SEQUENTIAL):每个节点都会为它的一级子节点维护一个顺序,也会持久会在zookeeper的服务器上直至主动删除。
3、临时节点(EPHENERAL):临时节点的生命周期和当前客户端的回话保持一致,当客户端会话失效,则自动清理该节点。
4、临时有序节点(EPHEMERAL_SEQUENTIAL):相对于临时节点来说,多了一个顺序性
5、CONTAINER:当子节点被删除后,Container也会被删除
6、PERSISTENT_WITH_TTL:当时间超过TTL的时间没有修改节点的时候,会自动删除
7、PERSISTENT_SEQUENTIAL_WITH_TTL:如果该节点没有子Znode,并且TTL时间没没有做修改,则会删除Znode。(TTL的单位是毫秒,必须大于0且不能大于EphemeralType.MAX_TTL)
会话
- 共4种状态,如下: 1、client初始化连接,状态转为CONNECTING(①)
2、client与server端连接上,状态转为CONNECTED(②)
3、client丢失了server的连接或者没有接收到server的响应状态,状态转为CONNECTING(③)
4、若会话过期(server声明会话过期,而不是client)状态转为CLOSED(⑤)
5、client主动关闭会话,状态转为CLOSED(④)
Stat状态信息
每个节点除了存储数据内容以外,还存储了数据节点本身的一些状态信息,通过get命令可以获得状态信息的详细内容
版本(version)
zookeeper为了保证数据的原子性,给数据节点增加了版本的概念,每个节点都有三个版本信息,每次对节点的更新操作都会引起版本的变化。
-
版本有点像悲观锁和乐观锁
悲观锁:数据库中一种非常典型且非常严格的并发控制策略。假如一个事务A正在对数据进行操作,那么整个操作过程中,都会将数据置于锁定状态,在这期间,其他事务无法对数据进行任何操作。
乐观锁:乐观锁跟悲观锁的想法正好相反,它假定多个事务同时处理一个数据,当提交更新操作时,都会验证数据在该事务读取后是否已经被其它事务修改,如果已经修改,则进行回滚操作zookeeper的版本机制就类似于乐观锁的机制(写入校验)
Watcher机制
- zookeeper提供了分布式数据分发布/订阅功能,允许客户端向server端注册一个watcher监听事件,当服务端的一些指定事件发生时(例如,更新节点数据),会发送给客户端一个消息通知。
watcher机制是一次性的,及触发了监听事件,就会删除该监听,如果想要一直监听某个事件,可以在客户端接收到消息通知的时候再次注册watcher监听,否则下一次将无法监听到该事件。
zookeeper基于java客户端的访问
针对zookeeper,比较常用的客户端有Curator、Zkclient,由于Curator对zookeeper的抽象层次比较高,简化了客户端的代码量, 因此Curator被广大使用,本文作者也推荐使用Curator进行zookeeper的操作
Curator的优势:
1、封装了zookeeper client和zookeeper server端之间的操作
2、提供了一套fluent风格的操作api
3、提供了zookeeper的各种应用场景,例如:共享锁,leader选举的抽象封装
依赖jar包
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
建立连接
CuratorFramework curatorFramework = new CuratorFrameworkImpl(
CuratorFrameworkFactory
.builder()
.retryPolicy(new ExponentialBackoffRetry(1000, 3))//重试3次,每次重试时间加1000ms
.sessionTimeoutMs(5000)
.connectionTimeoutMs(5000)
.namespace("curator")
.connectString(CONNECT_STRING));
curatorFramework.start();
重试策略
1、ExponentialBackoffRetry:指定重试次数,且每一次重试的停顿时间都要递加
2、RetryOneTime:重试一次
3、RetryNTimes:指定最大重试次数
4、RetryUntilElapsed:一直重试知道达到规定的时间
namespace:session会话含有命名空间,代码中的例子指定所有的操作都是在“/curator”这个目录进行的,有利于zookeeper针对不同业务进行管理