在互联网大行其道的今天, 各种分布式系统已经司空见惯. 搜索引擎、电商网站、微博、微信、O2O平台等, 凡是涉及到大规模用户、高并发访问的, 无一不是分布式[1]. RPC作为分布式架构的核心, 一旦出现问题, 整个分布式网络就会出现瘫痪, RPC框架必须做到高可用才能保证分布式网络的稳定. ICE中间件作为一款跨语言开发的RPC框架, 因其高效的性能和完整的服务, 被惠普等大型企业所采用.
Zookeeper是开放源码的分布式应用程序协调服务, 许多分布式软件都采用Zookeeper作为集群[2]管理, Zookeeper本身也支持高可用, 随着软件版本的更新, Zookeeper的高效与稳定很适合作为分布式环境下的高可用服务[3].
1 Zookeeper技术简介Zookeeper是Hadoop的正式子项目, 是一个高效可靠的开源的分布式协调服务框架, 由知名互联网公司雅虎创建, 是Google公司的分布式服务框架Chubby的开源实现. 在分布式环境下, Zookeeper可以保证顺序一致性, 原子性, 可靠性和实时性.
Zookeeper本质上是一个分布式的小文件存储系统, 具有下述应用特性:
(1) 简单. Zookeeper允许通过分布式程序共享方式, 组织成类似标准文件系统层级命名空间协调的分布式程序. 这个命名空间包含类似文件和目录的数据寄存器, 不像典型的用于存储的文件系统. Zookeeper的数据保存在内存中, 可实现高吞吐量和低延迟访问.
(2) 冗余. Zookeeper本身和其自身协调的程序一样, 设计为冗余的, 在拥有许多主机集合的主机组上运行. 组成Zookeeper服务群的所有服务器都已知对方, 只要所有服务器中的主服务器可用, Zookeeper服务就是可用的. 客户端链接的一个Zookeeper服务器通过发送请求、获取响应、监听事件、发送心跳维持一个TCP(传输控制协议)链接, 如果链接中断, 客户端将连接到另外一个服务器
(3) 有序. Zookeeper用一个反映所有Zookeeper事务顺序的数字, 标记每一个更新. 以后的操作可以使用这个顺序数字标记.
(4) 快速. Zookeeper在读取占主要地位的负载环境下, 在读取操作比写入操作更频繁的情况下, 读取速度非常快.
(5) 丰富的API(应用程序编程接口). Zookeeper为开发人员提供了一套丰富的API, 减轻了开发人员编写通用协议的负担[4].
1.1 Zookeeper的配置管理程序在启动运行中总是需要配置文件, 如果程序分散部署在多台机器上, 要逐个改变所在机器的配置文件就变得困难. 现在可以把这些配置全部放到Zookeeper上去, 保存在Zookeeper的某个目录节点中, 然后所有相关应用程序对这个目录节点进行监听, 一旦配置信息发生变化, 每个应用程序就会收到Zookeeper的通知, 然后从Zookeeper获取新的配置信息应用到系统中[5]. 基本过程如图1.
1.2 Zookeeper的集群管理
所有机器约定在父目录father下创建临时目录节点, 然后Zookeeper监听父目录节点的子节点的消息变化. 一旦有机器出现问题, 该机器与Zookeeper的连接就会断开, 其所创建的临时目录节点就会被删除, 所有的其他机器都会收到通知.
当有新机器加入, 就会在father父目录下创建临时节点, Zookeeper会把这个消息发送给父目录下的所有机器, 所有机器就会收到新机器加入的通知. 如图2.
1.3 Zookeeper的选举机制
Zookeeper的角色主要有3类: 领导者(leader)、学习者(learner)和客户(client), 三者关系如图3所示. 领导者负责处理写请求和系统状态更新请求; 学习者负责接收领导者发送的请求并对请求进行投票, 以及客户端的读写请求; 客户端负责通过TCP链接向学习者发送请求和获取响应. 学习者又可细分为跟随者(follower)和观察者(observer), 两者的区别为: 观察者只同步领导者的状态, 但不参与领导者失效时的竞选和投票[6].
当领导者失去响应时, 进入恢复模式(竞选), 某服务器推荐自己作为领导者, 将消息发送给集群, 等待其它服务器接收该举荐信息. 若同意, 向领导者服务器发送同意消息; 若不同意, 重复自荐流程, 直到获得N/2+1票数后, 才被认定为领导者, 整个竞选流程结束. 具体流程如下:
首先, 某台服务器发起选举线程, 选举线程向所有服务器发起一次询问. 当收到回复, 选举线程验证zxid(Zookeeper transaction ID), 判断是否为自己发起的询问, 并获 取对方id, 同时保存到此次选举投票记录表中. 当收到所有服务器回复后, 选举线程计算并选出zxid值最大的服务器, 将该服务器的信息, 设置为被推荐的领导者. 被推荐的领导者发起选举, 向其它服务器发送PROPOSAL消息, 其它服务器接收该消息后, 进行投票; 若同意, 则发送ACK消息. 此时, 被推荐的领导者判断是否有超过N/2+1的服务器同意; 若超过, 则被推荐的领导者获胜; 否则, 重复流程, 直到领导者被选出.
1.4 Watcher机制客户端在所关心的目录节点上注册Watcher, 当被监听的节点状态发生变化时(即数据改变、被删除、子目录节点增加或删除时), 会触发WatchedEvent事件, 并发送给对其进行监听的客户端. Watcher机制可以保证整个集群系统中各个服务器之间的通信和协调, 实现对数据变更的实时处理. Zookeeper所提供的基本操作包括: create(创建节点)、delete(删除节点)、setData(设置节点数据)、getData(获取节点数据)、exists(查看节点是否存在)、以及getChildren(获取子节点)等. 其中, exists、getChildren、getData操作可以在节点上注册Watcher; 而create, delete, setData操作可以触发设置在节点上的Watcher[7].
在该机制中, 服务端存储事件的信息, 客户端存储事件的信息和Watcher的执行逻辑. 当在某节点上注册Watcher事件时, 客户端线程向服务器注册Watcher; 同时, 在Watcher对象管理器中存储Watcher对象以及对应的节点路径. 当某节点触发Watcher事件时, 服务器向对应的客户端发送通知(包含事件类型和节点路径), 该客户端线程从Watcher对象管理器中根据映射关系取出对应的Watcher对象, 执行回调函数. 其中, 回调函数包含WatchEvent类型的参数, 该类型参数(WatchEvent)封装事件的通知状态(keeperState)、事件类型(EventType)和节点路径(path), 方便回调函数对事件进行处理.
2 ICE中间件简介ICE是一种新的面向对象的中间件技术. 从根本上说, ICE为构建面向对象的客户-服务器应用提供了工具、API和库支持[8]. 可用于替代像CORBA或COM/DCOM/COM+这样的中间件, 为构建面向对的分布式客户服务器应用提供了平台支持. ICE应用适合在异种环境中使用: 客户和服务器可以用不同的编程语言编写, 可以运行在不同的操作系统和机器架构上, 并且可以使用多种网络技术进行通信. 在最新的3.7版本中, 支持c++, c#, .Net, java, javascript, objective-c, python, ruby等编程语言, 操作系统支持windows, linux和mac os, 支持最新的docker部署, 并且无论部署环境如何, 这些应用的源码都是可移植的.
ICE定义了自己的规范语言Slice, 它用于使对象接口与其实现相分离的基础性抽象机制. 客户和服务器可以用不同的编程语言编写, 可以运行在不同的操作系统和机器架构上, 并且可以使用多 种网络协议进行高效通信.
ICE客户与服务器内部的逻辑结构如图4所示, ICE核心为远地通信提供了客户端和服务器端运行时支持, ICE核心是作为客户和服务器可与之链接的库提供的, 客户ICE核心、服务器ICE核心和对象适配来自于库代码, 代理和骨架由Slice定义生成. 客户应用和服务器应用来自于程序员编写的上层应用代码[9].
3 IceGrid服务架构分析IceGrid是ICE的核心服务, 提供了若干基本的服务, 可以简化分布式应用的开发和部署[9]. IceGrid主要由registry和node组成, registry主要用来为客户端提供定位服务, 并且管理部署的node节点, node用来装载服务, 根据registry的指示对服务进行部署与管理.
定位服务: 保证服务节点的位置透明性. 当客户端需要访问某一个服务时, 不需要了解服务的具体位置, 直接访问registry, 由registry将服务的具体位置告诉客户端, 做到服务端的位置透明.
服务管理: 管理服务的注册, 启动与关闭. 所有的服务都需要向registry进行注册, 当服务需要被访问时, 由registry通知相应的node对服务进行启动与关闭的管理, 服务的负载均衡也由registry进行控制, 整个网络服务的部署也由registry进行管理. registry是整个网络的枢纽.
4 对IceGrid提出的改进IceGrid作为整个ICE网络服务的骨架, 起着至关重要的作用, 所有的分布式网络服务都通过IceGrid进行访问, 当整个网络比较庞大时, 单个registry已经不能满足网络的负载需求, 当registry出现问题时, 整个网络就会瘫痪. 所以registry本身支持主从模式的冗余.
首先启动master. Master的配置文件与slave的配置文件有所不同, master中必须指定registry的名字为master, 以和其他slave进行区分.
远程将部署文件discriptor发送给master. 当master收到部署文件后, 会等待node节点和slave的注册.
启动所有的slave. 当slave启动后, 会主动向master进行注册, 注册的过程中, master会将部署文件发送给所有的slave, 在slave与master之间, 实现了一个发布订阅服务, 当master对部署文件进行更新时, 会通知所有的slave进行部署文件同步.
启动所有的node节点. 当node节点启动后, 会首先向master进行注册, 随后master将相应的部署配置文件发给node, 并将所有slave的列表也发给node, node通过部署文件启动相应的服务. 然后向所有的slave进行注册.
当服务部署完成后, master和slave都可以给客户端提供服务, 都可以对node进行管理, 但是只能通过master对部署文件进行更改, slave对部署文件是只读模式.
4.1 registry主从模式存在的问题(1) 主从切换不能自动进行
当master出现问题时, slave不能主动进行选举切换到master, 如果master出现问题, slave虽然可以继续提供服务, 但是部署文件不能进行更新, 一旦需要更新部署文件, 就必须更改某一个slave的配置为master, 使用新的配置重新启动这个slave, 当整个网络十分庞大复杂时, 会增加运维的成本.
(2) registry要实现的功能太多
registry不仅要提供位置服务, 服务管理, 还需要实现发布订阅服务对部署文件进行同步管理. 当部署文件有更新时, master要通知所有的slave, 并且将部署文件同步到所有的slave中, 当某个slave因为网络问题断开连接重新与master建立连接时, master要重新与slave同步数据库文件. 当网络负载很大时, registry要做的事情太多, 将这么多功能放在一起, 很容易造成网络故障, 进而加剧网络环境的进一步恶化.
4.2 通过Zookeeper对registry主从模式的改进Zookeeper作为一款开源多年的分布式协调服务框架, 支持高可用, 本身非常可靠. 通过利用Zookeeper的配置管理和集群管理功能, 可以解决registry中遇到的问题.
整体的Zookeeper目录结构图如图5, 其中根节点为“/IceGrid”, 下一级包含2个目录节点: “/Configuration”, “/Registrys”.
(1) 利用集群管理解决主从切换问题
在Zookeeper中, 首先建立持久化目录节点(PERSISTENT)“/registrys”, 称为父目录, 用于集中化管理所有的registry服务器, 每一个registry均被创建为父目录下的临时目录节点(EPHEMERAL), 称为子目录, 并在父目录下注册Watcher. 当某一个registry宕机或因网络问题离开集群时, 其会话(session)过期, 对应的临时目录节点被自动删除. 根据Watcher机制, 当被监听的父目录节点状态发生变化时, 即父目录节点下的子目录节点被删除, 就会触发WatchedEvent事件, 关注该父目录节点的所有registry均被通知. 若失去响应的registry恰为master时, 根据选举机制, Zookeeper服务器进入恢复模式(竞选), 触发选举流程, 选出新的master. 同理, 当父目录节点的子目录节点增加时, 关注该父目录节点的所有registry均被通知, 且根据选举机制, Zookeeper服务器进入广播模式(同步), 该新加入的registry与master进行状态同步.
因此, 利用Watcher机制和选举机制, 可解决IceGrid中registry的主从切换问题. 当master出现问题, 由Zookeeper选出新的master, 当有新的registry加入时, 由Zookeeper通知master与新的registry进行状态同步.
(2) 利用配置管理解决部署文件同步问题
在IceGrid中, 由于registry集群中的各个服务节点, 均采用同一套部署文件; 更新部署文件只能通过master进行, 随后master需要通知所有的slave进行部署文件同步, 同步期间, 所有的slava需要与master进行读写操作.
依据Zookeeper节点特有的数据存储能力, 以及Watcher机制, 可以实现部署文件信息的统一管理. 将整个registry的部署信息存储在持久化目录节点(PERSISTENT)“/Configuration”上, 集群中所有registry作为Zookeeper客户端 , 在该节点上注册Watcher. 当该节点上的数据发生更改(即部署信息被管理员修改), 触发设置在该节点上的Watcher. 所有关注该节点的客户端均被通知, 并根据收到的通知更新自身部署信息, 实现与master部署信息的同步. 这样, 每当部署信息被修改, 不需要master对slave进行通知, 由Zookeeper的watcher机制进行通知, 部署文件的同步也由Zookeeper进行同步, master只需要专注于提供其他服务就好, 这样也不需要master与slave进行频繁的读写操作, 降低master的读写压力. 所有的I/O读写全部由Zookeeper集群提供.
5 测试结果通过在3台服务器上部署3个IceRegistry服务, 分别取名为r1, r2, r3这3台服务器都向Zookeeper集群进行注册, 在首次注册中, r2为主节点, r1和r3为从节点, 期间断开主节点r2, 然后通过Zookeeper的配置管理对部署文件进行更新, 发现部署信息仍能在余下的r1和r3中进行同步, 通过Zookeeper的shell命令查看节点信息, 发现r1被选为了新的主节点, IceGrid部署的服务更新成功.
然后重新开启r2, 再通过shell对Zookeeper的文件系统进行查看, r2又重新向Zookeeper进行注册, 并且部署文件也自动同步到了r2的数据库中.
通过以上的集群搭建与测试, 说明IceRegistry的主从节点可以自动切换, 不再需要人工干预. 并且不再需要主节点来同步部署信息或者发现新的从节点, 这些责任全部由Zookeeper集群来做.
6 结论与展望通过将Zookeeper加入到IceGrid框架中, 利用Zookeeper的集群管理可以解决registry不能自动主从切换的问题, 利用Zookeeper的配置管理可以解决registry之间的部署文件同步问题, 改进后的IceGrid将主从切换和部署文件同步等功能剥离到了Zookeeper中, 这样不仅解决了IceGrid之前存在的主从切换的缺陷问题, 也减轻了registry需要承担的任务, 可以更可靠的为client提供定位服务.
本文就ICE的主从切换方面进行了探索, 提出了一种基于Zookeeper的解决方案, 随着中间件技术的不断进步, 还有更多优秀的中间件加入到分布式应用当中来. 因此基于中间件的架构模式会随着广泛应用而不断成熟, 中间件技术会更加适应复杂的网络环境和繁杂的应用场景.
[1] |
Tanenbaum AS, Van Steen M. 分布式系统原理与范型. 杨剑锋, 常晓波, 李敏, 译. 北京: 清华大学出版社, 2004.
|
[2] |
CSDN博客. 分布式与集群的区别. https://blog.csdn.net/bluishglc/article/details/5483162, 2010-04-13.
|
[3] |
Bernstein PA. Middleware: A model for distributed system services. Communications of the ACM, 1996, 39(2): 86-98. DOI:10.1145/230798.230809 |
[4] |
黄毅斐. 基于ZooKeeper的分布式同步框架设计与实现[硕士学位论文]. 杭州: 浙江大学, 2012.
|
[5] |
苟丽美, 张锋叶, 林国华. 基于Zookeeper的GIS集群实现. 计算机工程与设计, 2017, 38(9): 2573-2579. |
[6] |
CSDN博客. Zookeeper的功能以及工作原理. https://blog.csdn.net/xqb_756148978/article/details/52259381, [2016-08-20].
|
[7] |
ZooKeepcr官网. http://zookeeper.apache.org/doc.
|
[8] |
Henning M. A new approach to object-oriented middleware. IEEE Internet Computing, 2004, 8(1): 66–75.
|
[9] |
ZeroC, Lnc. Ice 3.6.4 documentation. https://zeroc.com/download/Ice/3.6/Ice-3.6.4.pdf, 2017.
|