zookeeper集群部署
前言
ZooKeeper 是 Apache 的一个顶级项目,为分布式应用提供高效、高可用的分布式协调服务,提供了诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知和分布式锁等分布式基础服务。由于 ZooKeeper 便捷的使用方式、卓越的性能和良好的稳定性,被广泛地应用于诸如 Hadoop、HBase、Kafka 和 Dubbo 等大型分布式系统中。
运行模式
Zookeeper 有三种运行模式:单机模式、伪集群模式和集群模式。
1.1 单机模式
这种模式一般适用于开发测试环境,一方面我们没有那么多机器资源,另外就是平时的开发调试并不需要极好的稳定性。
在 Linux 环境下运行单机模式需要执行以下步骤:
-
准备 Java 运行环境
由于zookeeper集群的运行需要Java运行环境,所以需要首先安装 JDK,这里安装 Java 1.6 或更高版本的 JDK,并配置好 Java 相关的环境变量 $JAVA_HOME 。 -
下载 ZooKeeper 安装包
下载地址:http://zookeeper.apache.org/releases.html。选择最新的 stable 版本并解压到指定目录,我们用 $ZK_HOME 表示该目录。
为了能够在任意目录启动zookeeper,我们需要配置环境变量。
ps:你也可以不配,这不是搭建集群的必要操作,只不过如果你不配置环境变量,那么每次启动zookeeper需要到安装文件的 bin 目录下去启动。
首先进入到 /etc/profile 目录,添加相应的配置信息:
#set zookeeper environment
export ZK_HOME=/usr/local/zookeeper/zookeeper-3.6.3
export PATH=$PATH:$ZK_HOME/bin
然后通过如下命令使得环境变量生效:
source /etc/profle
- 配置 zoo.cfg
首次使用 ZooKeeper,需要将 $ZK_HOME 下的 zoo_sample.cfg 文件重命名为 zoo.cfg,并进行以下配置
tickTime=2000 ##Zookeeper最小时间单元,单位毫秒(ms),默认值为3000
dataDir=/var/lib/zookeeper ##Zookeeper服务器存储快照文件的目录,必须配置
dataLogDir=/var/lib/log ##Zookeeper服务器存储事务日志的目录,默认为dataDir
clientPort=2181 ##服务器对外服务端口,一般设置为2181
initLimit=5 ##Leader服务器等待Follower启动并完成数据同步的时间,默认值10,表示tickTime的10倍
syncLimit=2 ##Leader服务器和Follower之间进行心跳检测的最大延时时间,默认值5,表示tickTime的5倍
- 启动服务
使用 $ZK_HOME/bin 目录下的 zkServer.sh 脚本进行服务的启动。
1.2 集群模式
zookeeper 集群通常是用来对用户的分布式应用程序提供协调服务的,为了保证数据的一致性,对 zookeeper 集群进行了这样三种角色划分:leader、follower、observer分别对应着总统、议员和观察者。
-
领导者(leader):负责进行投票的发起和决议,更新系统状态。
-
跟随者(follower):用于接收客户端请求并向客户端返回结果以及在选举过程中参与投票。
-
观察者(observer):也可以接收客户端连接,将写请求转发给leader节点,但是不参与投票过程,只同步leader的状态。通常对查询操作做负载。
一个 ZooKeeper 集群通常由一组机器组成,一般 3 台以上就可以组成一个可用的 ZooKeeper 集群了。
组成 ZooKeeper 集群的每台机器都会在内存中维护当前的服务器状态,并且每台机器之间都会互相保持通信。
提示:重要的一点是,只要集群中存在超过一半的机器能够正常工作,那么整个集群就能够正常对外服务。
ZooKeeper 的客户端程序会选择和集群中的任意一台服务器创建一个 TCP 连接,而且一旦客户端和服务器断开连接,客户端就会自动连接到集群中的其他服务器。
那么如何运行 ZooKeeper 集群模式呢?首先假如我们有三台服务器,IP 分别为 IP1、IP2 和 IP3,则需要执行以下步骤:
- 准备 Java 运行环境(同上)
- 下载 ZooKeeper 安装包(同上)
- 配置 zoo.cfg
tickTime=2000
dataDir=/var/lib/zookeeper
dataLogDir=/var/lib/log
clientPort=2181
initLimit=5
syncLimit=2
server.1=IP1:2888:3888
server.2=IP2:2888:3888
server.3=IP3:2888:3888
可以看到,相比于单机模式,集群模式多了 server.id=host:port1:port2 的配置。
其中,id 被称为 Server ID,用来标识该机器在集群中的机器序号(在每台机器的 dataDir 目录下创建 myid 文件,文件内容即为该机器对应的 Server ID 数字)。host 为机器 IP,port1 用于指定 Follower 服务器与 Leader 服务器进行通信和数据同步的端口,port2用于进行 Leader 选举过程中的投票通信。
在多解释下参数内容:
①、tickTime:基本事件单元,这个时间是作为Zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,每隔tickTime时间就会发送一个心跳;最小 的session过期时间为2倍tickTime
②、dataDir:存储内存中数据库快照的位置,除非另有说明,否则指向数据库更新的事务日志。注意:应该谨慎的选择日志存放的位置,使用专用的日志存储设备能够大大提高系统的性能,如果将日志存储在比较繁忙的存储设备上,那么将会很大程度上影像系统性能。
③、client:监听客户端连接的端口。
④、initLimit:允许follower连接并同步到Leader的初始化连接时间,以tickTime为单位。当初始化连接时间超过该值,则表示连接失败。
⑤、syncLimit:表示Leader与Follower之间发送消息时,请求和应答时间长度。如果follower在设置时间内不能与leader通信,那么此follower将会被丢弃。
⑥、server.A=B:C:D
A:其中 A 是一个数字,表示这个是服务器的编号;
B:是这个服务器的 ip 地址;
C:Zookeeper服务器之间的通信端口;
D:Leader选举的端口。
我们需要修改的第一个是 dataDir ,在指定的位置处创建好目录。
第二个需要新增的是 server.A=B:C:D 配置,其中 A 对应下面我们即将介绍的myid 文件。B是集群的各个IP地址,C:D 是端口配置
- 创建 myid 文件
在 dataDir 目录下创建名为 myid 的文件,在文件第一行写上对应的 Server ID。
主机1
echo 1 > /var/lib/zookeeper/myid
主机2
echo 2 > /var/lib/zookeeper/myid
主机3
echo 3 > /var/lib/zookeeper/myid
- 按照相同步骤,为其他机器配置 zoo.cfg 和 myid文件
- 启动服务
#启动命令:
zkServer.sh start
#停止命令:
zkServer.sh stop
#重启命令:
zkServer.sh restart
#查看集群节点状态:
zkServer.sh status
查看各个节点的状态,三台机器中其中一台会被选举成为leader,而剩下的两台成为了 follower。这时候,如果你将leader节点机器关掉,会发现剩下两台又会有一台变成了 leader节点。
1.3 伪集群模式
这是一种特殊的集群模式,即集群的所有服务器都部署在一台机器上。当你手头上有一台比较好的机器,如果作为单机模式进行部署,就会浪费资源,这种情况下,ZooKeeper允许你在一台机器上通过启动不同的端口来启动多个 ZooKeeper 服务实例,以此来以集群的特性来对外服务。
这种模式下,只需要把 zoo.cfg 做如下修改:
tickTime=2000
dataDir=/var/lib/zookeeper
dataLogDir=/var/lib/log
clientPort=2181
initLimit=5
syncLimit=2
server.1=IP1:2888:3888
server.2=IP1:2889:3889
server.3=IP1:2890:3890
迁移实践
迁移要求
对 zookeeper 集群的迁移,需要保证两点:
1)数据不能丢
2)服务不能中断
即对于业务方来说,迁移过程应该是尽量无感知的,不能影响到业务。
实施方案
zookeeper 集群的迁移,针对不同的 zk 部署版本,迁移过程也有所区别。如果 zk 版本 >= 3.5,那么 zk 集群支持动态配置,此时迁移过程会简单很多(参考 zookeeper 官网介绍https://zookeeper.apache.org/doc/r3.5.9/zookeeperReconfig.html)
而对于 zk < 3.5 之前的版本,可以有两种解决方案:
(1)停机迁移
(2)平滑迁移
方案一 停机迁移
这个方案最简单,操作方便,先把旧节点上的 zk 进程停掉,然后将 zk 数据目录 copy 一份到新节点,然后启动新节点进程即可。但缺点是迁移期间无法继续提供服务,而且停机时间视数据目录大小 copy 耗时而定。因此该方案适用于非线上环境 zookeeper 集群迁移
方案二 平滑迁移
该方案的基本思路是先扩展 zookeeper 集群到新节点,然后下线旧节点,在整个迁移过程中 zk 集群可以持续提供服务。因此,平滑迁移可以保证服务不中断,适合线上环境执行,但缺点是操作复杂,容易出错。
具体迁移流程
由于我们目前部署的 zk 集群版本为 3.4.12,为了保证线上服务不中断,我们采用平滑迁移方案。
假定迁移前集群由 server1, server2, server3 组成,迁移后集群由 server4, server5, server6 组成。原有节点配置如下所示:
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
迁移流程如下:
1)新增 ID 为 4,5,6 的节点(注意,新增节点 id 需要大于旧节点 id),并依次启动 server4, server5, server6,其中 server4 需要进行二次重启。
server4 的配置如下:
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
server.4=localhost:2884:3884
server.5=localhost:2885:3885
#需要在启动server6后进行第二次重启并打开剩余节点注释
#server.6=localhost:2886:3886
server5 的配置如下:
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
server.4=localhost:2884:3884
server.5=localhost:2885:3885
server.6=localhost:2886:3886
server6 的配置如下:
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
server.4=localhost:2884:3884
server.5=localhost:2885:3885
server.6=localhost:2886:3886
即新增节点的启动顺序为 server4(列表中不包含 server6) -> server5 -> server6 -> 重启 server4(包含完整列表)。
2)对 1,2,3 节点的配置文件,分别补充新增节点列表,e.g.
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
server.4=localhost:2884:3884
server.5=localhost:2885:3885
server.6=localhost:2886:3886
3)对原节点 1,2,3 中的 follower 节点,分别进行重启
可以使用命令来查看当前集群节点的角色。
echo mntr|nc localhost 2181
假设现在节点 2 是 leader,那么分别重启节点 1 和节点 3(此时除了节点 2,其余节点都能看到集群有 6 个 member)
4)对原节点 1,2,3 中的 leader 节点,进行重启
重启原 leader 节点之前,先确认 follower 节点已经同步完成
echo mntr|nc localhost 2181
zk_followers 5
zk_synced_followers 5
重启旧 leader 后,会选举出新的 leader(一般是 id 最大的节点),假设为节点 6。
至此,我们拥有了一个 6 个节点组成的新 zk 集群,其中 3 个旧节点,3 个新节点。
5)对外发布新节点 4,5,6 组成的连接串(即业务方将使用这个串进行新集群交互)
这一步很重要,它可以让业务方通过新地址串连接 zk 集群,方便我们后续下线旧节点。比如我们目前的集群有 6 个节点组成,但是我们对外发布 e.g. ip4:2181,ip5:2181,ip6:2181 的新节点连接串,这样业务方就不再感知旧节点,后续我们下线旧节点时就不会对业务方造成影响。
6)确认业务方替换完新连接串后(注意,因为新连接串只包含 4,5,6 节点,所以如果业务方替换完后,不会再有业务方机器连接旧节点):
A.先下线节点 1 (此时可用节点为 5 个,满足 6 个节点集群需要至少 4 个可用的要求),剩余两个可用的旧节点用来滚动替换,最终建成一个 3 个新节点的集群。
B.剩余 2,3,4,5,6 节点分别将节点 1 的配置从 zoo.cfg 中删除/注释,然后按先 follower,后 leader 的顺序重启各个节点,这样形成 5 个节点的 zk 集群。
C.对于 5 个节点的集群,可以容忍两个节点失效,因此,先下线旧节点 2,然后对于剩余的 3,4,5,6 节点分别将节点 2 的配置从 zoo.cfg 中删除/注释,然后按先 follower,后 leader 的顺序重启各个节点,这样形成 4 个节点的集群。
D.对于 4 个节点的集群,可以容忍一个节点失效,此时要注意不能直接下线节点 3,否则后续重启节点 4,5,6 时会造成集群不可用。因此对于剩余的 4,5,6 节点分别将节点 3 的配置从 zoo.cfg 中删除/注释,然后按先 follower,后 leader 的顺序重启各个节点,这样最终形成 3 个新节点的集群,此时下线最后一个旧节点 3。
7)done,此时最终形成节点 4,5,6 组成的新集群,数据跟旧集群一致。
注意事项
1.新增节点必须按照节点顺序依次启动,不能同时启动新增节点,关键因素就是先要保持原有的 leader 不变,不能因为新增节点导致重新选举。
这里我们要解释下为什么第一次启动节点 4 时配置列表中不能包含节点 6,假设我们包含节点 6,那么节点 4 看到集群包含 6 个节点,需要至少 4 个节点选择一致才能确定 leader 节点,而这时节点 1,2,3 会有一致选择,而节点 4,5,6 会有一致选择,都达不到确定 leader 节点的要求。
2.新增节点的 id 必须大于原有节点 id。这是因为 zookeeper 在两个节点之间建立连接时,只允许 id 较大的节点向 id 较小的节点发起连接,如果 id 较小的节点向 id 较大的节点发起连接,会被舍弃,如下图所示:
id较小的节点向id较大节点发起连接
假设新增节点 id 比原有节点 id 小,会出现什么情况呢?
答案就是会出现两个 leader,并且最后会导致数据不一致,如下图所示:
当前集群状态,出现两个leader
最后形成的leader为4,此时集群所存数据
而真实数据为原来 leader,节点 5 的数据为
/xiaojiang = 'born-in-beihai'
/cangcang = 'born-in-hangzhou'
造成这种状况的根本原因,在于重启原有 leader 节点前,现有集群节点没有全部处于 sync 状态。而新增较小的节点 id,使得在各个新增节点重启过程中出现较大 id 节点(原有节点)无法感知较小 id 节点加入集群的情况:
id较大节点无法感知较小id节点的加入
举个例子,原来有节点 4,5,6(leader 为 5),新增节点 1,2,3,那么启动完新增节点后,整个 zk 集群还是只有原来的 4,5,6 节点处于 sync 状态,此时节点 1,2,3 都处于寻找 leader 状态,并且都投票给节点 3(相同情况下,编号大的胜出)。那么当重启节点 4 后,节点 1,2,3,4 选出了节点 4 作为 leader(选票数超过 6 个节点的一半),而此时节点 5,6 仍然组成以 5 为 leader 的集群,这时两个集群都能提供服务,那么很明显数据将出现不一致。而后面继续重启节点 6 和节点 5 时,leader 仍为节点 4(仍然获得超过集群一半以上节点的支持),那么最后整个集群将以 leader(节点 4)的数据作为依据,因而先前在节点 5 作为 leader 期间变更的数据将丢失!!! 这无疑会是灾难性的后果。
3.在对外提供新集群连接串时,不要包含老节点地址。
业务方客户端在使用 zk 连接串时,都会随机挑一个地址进行尝试,如果无法连接则尝试下一个地址。因此我们只提供新地址组成的连接串,可以方便我们后续判断是否所有业务方都已使用新串进行连接。
4.在进行迁移的第 6 步进行滚动下线的 D 步骤时,要注意:不能先下线节点 3 然后再依次进行重启新节点。这是因为对于 4 个节点集群只能容忍一个节点失效,如果先下线节点 3,那么后续重启任何一个节点都将使得 zk 集群在重启期间不可用。
漏洞修复
方案一、绑定指定IP访问
登陆zookeeper
./zkCli.sh -server <IP>:<port>
#查看当前权限
getAcl /
#添加可访问IP
setAcl / ip:192.168.1.xx:cdrwa,ip:192.168.1.xx:cdrwa
#查看是否正常添加
getAcl /
#未授权也可以连接,但是查看节点时会报错"KeeperErrorCode = NoAuth for /",localhost都不行,必须填可访问IP,才能访问。
[zk: localhost:2181(CONNECTED) 0] ls /
KeeperErrorCode = NoAuth for /
[zk: localhost:2181(CONNECTED) 1]
#回退
./zkCli.sh -server <IP>:<port>
#设置为所有人可访问:
setAcl / world:anyone:cdrwa
方案二、增加防火墙端口访问限制
Centos 6
#增加限制其他IP访问-注意顺序-限制在后
iptables -I INPUT -p tcp --dport 2181 -j DROP
#增加开放IP源-注意顺序-IP在前
iptables -I INPUT -s 10.0.0.1 -p tcp --dport 2181 -j ACCEPT
iptables -I INPUT -s 10.0.0.2 -p tcp --dport 2181 -j ACCEPT
iptables -I INPUT -s 10.0.0.3 -p tcp --dport 2181 -j ACCEPT
#保存
service iptables save
#重启
service iptables restart
#打印配置内容
iptables -L
#Centos 7
firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="192.0.2.181" port protocol="tcp" port="2181" accept"
firewall-cmd --zone=public --remove-port=2181/tcp --permanent
firewall-cmd --reload
firewall-cmd --list-all
方案三、设置身份验证
#增加一个认证用户
#addauth digest 用户名:密码明文
addauth digest user_name:pass_word
#设置权限
#setAcl /path auth:用户名:密码明文:权限
#setAcl /path digest:用户名:密码密文:权限
setAcl /test auth:user_name:pass_word:cdrwa
#查看Acl设置
getAcl /path