想精通分布式以及高并发架构?那你得先搞定ZooKeeper架构原理!
Posted hulianwangjiagoushi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了想精通分布式以及高并发架构?那你得先搞定ZooKeeper架构原理!相关的知识,希望对你有一定的参考价值。
Zookeeper是分布式一致性问题的工业解决方案,是Apache Hadoop下解决分布式一致性的一个组件,后被分离出来成为Apache的顶级项目。
工程来源:是雅虎公司内部项目,据说雅虎内部很多项目都是以动物命名,这个动物管理员的名字起的很是形象。
被开源出来后得到开源社区的快速推进,服务端Java语言实现,棒,git有3000+的star:
https://github.com/apache/zookeeper
zookeeper集群结构
集群的角色,比较典型的是Master/Slave(主备模式),zk中的概念跟这个不一样,包含Leader、Follower、Observer三个角色,leader提供读和写的能力,follower只对外提供读的能力。
会话(session)
客户端跟服务端交互,是先与服务端建立一个TCP长连接,会话开始,通过心跳检测与服务端保持会话有效,向服务端发送请求和接收响应。
zk将所有的数据都加载在内存一份,同时有事务日志文件(持久化文件),服务端会定时dump快照数据,重启机器的时候会根据快照和事务日志恢复内存数据库的数据,这跟redis的AOF和RDB概念类似。
zookeeper上的数据结构
zk上的数据的结构跟linux文件系统很像,是个树状结构
节点(node)上的信息字段
节点类型包括:
- 持久型节点
- 顺序持久型节点
- 临时节点
- 顺序临时节点
其中临时节点特性就是创建它的主体消失后,它就跟着消失了。后续的应用就是利用的节点的特性实现的。
事件监听器(watcher)
这个应该是zookeeper最重要的概念之一了,zk允许用户在特定的节点(znode)上注册watcher,并且在特定事件触发的时候,zk服务端会将事件通知到感兴趣的客户端上。
伪集群的搭建:
zoo.cfg 配置文件
- 拷贝三份文件
- 修改配置zoo.cfg,区别出日志目录和端口号,dataDir文件下添加表示机器号的文件myid
- 集群配置列表,第一个端口是机器间业务通讯的端口,第二个端口是用来进行leader选举的端口
- 分别启动三台服务器
启动成功后,命令行连接zk,可以用指令做些增删改查的操作
telnet 127.0.0.1 2181
stat:可以看集群的状态信息
stat信息
每次事务操作,会在dataDir的目录下的事务日志,是序列化的二进制文件,zookeeper提供了查看事务日志的工具类LogFormatter
LogFormatter转换后的事务日志文件
Java客户端使用
- zookeeper自带的
- 开源的客户端ZkClient,实现session超时重连,watcher反复注册的功能,简化开发人员的使用
- 开源客户端Crurator,解决底层的细节开发工作,目前是apache的顶级项目,是使用最广泛的zk客户端。
- 提供了可读性更新的api接口
- 提供了各种应用场景的抽象封装(共享锁服务、Master选举机制,分布式计数器等)
- guava is to java what cruator is to zookeeper
cruator客户端例子
zookeeper应用场景
利用zookeeper的特性,可以比较方便的构建分布式应用会涉及的核心功能,比如:配置中心、命名服务、分布式协调/通知、集群的管理、master选举、分布式锁等
以下应用基本基于zookeeper的两大特性实现
- 客户端如果对zookeeper的一个数据节点注册watcher监听,那么当该数据节点的内容或其子节点的列表发生变更是,zookeeper服务器就会想订阅的客户端发送变更通知
- 对在zookeeper上创建的临时节点(sequent类型),一旦客户端与服务器之间的回话失效,那么临时节点会被自动清除
--配置中心:
配置中心
zookeeper利用推拉结合的方式,客户端向服务端注册自己需要关注的节点,一旦该数据发生变更,那么服务器就会向相应的客户端发送watcher时间通知。
客户收到这个消息通知之后,再主动到服务端获取最新的数据。即回调的event中包含具体的数据。
这个应用的的业务员特点:
- 数据不经常变化
- 数据量通常比较小,保存的内存里,访问很快
- 配置动态变化,不需要重启机器,数据变化,会通知相应的客户端
- 集群共享配置一致,比如数据库连接的配置,业务的开关,甚至一些数据量小,不经常变的业务数据如弹窗文案,活动文案也放里边,用于快速迭代实现功能。
--命名服务
利用zookeeper的顺序节点,树形结构的数据特点,实现命名服务:
- 比如RPC框架中,每个服务在zookeeper中对应一个节点(serviceName),节点下存放这个服务所用到的资源,比如部署的ip列表,接口列表
这样RPC的客户端只需要传对应的服务名字,和接口,就能找到对应的服务。
- 全局唯一id的生成,UUID(通用的唯一标识码)可以实现,但是他的缺点是:太长,包含32位字符和4个短线字符串;看不出业务含义,不方便排查问题。
使用zookeeper实现:不同的业务下创建一个节点,具体的节点下用zk的顺序节点(sequent)生成id当做这个业务的全局唯一id使用
--分布式通知/协调
ZooKeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。
使用方法通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能够收到通知,并作出相应处理。
应用:
心跳检测机制:传统的方式是ping,复杂的话是建立长连接检测系统和被检测系统之间并不直接关联起来,而是通过zookeeper上某个节点关联,大大减少系统耦合
系统调度模式:某系统由控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。
管理人员在控制台作的一些操作,实际上是修改了ZK上某些节点的状态,而ZK就把这些变化通知给他们注册Watcher的客户端,即推送系统。于是,作出相应的推送任务
作汇报模式:一些类似于任务分发系统,子任务启动后,到ZK来注册一个临时节点,并且定时将自己的进度进行汇报(将进度写回这个临时节点)
总之,使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合。
--分布式锁
这个应用主要得益于ZooKeeper为我们保证了数据的强一致性
即用户只要完全相信每时每刻,zk集群中任意节点(一个zk server)上的相同znode的数据是一定是相同的。
一个节点要么创建成功,要么失败,并且只由一个客户端创建。
独占锁:
保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。
通常的做法是把ZK上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建/distribute_lock节点,最终成功创建的那个客户端也即拥有了这把锁。
共享时序控制锁:
Zookeeper很容易实现这个功能,实现方式是需要获得锁的Server,创建一个EPHEMERAL_SEQUENTIAL目录节点。
然后调用getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点。
如果正是自己创建的,那么它就获得了这个锁,如果不是,那么它就调用exists(String path, boolean watch)方法,并监控Zookeeper上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁。
释放锁很简单,只要删除前面它自己所创建的目录节点就行了。
--master选举
master选举应用图
有个容易理解的方案,依靠关系型数据库主键的特性,集群的机器同时往一张表里插入数据,数据库会自动进行主键冲突检查,可以选择插入成功的客户端作为master
这种方式存在一个问题就是,master机器挂了,没有人通知
zk实现可以方便做到这一点:zk的创建节点api接口,具有强一致性,能够保证客户端并发创建的时候,最终一定只有一个客户端创建成功。
- 客户端集群每天定时往/master_election/(当天日期)2017-03-24/binding创 建临时节点,只有一个成功,为master
- 创建失败的在/master_election/(当天日期)2017-03-24 注册监听事件,当master挂了,其余机器收到通知,重新进行选举
--集群管理
应用举例:集群机器存活性监控系统,例如:
监控系统在/clusterServers节点注册一个watcher监听,那么但凡进行动态添加机器的操作,就在/clusterServers下创建一个临时节点, /clusterServers/ip。
这样监控系统就能够实时的检测到机器的变动,通过getChild方法获取所有的临时节点,来判断增加的机器。
当有机器down调或者手动下线,相应临时节点会消失,监控系统也会接收到,来处理监控服务的具体业务
具体服务器部署agent实现
zookeeper的HA设计实现
以上说了那么多犀利实用的应用场景,它们依赖zookeeper,说明这些应用服务的高可用性依赖的zookeeper本身的HA。
zk的选举算法
算法协议zab协议,“少数服从多数”协议一种
3.4.0版本之后Zookeeper只保留了TCP版本的FastLeaderElection选举算法
分析选举算法前,先熟悉了解下zk的一些术语定义解释:
- SID:服务器ID,在集群的配置文件里配置
- ZXID:是一个事务ID,用来唯一标示一次服务器状态变更,集群中每台机器上的ZXID可能不一样
- Vote:投票选举,当集群中机器发现自己无法检测到leader的时候,开始尝试进行投票
- Quorum:过半机器数, quorum=(集群机器数)n/2+1,比如集群数量是3, quorum=2
集群数量是4,quorum=2,集群数量是5,quorum=3
当哪些情况发生时会出发leader重新选举呢?
当zk的一台服务器出现以下两种情况的时候,会进入leader选举流程
- 加机器,服务器初始化
- 服务器运行期间无法和leader通信,leader所在服务器down掉了
对于第一种情况,即已经存在一台leader服务器,当改机器试图去选举leader的时候,会被告知当前服务器的leader信息,对于该机器仅仅需要和leader建立连接,并进行状态同步即可
主要看下第二种情况:
有两种情况导致集群不存在leader,一个是集群刚启动初始化的时候,另一种情况是运行期间leader所在服务器挂了。
无论哪种情况集群所有集群都处在一个找leader的状态,称作Looking状态,开始想其他机器发送消息投票
开始leader选举投票的协议规则是怎样呢?
- 投票的消息包含(SID,ZXID)
- 一开始试图投自己,把投票消息广播出去
- 先比较ZXID,选择ZXID大的
- ZXID相等的,比较SID,选择SID大的
- 如果自己的值大于别的服务器广播来的消息,投票不做变更
- 反正,更换投票,开始第二轮投票,广播出去投票信息
- 每轮结束统计投票,如果一台服务器收到超过半数的相同投票,那个这个服务器对用的SID机器为Leader
5台机器宕机两台后,leader选举的过程图示
因此,一个错误的认识,为了使zookeeper集群能顺利的选出leader,必须将zookeeper集群的服务器数部署为奇数。
从上边例子能看出来部署任意台机器都能够正常选举运行。部署奇数台是官方给的建议,因为奇数和奇数+1的容灾能力是一样的。比如:
5台服务器,能够对2台机器挂掉的情况进程容灾
6台服务器,能够对2台机器挂掉的情况进程容灾,如果挂掉3台,剩下的机器就无法实现过半了。
以上是关于想精通分布式以及高并发架构?那你得先搞定ZooKeeper架构原理!的主要内容,如果未能解决你的问题,请参考以下文章