什么是ZooKeeper?ZooKeeper是Apache的顶级项目,为分布式应用程序提供高效、高可用的分布式协调服务。分布式协调/通知、分布式锁等分布式基础服务。ZooKeeper由于使用方便、性能优异、稳定性好,被广泛应用于Hadoop、HBase、Kafka、Dubbo等大型分布式系统中。Zookeeper有三种运行模式:单机模式、伪集群模式和集群模式。单机模式:这种模式一般适用于开发和测试环境。一方面,我们没有那么多的机器资源,另一方面,平时的开发调试对稳定性要求不高。集群模式:一个ZooKeeper集群通常由一组机器组成,一般3台以上的机器可以组成一个可用的ZooKeeper集群。组成ZooKeeper集群的每台机器都在内存中维护当前服务器状态,并且每台机器都保持相互通信。伪集群模式:这是一种特殊的集群模式,即集群的所有服务器都部署在一台机器上。当你手头有更好的机器时,如果以单机模式部署,会浪费资源。在这种情况下,ZooKeeper允许你在一台机器上通过启动不同的端口来启动多个ZooKeeper服务实例,以此来对外提供具有集群特性的服务。ZooKeeper相关知识Zookeeper中的角色:Leader:负责投票的发起和决策,更新系统状态。Follower(跟随者):用于接收客户端请求并将结果返回给客户端,并在master选择过程中进行投票。观察者(observer):可以接受客户端连接,将写请求转发给领导者,但观察者不参与投票过程,只是为了扩展系统和提高读取速度。Zookeeper的数据模型具有层次化的目录结构,其命名符合常规的文件系统规范,类似于Linux。每个节点在Zookeeper中被称为Znode,它有一个唯一的路径标识。节点Znode可以包含数据和子节点,但EPHEMERAL类型的节点不能有子节点。Znode中的数据可以有多个版本。比如某个路径下存储了多个数据版本,那么在查询该路径下的数据时需要携带版本。客户端应用程序可以在节点上设置监视器。节点不支持部分读写,而是一次完整的读写。ZooKeeper节点特性ZooKeeper节点是有生命周期的,这取决于节点的类型。在ZooKeeper中,节点按持续时间可分为持久节点(PERSISTENT)、临时节点(EPHEMERAL),按是否有序可分为顺序节点(SEQUENTIAL)和无序节点(默认为disordered)。持久化节点一旦创建,除非主动移除,否则会一直保存在Zookeeper中(不会因为创建该节点的客户端会话失效而消失)。ZooKeeper的应用场景ZooKeeper是一个高可用的分布式数据管理和系统协调框架。该框架基于Paxos算法的实现,保证了分布式环境下数据的强一致性。ZooKeeper也正是基于这个特性,解决了很多分布式问题。值得注意的是,ZooKeeper本身并不是为这些应用场景设计的。它是许多开发者根据其框架的特点,使用其提供的一系列API接口(或称为原语集)进行探索的典型用途。方法。数据发布订阅(配置中心)发布订阅模型,所谓配置中心,顾名思义就是发布者将数据发布到ZooKeeper节点,供订阅者动态获取数据,实现集中管理和动态更新的配置信息。比如全局配置信息,service服务框架的服务地址列表等,都非常适合使用。应用中使用的一些配置信息放在ZooKeeper上进行集中管理。这种场景通常是这样的:当应用启动时,会主动获取一个配置,同时在节点上注册一个Watcher。这样,以后每次更新配置时,都会实时通知订阅的客户端,从而达到获取最新配置信息的目的。在分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存储在ZooKeeper的一些指定节点中,供各个客户端订阅。分布式日志收集系统该系统的核心工作是收集分布在不同机器上的日志。采集器通常会根据应用分配采集任务单元,因此需要在ZooKeeper上创建一个以应用名为路径的节点P,并将该应用的所有机器IP以子节点的形式注册到节点P上。这样当机器发生变化时,可以实时通知采集器调整任务分配。系统中有些信息需要动态获取,手动修改这些信息会有问题。通常会暴露一个接口,比如JMX接口,来获取一些运行时信息。引入ZooKeeper后,不需要自己去实现一套方案,只要将信息存储在指定的ZooKeeper节点上即可。注意:在上面提到的应用场景中,有一个默认的前提——数据量小,但数据更新可能更快。负载均衡这里所说的负载均衡指的是软负载均衡。在分布式环境中,为了保证高可用性,通常同一个应用或者同一个服务提供者会部署多个副本来实现对等服务。消费者需要在这些对等服务器中选择一个来执行相关的业务逻辑,通常是消息中间件中的生产者和消费者负载均衡。命名服务命名服务也是分布式系统中常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用程序可以根据指定的名称获取资源或服务的地址、提供者等信息。命名实体通常可以是集群中的一台机器、提供的服务地址、远程对象等——我们可以将它们统称为名称(Name)。比较常见的是一些分布式服务框架中的服务地址列表。通过调用ZooKeeper提供的创建节点的API,很容易创建一个全局唯一的路径,可以作为名字。阿里巴巴集团的开源分布式服务框架Dubbo使用ZooKeeper作为其命名服务,维护一个全局的服务地址列表。在Dubbo的实现中:服务提供者在启动时,将自己的URL地址写入到ZooKeeper上指定节点/dubbo/${serviceName}/providers目录,此操作完成服务的发布。服务消费者启动时订阅/dubbo/{serviceName}/consumers目录写入自己的URL地址。注意:所有注册到ZooKeeper的地址都是临时节点,以便服务提供者和消费者能够自动感知资源变化。另外,Dubbo还有对服务粒度的监控。方法是订阅/dubbo/${serviceName}目录下所有提供者和消费者的信息。分布式通知/协调ZooKeeper中独特的Watcher注册和异步通知机制可以很好地实现分布式环境下不同系统之间的通知和协调,实现数据变化的实时处理。使用方法通常是不同的系统在ZooKeeper上注册同一个Znode,监听Znode的变化(包括Znode本身及其子节点的内容),其中一个系统更新Znode,然后另一个系统可以收到通知并做出相应的动作。处理。另一种心跳检测机制:检测系统和被检测系统不直接关联,而是通过ZooKeeper上的一个节点关联,大大降低了系统耦合度。另一种系统调度方式:一个系统由控制台和推送系统组成,控制台的职责是控制推送系统进行相应的推送工作。管理者在控制台上所做的一些操作实际上会修改ZooKeeper上某些节点的状态,ZooKeeper会将这些改变通知给注册Watcher的客户端,也就是推送系统。于是做了相应的推送任务。另一种工作报告模型:类似于任务分配系统。子任务启动后,去ZooKeeper注册一个临时节点,并定期汇报其进度(将进度回写到这个临时节点)。这样任务管理器就可以实时了解任务的进度。分布式锁分布式锁主要受益于ZooKeeper,为我们保证了数据的强一致性。锁服务可以分为两类:一类是保持独占,一类是控制时序。所谓独占性,就是在所有试图获取锁的客户端中,最终只有一个能够成功获取到锁。通常的做法是将ZooKeeper上的一个Znode看成一把锁,通过创建znode来实现。所有客户端创建一个/distribute_lock节点,创建成功的客户端最终拥有锁。控制时序是指所有查看锁获取锁的client最后都会被调度执行,但是有一个全局的时序。做法和上面基本类似,只是这里已经预先存在/distribute_lock,客户端在其下创建一个临时的有序节点(这个可以通过节点的属性:CreateMode.EPHEMERAL_SEQUENTIAL来指定)。ZooKeeper的父节点(/distribute_lock)维护一个序列来保证子节点创建的时序,从而形成每个client的全局时序。由于同一节点下的子节点名称不能相同,所以只要在某个节点下创建了一个Znode,创建成功就意味着加锁成功。注册一个监听器来监听这个Znode,只要这个Znode被删除,就会通知其他客户端锁定它。创建临时顺序节点:在某个节点下创建一个节点,当有请求来的时候再创建一个节点。因为是顺序的,所以序号最小的会获得锁。当锁被释放时,通知下一个序号获得锁。在分布式队列方面,有两种队列:一种是常规的先进先出队列,另一种是等待队列成员聚集后,再按顺序执行。第一类队列的基本原理与前面提到的分布式锁服务中的时序控制场景相同,这里不再赘述。第二种队列其实是在FIFO队列基础上的增强。通常,可以在/queueZnode下预先建立一个/queue/num节点,值n(或直接将n赋值给/queue)表示队列大小。之后每有一个队列成员加入,就判断是否达到队列大小,决定是否可以开始执行。这种用法的一个典型场景是:在分布式环境中,一个大任务TaskA需要完成(或者有条件地准备好)才能执行很多子任务。此时,如果其中一个子任务完成(就绪),则去/taskList创建自己的临时计时节点(CreateMode.EPHEMERAL_SEQUENTIAL)。当/taskList发现它下面的子节点个数满足指定个数时,可以进行下一步,依次处理。使用dokcer-compose搭建集群上面我们介绍了ZooKeeper的应用场景非常多,那么我们先来学习一下如何搭建ZooKeeper集群,然后对上面的应用场景进行实践。文件目录结构如下:├──docker-compose.yml写入docker-compose.yml文件docker-compose.yml文件内容如下:version:'3.4'services:zoo1:image:zookeeperrestart:始终主机名:zoo1端口:-2181:2181环境:ZOO_MY_ID:1ZOO_SERVERS:server.1=0.0.0.0:2888:3888;2181server.2=zoo2:2888:3888;2181server.3=zoo3:2888:3888;2181imagezoozookeeper重启:总是主机名:zoo2端口:-2182:2181环境:ZOO_MY_ID:2ZOO_SERVERS:server.1=zoo1:2888:3888;2181server.2=0.818.8server.13:0818.8.0。:2888:3888;2181server.3=0.0.0.0:2888:3888;2181在这个配置文件中,Docker运行了三个Zookeeper镜像,通过ports字段将本地的2181、2182、2183端口绑定到对应容器的2181端口。ZOO_MY_ID和ZOO_SERVERS是搭建Zookeeper集群需要的两个环境变量。ZOO_MY_ID标识服务的id,是1-255之间的整数,在集群中必须是唯一的。ZOO_SERVERS是集群中主机的列表。在docker-compose.yml所在目录执行docker-composeup,可以看到启动日志。连接ZooKeeper启动集群后,我们就可以连接ZooKeeper对其进行节点相关的操作。首先你需要下载ZooKeeper。解压它。转到其conf/目录并将zoo_sample.cfg更改为zoo.cfg。配置文件说明#每个tick的毫秒数#tickTime:CS通信心跳数#Zookeeper服务器之间或者client和server之间保持心跳的时间间隔,即每tickTime发送一次心跳。tickTime以毫秒为单位。tickTime=2000#initial#同步阶段可以进行的tick数#initLimit:LF初始通信时间限制#follower服务器(F)和leader服务器(L)在集群中(tickTimes的数量)。initLimit=5#Thenumberofticksthatcanbepassedbetween#发送请求和得到确认#syncLimit:LF同步通信时间限制#在follower服务器和leader服务器之间的请求和响应之间可以容忍的最大心跳数集群(滴答次数)。syncLimit=2#存储快照的目录。#不要使用/tmp来存储,这里的/tmp只是#示例而已。#dataDir:数据文件目录#Zookeeper保存数据的目录。默认情况下,Zookeeper将写入数据的日志文件也存放在该目录下。dataDir=/data/soft/zookeeper-3.4.12/data#dataLogDir:日志文件目录#Zookeeper保存日志文件的目录。dataLogDir=/data/soft/zookeeper-3.4.12/logs#客户端连接的端口#clientPort:客户端连接端口#客户端连接到Zookeeper服务器的端口。Zookeeper会监听这个端口,接受客户端的访问请求。clientPort=2181#客户端连接的最大数量。#如果你需要处理更多的客户端,请增加这个值#maxClientCnxns=60##在打开自动清除之前一定要阅读#管理员指南的维护部分。##http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance##要在dataDir中保留的快照数量#autopurge.snapRetainCount=3#以小时为单位的清除任务间隔#设置为“0”以禁用自动清除功能#autopurge.purgeInterval=1#服务器名称和地址:集群信息(服务器编号,服务器地址,LF通信端口,选举端口)#这个配置项的写法比较特殊,规则如下:#server.N=YYY:A:B#其中N代表服务器编号,YYY代表服务器IP地址,A代表LF通信端口,代表集群中服务器与leader交换信息的端口。B是选举端口,是指选举新的leader时服务器之间相互通信的端口(当leader挂掉后,其余的server会相互通信,选出新的leader)。一般来说,集群中每台服务器的A端口都是一样的,每台服务器的B端口也是一样的。但是使用伪集群时,IP地址都是一样的,只是A口和B口不同。无需修改zoo.cfg即可使用默认配置。接下来在解压后的bin/目录下执行命令./zkCli.sh-server127.0.0.1:2181进行连接。欢迎使用ZooKeeper!2020-06-0115:03:52,512[myid:]-INFO[main-SendThread(localhost:2181):ClientCnxn$SendThread@1025]-打开与服务器localhost/127.0.0.1:2181的套接字连接。不会尝试使用SASL进行身份验证(未知错误)JLine支持已启用2020-06-0115:03:52,576[myid:]-INFO[main-SendThread(localhost:2181):ClientCnxn$SendThread@879]-Socketablish连接到localhost/127.0.0.1:2181,启动session2020-06-0115:03:52,599[myid:]-INFO[main-SendThread(localhost:2181):ClientCnxn$SendThread@1299]-在服务器localhost/127.0上完成会话建立.0.1:2181,sessionid=0x100001140080000,negotiatedtimeout=30000WATCHER::WatchedEventstate:SyncConnectedtype:Nonepath:null[zk:127.0.0.1:2181(CONNECTED)0]接下来可以使用命令查看节点:使用ls命令查看ZooKeeper中当前包含的内容。命令:ls/[zk:127.0.0.1:2181(CONNECTED)10]ls/[zookeeper]创建一个新的znode节点zk和与之关联的字符串。命令:create/zkmyData[zk:127.0.0.1:2181(CONNECTED)11]create/zkmyDataCreated/zk[zk:127.0.0.1:2181(CONNECTED)12]ls/[zk,zookeeper][zk:127.0.0.1:2181(CONNECTED)13]获取znode节点zk。命令:get/zk[zk:127.0.0.1:2181(CONNECTED)13]get/zkmyDatacZxid=0x400000008ctime=MonJun0115:07:50CST2020mZx40=0x080=0x400MonJun0115:07:50CST2020pZxid=0x400000008cversion=0dataVersion=0aclVersion=0ephemeralOwner=0x0dataLength=6numChildren=0删除znode节点zk。命令:delete/zk[zk:127.0.0.1:2181(CONNECTED)14]delete/zk[zk:127.0.0.1:2181(CONNECTED)15]ls/[zookeeper]转自:不学习无数程序员,链接:jianshu.com/p/6d349acf48aa
