一、Zookeeper基础知识
1 zookeeper是一个类似hdfs的树形文件结构,zookeeper可以用来保证数据在(zk)集群之间的数据的事务性一致。
2 zookeeper有watch事件,是一次性触发的,当watch监视的数据发生变化时,通知设置了该watch的client,即watcher。
3 zookeeper有三个角色:Leader,Follower,Observer。
Follower 和 Observer 都能提供读服务,不能提供写服务。两者唯一的区别在于,Observer机器不参与 Leader 选举过程,也不参与写操作的『过半写成功』策略,因此 Observer 可以在不影响写性能的情况下提升集群的读性能。
4 zookeeper的结构其实就是一个树形结构,leader就相当于其中的根结点,其它节点就相当于follow节点,每个节点都保留自己的内容。
zookeeper的节点分两类:持久节点和临时节点
PERSISTENT 持久化节点
PERSISTENT_SEQUENTIAL 顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1
EPHEMERAL 临时节点, 客户端session超时这类节点就会被自动删除
EPHEMERAL_SEQUENTIAL 临时自动编号节点
5 leader选举
(1) 变更状态。Leader挂后,余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。
(2) 每个Server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定Server1的ZXID为123,Server3的ZXID为122;在第一轮投票中,Server1和Server3都会投自己,产生投票(1, 123),(3, 122),然后各自将投票发送给集群中所有机器。
(3) 接收来自各个服务器的投票。与启动时过程相同。
(4) 处理投票。与启动时过程相同,此时,Server1将会成为Leader。
(5) 统计投票。与启动时过程相同。
(6) 改变服务器的状态。与启动时过程相同。
6 zookeeper的默认端口
2181:对client端提供服务
2888:集群内机器通讯使用(Leader监听此端口)
3888:选举leader使用
7 zookeeper应用场景:
统一命名服务(Name Service)
配置管理(Configuration Management)
集群管理(Group Membership)
共享锁(Locks)
队列管理
二、相关名词
1.CAP定理
一个分布式系统不可能同时满足一致性、可用性和分区容错性这三个基本需求,最多只能满足其中的两项。
三个特性如下:
一致性(Consistency):指数据在多个副本之间保持一致。一个数据更新后,所有的用户能看到最新的值。
可用性(Availability):指系统提供的服务必须一直处于可用的状态,对用户的每一个操作请求总是能够在有限的时间内返回结果。
分区容错性(Partition tolerance):在遇到任何网络分区故障的时候,任然需要能够对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
CAP定理应用:放弃A或放弃C或放弃P。
2.BASE理论
是对CAP中的一致性和可用性的权衡结果。即使无法做到强一致性,也可以采用适当的方法达到最终一致性。
BASE三要素:
基本可用:系统在发生不可预知的故障时,允许损失部分的可用性(包括响应时间、功能上的损失)。
弱状态:也叫软状态,与硬状态相对。存在中间状态,允许系统在不同节点的数据副本在进行数据同步的时候存在延时。
最终一致性:系统的所有数据副本,经过一段时间以后,最终能够达到一致的状态,不需要实时保证数据的强一致性。
3.ZAB协议
为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。
ZAB协议包括两种基本模式:崩溃恢复和消息广播。
在消息广播中,Leader服务器会为每一个Follower服务器各自分配一个单独的队列,将需要广播的事务依次放入这些队列中,并根据FIFO的策略进行消息发送。
崩溃恢复:当Leader服务器出现崩溃或者由于网络原因导致Leader服务器失去与过半Follower的联系,就进入崩溃恢复模式。
进程正常工作时,处于UP状态;进程崩溃时,称为处于DOWN状态。
ZAB协议是整个Zookeeper框架的核心所在,它规定任何时候都需要保证只有一个主进程负责进行消息广播,如果主进程崩溃了就要选举一个新的主进程,选举机制和消息广播机制是紧密相关。
ZAB与Paxos的联系与区别:
联系:都存在类似于Leader进程的角色,负责协调多个Follower进程的运行;Leader进程都会等待超过半数的Follower做出正确的反馈后,才会将一个提案进行提交。
区别:设计目标不一样。ZAB用于构建一个高可用的分布式数据主备系统,Paxos用于构建分布式一致性状态机系统。
4.Paxos算法:
包含的角色有:Proposer(申请者)、Acceptor(接收者)、Learner(领导者);
执行过程:
提案的选定:一个Proposer向一个或多个Acceptor发提案,由半数以上Acceptor批准的提案会被选定。
Proposer生成提案:在确定提案后,Proposer会将该提案再次发送给某个Acceptor集合,并期望获得它们的批准。
Acceptor批准提案:Aceeptor接到Proposer的Prepare或Accept请求后,做出相应的响应。
提案的获取:Learner获取提案。
优点:引入“过半”的概念,即少数服从多数的原则,并且支持节点角色之间的轮换,极大地避免了分布式的出现,也解决了无限等待和“脑裂”等问题。
三、Zookeeper配置
1 结构:一共三个节点 (zk服务器集群规模不小于3个节点),要求服务器之间系统时间保持一致。
2 上传zk
1)进行解压:tar zookeeper-3.4.5.tar.gz
2)重命名:mv zookeeper-3.4.5 zookeeper
3)修改环境变量:vi /etc/profile
export ZOOKEEPER_HOME=/usr/local/zookeeper
export PATH=.:$HADOOP_HOME/bin:$ZOOKEEPER_HOME/bin:$JAVA_HOME/...
刷新:source /etc/profile
4)修改conf:
cd /usr/local/zookeeper/conf
mv zoo_sample.cfg zoo.cfg
5)修改vi zoo.cfg
dataDir=/usr/local/zookeeper/data
在最后面添加
server.0=bhz:2888:3888
server.1=hadoop1:2888:3888
server.2=hadoop2:2888:3888
6)服务器标识配置:
创建文件夹:mkdir data
创建文件myid并填写内容为0:vi myid (内容为服务器标识 : 0)
进行复制zookeeper目录到hadoop01和hadoop02 还有/etc/profile文件
把hadoop01、hadoop02中的myid文件里的值修改为1和2 路径(vi /usr/local/zookeeper/data/myid)
7)启动zookeeper:
路径:/usr/local/zookeeper/bin
执行:zkServer.sh start
状态:zkServer.sh status(在三个节点上检验zk的mode,一个leader和俩个follower)
3 操作zookeeper (shell)
zkCli.sh 进入zookeeper客户端
根据提示命令进行操作:
查找:ls / ls /zookeeper
创建并赋值:create /bhz hadoop
获取:get /bhz
设值:set /bhz baihezhuo
可以看到zookeeper集群的数据一致性
创建节点有俩种类型:短暂(ephemeral) 持久(persistent)
四、zoo.cfg详解:
tickTime: 基本事件单元,以毫秒为单位。这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每隔 tickTime时间就会发送一个心跳。
dataDir: 存储内存中数据库快照的位置,顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
initLimit: 这个配置项是用来配置 Zookeeper 接受客户端初始化连接时最长能忍受多少个心跳时间间隔数,当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒。
syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10 秒
server.A = B:C:D :
A表示这个是第几号服务器,
B 是这个服务器的 ip 地址;
C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;
D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader
五、zookeeper的不足
1.zookeeper不是为高可用性设计的
由于要跨机房容灾,很多系统实际上是需要跨机房部署的。出于性价比的考虑我们通常会让多个机房同时工作,而不会搭建N倍的冗余。也就是说单个机房肯定撑不住全流量(你能设想谷歌在全球只剩下一个机房在干活吗)。由于zookeeper集群只能有一个master,因此一旦机房之间连接出现故障,zookeeper master就只能照顾一个机房,其他机房运行的业务模块由于没有master都只能停掉。于是所有流量集中到有master的那个机房,于是系统crash。
即使是在同一个机房里面,由于网段的不同,在调整机房交换机的时候偶尔也会发生网段隔离的情况。实际上机房每个月基本上都会发生短暂的网络隔离之类的子网段调整。在那个时刻zookeeper将处于不可用状态。如果整个业务系统基于zookeeper(比如要求每个业务请求都先去zookeeper获取业务系统的master地址),则系统的可用性将非常脆弱。
由于zookeeper对于网络隔离的极度敏感,导致zookeeper对于网络的任何风吹草动都会做出激烈反应。这使得zookeeper的‘不可用’时间比较多,我们不能让zookeeper的‘不可用’,变成系统的不可用。
2.zookeeper的选举过程速度很慢
这是一个很难从理论分析上看到的弱点,但是你一旦遇到就会痛不欲生。
前面我们已经说过,网络实际上常常是会出现隔离等不完整状态的,而zookeeper对那种情况非常敏感。一旦出现网络隔离,zookeeper就要发起选举流程。
zookeeper的选举流程通常耗时30到120秒,期间zookeeper由于没有master,都是不可用的。
对于网络里面偶尔出现的,比如半秒一秒的网络隔离,zookeeper会由于选举过程,而把不可用时间放大几十倍。
3.zookeeper的性能是有限的
典型的zookeeper的tps大概是一万多,无法覆盖系统内部每天动辄几十亿次的调用。因此每次请求都去zookeeper获取业务系统master信息是不可能的。
因此zookeeper的client必须自己缓存业务系统的master地址。
因此zookeeper提供的‘强一致性’实际上是不可用的。如果我们需要强一致性,还需要其他机制来进行保障:比如用自动化脚本把业务系统的old master给kill掉,但是那会有很多陷阱(这里先不展开这个议题,读者可以自己想想会有哪些陷阱)。
4.zookeeper的权限控制非常薄弱
在大型的复杂系统里面,使用zookeeper必须自己再额外的开发一套权限控制系统,通过那套权限控制系统再访问zookeeper
额外的权限控制系统不但增加了系统复杂性和维护成本,而且降低了系统的总体性能
5.即使有了zookeeper也很难避免业务系统的数据不一致
前面已经讨论过了,由于zookeeper的性能限制,我们无法让每次系统内部调用都走zookeeper,因此总有某些时刻,业务系统会存在两个master(业务系统client那边缓存的业务系统master信息是定时从zookeeper更新的,因此会有更新不同步的问题)。
如果要在业务系统client的master信息不一直的情况下,仍要保持系统的数据一致性,唯一的方法是“先kill掉老master,再在zookeeper上更新master信息”。但是在是否要kill current master这个问题上,程序是无法完全自动决定的(因为网络隔离的时候zookeeper已经不可用了,自动脚本没有全局信息,不管怎么做都可能是错的,什么都不做也可能是错的。当网络故障的时候,只有运维人员才有全局信息,程序是无法接电话得知其他机房的情况的)。因此系统无法自动的保障数据一致性,必须要人工介入。而人工介入的典型时间是半个小时以上,我们不能让系统这么长时间不可用。因此我们必须在某个方向上进行妥协,最常见的妥协方式是放弃‘强一致性’,而接受‘最终一致性’。
如果我们需要人工介入才能保证‘可靠的强一致性’,那么zookeeper的价值就大打折扣。
六、zookeeper应用场景
1.数据发布与订阅(配置中心)
数据发布与订阅,即所谓的配置中心,顾名思义就是发布者将数据发布到 ZooKeeper 节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和动态更新。
发布/订阅系统一般有两种设计模式,分别是推(Push)和拉(Pull)模式。
- 推模式
服务端主动将数据更新发送给所有订阅的客户端
- 拉模式
客户端主动发起请求来获取最新数据,通常客户端都采用定时轮询拉取的方式
ZooKeeper 采用的是推拉相结合的方式:
客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据
2.命名服务
命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务,远程对象等等——这些我们都可以统称他们为名字。
其中较为常见的就是一些分布式服务框架(如RPC)中的服务地址列表。通过在ZooKeepr里创建顺序节点,能够很容易创建一个全局唯一的路径,这个路径就可以作为一个名字。
ZooKeeper 的命名服务即生成全局唯一的ID。
3.分布式协调服务/通知
ZooKeeper 中特有 Watcher 注册与异步通知机制,能够很好的实现分布式环境下不同机器,甚至不同系统之间的通知与协调,从而实现对数据变更的实时处理。使用方法通常是不同的客户端如果 机器节点 发生了变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并做出相应的处理。
ZooKeeper的分布式协调/通知,是一种通用的分布式系统机器间的通信方式。
4.Master选举
利用 ZooKeepr 的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即 ZooKeeper 将会保证客户端无法创建一个已经存在的 数据单元节点。
也就是说,如果同时有多个客户端请求创建同一个临时节点,那么最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式环境中进行 Master 选举了。
成功创建该节点的客户端所在的机器就成为了 Master。同时,其他没有成功创建该节点的客户端,都会在该节点上注册一个子节点变更的 Watcher,用于监控当前 Master 机器是否存活,一旦发现当前的Master挂了,那么其他客户端将会重新进行 Master 选举。这样就实现了 Master 的动态选举。
5.分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式
分布式锁又分为排他锁和共享锁两种
排它锁
定义锁
ZooKeeper 上的一个 机器节点 可以表示一个锁
获得锁
把ZooKeeper上的一个节点看作是一个锁,获得锁就通过创建临时节点的方式来实现。
ZooKeeper 会保证在所有客户端中,最终只有一个客户端能够创建成功,那么就可以 认为该客户端获得了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock 节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。
释放锁
因为锁是一个临时节点,释放锁有两种方式
当前获得锁的客户端机器发生宕机或重启,那么该临时节点就会被删除,释放锁。
正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除,释放锁。
无论在什么情况下移除了lock节点,ZooKeeper 都会通知所有在 /exclusive_lock 节点上注册了节点变更 Watcher 监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复『获取锁』过程。
共享锁
共享锁在同一个进程中很容易实现,但是在跨进程或者在不同 Server 之间就不好实现了。Zookeeper 却很容易实现这个功能,实现方式也是需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不是那么它就调用 exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。