构建具有跨域容灾能力的 Zookeeper 服务
Zookeeper 是 Apache 的顶级项目,为应用提供高效、高可用的分布式协调服务,包括数据发布/订阅、负载均衡、命名服务、分布式协调/通知及分布式锁等。其具有使用便捷、性能高及稳定性良好的特点,被广泛使用在很多的开源组件中,例如 Kafka、Hadoop、Dubbo 等,这些服务使用 Zookeeper 作为配置或者注册中心等。
1、跨域容灾的基本概念
高可用性是计算机系统比较重要的运行指标,系统在运行过程要因为避免软硬件故障造成服务不可用。保证系统高可用性的架构设计的核心准则是组件的冗余及故障的自动转移,除了通过多个节点构成集群来实现冗余外,还要考虑集群要在多个机房、多区域(同城及异地)等实现灾备等多个要素,保证从单个节点到数据中心故障甚至在自然灾难情况造成区域不可用情况下服务可以正常运行。
目前有多种高可用方案,包括集群的分布式架构、同城多机房、异地多机房等,其中”两地三中心”架构是目前业界典型的保证高可用性的跨地域容灾方案。
两地三中心包括了生产中心、同城灾备中心和异地灾备中心,其中:
生产中心,对外提供服务
同城灾备中心,第一级容灾保护,通常在离生产中心几十公里距离建立的同城灾备中心,应用可在不丢失数据的情况下切换到同城灾备中心
异地灾备中心,在离生产中心几百或上千公里外建立的异地灾备中心,应对区域性重大灾难,实现周期性异步复制灾备,这是两地三中心容灾方案的第二级容灾保护
在两地三中心方案中核心的问题包括:数据同步、网络时延及故障切换等问题
1)数据同步,各中心中数据要通过同步来保证一致性,可以分为实时同步及异步同步两种方式,要根据场景选择不同的方式,例如金融类的场景要实现实时,但是数据分析类的应用可接收数据有一定的延迟来选择异步。
2)网络时延,同城机房时延在 1ms~3ms,异地灾备中心通常与生产中心的距离较远时延会达到百毫秒。在业务运行过程中要跨机房的服务调用会给业务的可用性带来较大挑战。
3)故障切换与恢复,对于高可用性要求高的服务要求故障时快速切换,手动进行服务切换无法满足高可用的要求,故障后要实现业务的自动切换,保证最小化影响时间。
2.系统架构
Apache Zookeeper 是分布式集群服务,由一组主机组成。在生产环境中通常由 3 台以上组成高可用的一致性分布式系统,系统架构如下所示:
客户端程序会选择和集群中任意一台服务器创建 TCP 连接,并且客户端和服务器断开连接后可以连接其他服务器。在集群中 Zookeeper Server 分为三种角色:
Leader,由选举产生,负责进行投票的发起和决议,更新系统状态
Follower,用于接受客户端请求并向客户端返回结果,在选举过程中参与投票。可以将 Follower 配置为只读服务(readonlymode.enabled)
Observer,为了扩展系统并提高读取速度,接受客户端连接及读写请求,并将写请求转发给 Leader,但 Observer 不参与投票过程,只同步 Leader 的状态。
客户端(Client),请求的发起方
Zookeeper 配置多个实例共同构成一个集群来对外提高服务以达到水平扩展的目的,每个实例中的数据是相同的,并且均可以对外提供读写服务,对于客户端来说每个服务器实例是相同的。
2.1 选举过程在 Zookeeper 中客户端可以读取任意服务实例的数据,因此要保证集群中每个实例中数据的一致性,客户端的请求分为两种:事务性的会话请求,即更改集群的数据的请求,包括节点的创建、节点数据的写入、ACL 的配置、节点的删除等;读类型请求,读取节点、读取节点数据、获取 ACL 信息、配置节点 Watch 等,事务性的会话请求影响集群中实例的数据的一致性。
对于事务性的会话请求,Zookeeper 集群的服务端会将请求统一转发给 Leader 服务器进行操作,Leader 服务器内部执行该事务性的会话请求的过程中会将数据同步写到其他角色服务器,从而保证会话请求的执行顺序,进而保证整个 Zookeeper 集群的数据一致性。因此 Zookeeper 集群中 Leader 是保证分布式数据一致性的关键所在,集群中的服务实例启动后由选举机制来决定 Leader,当 Leader 服务器宕机后,整个集群将暂停对外服务,从而进入新一轮 Leader 选举。Zookeeper 使用 ZAB 协议(Paxos 算法的扩展)作为数据一致性算法,在协议中有以下状态:
Looking,系统刚启动或者 Leader 崩溃后正在处于选举状态
Following,Follower 节点所处状态,与 Leader 进行数据同步
Leading,Leader 所处状态,当前集群中有一个 Leader 为主进程
Observing,观察状态,同步 Leader 的状态,但是不参与选举
节点状态之间的转换如下图所示:
Zookeeper 启动时所有节点的初始状态为 Looking 或者 Observing,这个时候集群会尝试选举一个 Leader 节点(Observer 节点不参与选举)。选举出的 Leader 节点切换为 Leading 状态,当节点发现集群中已经选举出 Leader 则该节点会切换到 Following 状态,然后和 Leader 节点保持同步。
选举开始后处于 Looking 状态的节点向其他节点发送选举自己为 Leader 的请求信息,在信息中包括:
服务器 ID,编号越大则在选举算法中的权重越大
数据 ID,存放的最大数据 ID,值越大则权重越大
逻辑时钟(Epoch),投票的次数,同一轮投票过程中的逻辑时钟值是相同的,每投完一轮则这个数据就会增加,根据接收到的其他服务器返回的投票信息中的对比,根据不同的值做出不同的判断
选举状态,节点所处的状态
2.2 事务性会话请求处理流程
客户端向集群中任意实例发起事务性会话请求后,该实例会将请求转发至 Leader,由 Leader 进行请求的处理,执行流程如下图所示:
客户端进行事务性(写数据)请求时会指定 Zookeeper 集群中 Server 节点,如果该节点为 Follower 或者 Observer 节点,则将写请求转发给 Leader。Leader 通过内部的协议进行原子广播,直到一半以上的 Server 节点都成功写入数据,这次写请求便算是成功,然后 Leader 会通知相应的 Follower 节点写请求成功,该节点向 Client 返回写入成功响应。
2.3 读请求数据处理
读流程比较简单,由于 Zookeeper 集群中所有 Server 节点都拥有相同的数据,所以读取时可以在任意 Server 点上,读取过程如下图所示:
客户端连接到集群中某个节点,获取数据后直接返回
3.跨域容灾方案
Zookeeper 集群有多个服务实例,超过一半节点正常工作,则集群就能够正常运行,服务本身就是高可用的。但是要考虑数据中心(机房)故障时如何保证整个集群能够正常对外提供服务。解决多数据中心容灾的方案有单机房增加节点、同城多机房、两地三中心(异地容灾)等,针对以上几种方案的对比,如下表所示(后面篇幅会进行详尽分析):
其中同城多机房、两地三中心跨地域部署及独立部署方案,均可以达到跨域容灾的目的,目前 Zookeeper 原生的客户端 SDK 在选定 zk 节点不可达时不会进行 failover 到可用节点,因此使用 zookeeper 作为基础服务的服务都会进行扩展,包括定制化 HostProvider、使用框架 curator 或者在 zk 连不上时重试其他节点等实现自动故障切换。
1)单机房多节点
对于单机房增加节点将其部署在不同的机柜中可以一定程度提高高可用性,避免多个节点同时出现问题
节点选举及事务性请求都需要半数以上成功则算是成功,因此多增加节点对写请求及选举过程的影响都比较大,并且这种方案不能解决整个机房出现故障的容灾问题。
2)同城多机房
单机房做不到容灾,在同城进行多机房的部署,如下图所示:
多机房部署要考虑某机房故障情况下,保证有半数以上的 Zookeeper 节点正常工作,如果仅两个机房的情况下无法保证,上例中三机房部署是可以实现任意一个机房故障情况下,满足正常节点上大于半数以上,保证机房容灾。
3)两地三中心(跨域部署)
多机房级别的容灾对于一般的业务是足够的,但是无法解决服务的异地容灾问题,这里就需要两地三中心的方案来实现,如下图所示:
异地部署最大的问题是网络传输时延比较大,导致集群的投票节点的决策实现比较长,在事务性中需要半数节点同意提案则认为请求成功,因此影响请求的处理性能及集群的运行。
在生产环境中两地三中心是最常见、容灾性最好的部署方案,在 Zookeeper 中要充分考虑投票的过半原则,假定 zk 集群中集群总数为 N
地区 1 中心 1,将其作为生产中心,其分配的节点也要少于 1 半,分配节点数量
地区 1 中心 2 ,生产中心分配完成之后,与异地数据中心平分节点
地区 2 中心 1,则剩余节点
4)两地三中心优化方案
异地的网络比较复杂,很容易出现由于网络故障而重新选举导致整个集群的不可用,因此可以选择在生产中心进行集群部署,其他中心使用 Observer 节点,部署示例如下所示:
Observer 节点启动从 Leader 中同步数据,不参与选举及投票,因此对集群的事务性请求及选举没有影响。其仅提供读请求,因此在实现异地容灾的情况下一定程度上提升读性能。
但是存在的问题是 Observer 不支持事务性请求,并且 Leader 故障后无法同步数据(需要验证),不能对外提供服务。因此这种方案需要在生产中心节点故障后,将手动的将容灾中心的 Observer 切换成 Participant 模式,使其参与选举,对外提供事务性请求的服务。
两地三中心(独立部署)
以上方案各自存在着一定的问题,包括时延导致不稳定、写请求性能低、无法实现异地容灾等,还有一种方案是将各中心的集群独立部署,运行过程中进行数据的实时同步
服务使用生产中心的 Zookeeper 集群,其他灾中心的集群可异步同步数据,在保证集群性能的同时也实现了容灾。但是 Zookeeper 集群的客户端还不支持生产集群故障后自动 Failover 到灾备中心集群,无法做到故障的实时切换,并且还要引入集群外的数据同步工具。
4.跨域容灾演练
在双中心容灾架构下,如果双方权重相等,无法做出判断,权重不等情况下,如果权重大的机房受灾则剩余一个权限不够也无法起到作用,因此在两地三中心方案中通过第三个数据中心进行仲裁,这也是异地容灾的核心。这里进行两地三中心(统一集群部署)的容灾测试,核心关注点:
数据是否同步
跨域(不同城)情况下,时延是否会造成 Leader 选举异常
读写性能
客户端应用在生产中心集群故障时,是否实现自动故障切换
4.1 跨域容灾部署
这里选用资源池 A 和资源池 B 作为两地的机房,资源池 A 使用可用区 1 和可用区 2 作为同城区域中心,部署主机配置采用<4 VCore,16GB>,后续会在资源池 A 可用区 1 的主机上部署 kafka 集群作为应用服务。
1)申请主机,分别在移动云的资源池 A 和资源池 B 共申请 5 台虚拟机,开启公网,操作系统 BC-Linux 7.6,申请成功后进行网络时延测试
资源池 A1 到资源池 A2 的网络时延,基本在 2ms 左右
资源池 A2 到资源池 B3 的网络延迟在 30ms 左右
跨地域的主机之间的网络时延时间在可接受的范围内,不会影响整个集群的运行,并且 zookeeper 服务是以读请求为主,可选择就近节点进行数据读取,跨域部署不会影响读性能,可以保证性能在可接受的范围。
1)部署 jdk 及 zookeeper,jdk 部署不再介绍,zookeeper 的 zoo.cfg 核心配置:
启动命令:
启动后在三个可用区中可以连接对应的 zk Server,如下所示:
ZK 集群启动成功,目前 Leader 阶段为资源池 B3 可用区 1
1)测试 1
连接任意 zkServer,然后创建 ZK Node:/example,数据后: hello zk,成功后连接任意节点,均可以查看到/example,集群启动成功。
2)测试 2,通过 JDK API 测试 zk 客户端,核心代码如下:
运行后信息获取正常。
4.2 容灾故障演练
1) 模拟生产中心故障,停止两个 zk Server(资源池 A1,生产中心的两个节点),资源池 A1 的 zk server 连接失败,其余节点的 zk 连接正常
当 Leader 故障后 Follower 快速切换成 Leader。
2)客户端应用的自动故障切换,部署 Kafka 集群,配置 server.properties
kafka 集群启动命令执行如下:
集群启动后:
进行 benchmark 测试:
3) 模拟 zk 故障对服务的影响,停掉 leader zk,运行日志如下所示:
运行正常,再停掉相同可用区的 zk server,对服务不会造成影响。再停掉资源池 A 可用区 2 的 zk server,由于 zk 故障个数超过了 1/2,zk 集群故障。
评论