理解Zookeeper(六):Zookeeper的应用场景
Zookeeper是一个发布/订阅模式的分布式数据管理与协调框架,结合Watcher事件通知,可以搭建分布式框架中的很多核心功能。
数据发布和订阅
也就是常用的配置管理,将数据信息发布到一个或者多个ZK节点上,应用程序监听这些节点当有数据变化时就获取这些变化并应用到程序中,实现动态更新配置的功能。发布订阅中通常有推拉两种模式,ZK采用推拉结合,客户端注册感兴趣的节点,一旦节点发生变化服务器会发出Watcher事件通知(推),然后客户端收到消息后主动去服务器提取变化(拉)。
这种配置管理通常应用在具有配置通用性的场景中,比如机器列表、某些参数的开关或者数据库配置信息等。
-
这些信息通常体量小
-
在应用运行时会随时调整
-
这些应用(至少是某一组应用)都使用相同的配置
常规解决办法是本地配置文件或者内存变量(通过JMX方式对正在运行的JAVA程序进行修改)。这些常规办法在集群规模较小的时候比较方便但是集群规模一大管理就比较困难。
比如数据库配置信息变化
-
首先在ZK上创建一个节点叫做 /Configuration 这里面有一个APP名称节点表示特定APP,这个APP名称节点下面有一个叫做 dbConfig的节点用于存储数据库配置信息(/Configuration-->APP1-->dbConfig)
-
然后将配置信息写入到这个节点(这些信息肯定是序列化后写入的,如果在程序中不是以对象形式存在也可以不用序列化)
-
应用程序启动后读取这个节点数据,然后在这个节点上注册一个Watcher事件,当数据有变化时再次读取该节点数据并应用到自己的程序上。
负载均衡
ZK提供的负载属于软负载均衡服务。通常的做法就是动态的DNS服务,如果你的机器数量比较少你可以手动在内网DNS上配置这些域名,你要知道一个域名就对应一个IP。如果集群规模比较大手动维护这些信息相当繁琐;另外你还可以采用HOST的方式就是在服务器上配置本地HOST把这些域名写进去,还是一样的问题规模小传统方式都很好用,但是规模大尤其是有时候需要动态扩容或者缩容的时候你再去手动维护这些HOST或者DNS记录显然就不能满足业务需求。
在Zookeeper中如何解决呢?下面的名称就是名字不一定你也这样取,毕竟节点名称是自定义的。
-
首先建立一个节点叫做 /DDNS 这下面包含所有应用的名字比如APP1,APP2等,每个应用名字节点下面又一个域名比如 app1.servers.abc.com这样一个域名(/DDNS-->APP1-->app1.servers.abc.com)后面这个域名就是改APP1集群使用的,这个节点包含的数据可以是IP:PORT或者多个IP:PORT,看下图
-
应用读取节点数据获取IP列表,然后根据负载均衡算法找到一个IP来使用,之后注册一个监听事件来监听该节点的变化
-
IP变更我们只需要在该节点进行操作就可以
上面的过程还是需要人工干预,如何改进一下呢?可以划分几个组件出来
-
Register,负载域名动态注册的服务,这个服务可以是单台但最好是一个集群,服务提供者通过SDK向Register注册,也就是把服务提供者所使用域名和IP:PORT发给Register,它再获取ZK集群节点数据,加上本次注册的数据,产生一个新数据更新到ZK节点上
-
Dispatcher,负载域名解析,通过域名向该服务查询获取IP:PORT列表,它监听ZK节点发生有变化之后就更新存储在自己内存中的信息,相当于域名使用者不直接去ZK节点读取数据。同时它还提供屏蔽功能,也就是说服务提供者都可用但是我可以设置只解析出部分服务提供者IP:PORT出来。
-
Scanner,负载检查维护服务状态,它作用是检查服务提供者的可用性,这里采用服务提供者主动汇报自身状态的方式,这种状态可以包括很多信息服务是否可用、当前QPS、RT、JVM的资源状态等。Scanner会记录服务提供者每一次的状态。如果一段时间后(通常为几秒)发现有服务提供者没有汇报,那么就认为不可用,然后去更新ZK节点,把失败的IP:PORT信息删除。
-
Monitor,负载监控DDNS系统本身的状态。
-
Controller,是一个DDNS的后台管理端,负载管理上述组件以及必要时候的手工干预。
-
SDK,服务提供者和消费者通过封装的SDK来和上述组件通信。
命名服务
命名服务是分布式系统中的基本功能之一。被命名的实体通常可以是集群中的机器、提供的服务地址或者远程对象,这些都可以称作为名字。常见的就是一些分布式服务框架(RPC、RMI)中的服务地址列表,通过使用名称服务客户端可以获取资源的实体、服务地址和提供者信息。命名服务就是通过一个资源引用的方式来实现对资源的定位和使用。在分布式环境中,上层应用仅仅需要一个全局唯一名称,就像数据库中的主键。
在单库单表系统中可以通过自增ID来标识每一条记录,但是随着规模变大分库分表很常见,那么自增ID有仅能针对单一表生成ID,所以在这种情况下无法依靠这个来标识唯一ID。UUID就是一种唯一识别码在分布式中也会用到,最典型的GUID,全局唯一标识符。但是长度过长不易识别。
在Zookeeper中通过创建顺序节点就可以实现。你创建的时候只需要提供节点名称,至于序号它自己会返回一个。你每次使用的语句是相同的,但是由于顺序节点的特性它返回的内容不同但是是有顺序的。那么你通过 type1-job00000000 这种就可以得到一个唯一ID。
分布式协调/通知
集群管理
集群管理包括集群监控和集群控制。前者侧重收集集群运行状态,后者侧重对进行的操作和控制。场景的需求是
-
获取当前集群有多少机器
-
每个机器运行的状态
-
对集群机器上下线操作
在传统方式中通常使用Agent,Agent收集服务器信息向监控中心上报。在小规模环境中这种方式还比较适用。但是集群规模变大问题也就来了。
-
大规模升级Agent困难,升级成本和进度
-
统一的Agent无法满足多样要求,对硬件资源比较好办,但是如果涉及到对程序内部的监控Agent就容易束手无策了,这种东西不适合用一个统一的Agent来监控
-
编程语言多样,你需要提供各种语言版本的Agent
通过Zookeeper怎么解决?利用节点监听和临时节点特性。
Master选举
简单来说就是争抢Zookeeper上个一个节点且这个节点只能被一个机器争抢到,其他没有抢到的就在这个节点上注册监听变化。
分布式锁服务
锁服务很好理解拿到锁就可以对外提供服务简单来说就是拿到锁的应用可以执行。比如相同的定时任务为了冗余部署在多个机器上,但是这种任务同一时刻只能执行一次,那么我们就可以利用Zookeeper来实现,这些服务到了时间点都会执行,那么到底谁可以执行呢?这就需要去争抢一个锁,谁拿到了谁就执行,其他没拿到的就不执行。