一、背景
1 后台系统由集中式发展为分布式
随着计算机系统的规模越来越大,业务量的迅速提升和互联网的爆炸式增长,集中式系统采用大型主机单机部署带来了一系列问题:系统大而复杂、难于维护、发生单点故障引起雪崩、扩展性差等。这些都使业务面临巨大的压力和严重的风险,为了解决集中式系统架构面临的痛点,分布式系统架构逐步走上舞台。分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统,可以很好的解决系统扩容、可用性以及降低成本。
2 分布式系统架构引入的新问题
“天下没有免费的午餐”,分布式系统架构带来了优点的同时,也提出了一系列的挑战:
(1)由于多节点甚至多地部署,节点之间的数据一致性如何保证?
(2)在并发场景下如何保证任务只被执行一次?
(3)一个节点挂掉不能提供服务时如何被集群知晓并由其他节点接替任务?
(4)存在资源共享时,资源的安全性和互斥性如何保证?
以上列举了分布式系统中面临的一些挑战,需要一个协调机制来解决分布式集群中的问题,使得开发者更专注于应用本身的逻辑而不是关注分布式系统处理。
3 分布式协调组件
为解决分布式系统中面临的这些问题,开发者们通过工程实践创造了很多非常优秀的分布式系统协调组件,这些组件可以在分布式环境下,保证分布式系统的数据一致性和容错性等。其中为我们熟知的有:ZooKeeper、ETCD、Consul 等。ZooKeeper 作为 Apache 的顶级开源项目,基于 Google Chubby 开源实现,在 Hadoop,Hbase,Kafka 等技术中充当核心组件的角色。虽然历史悠久,但就像陈酿一样,其设计思想和实现不论何时还是值得仔细学习和品味。
二、ZooKeeper
1 ZooKeeper 是什么
从理论概念角度解释:ZooKeeper 是一个分布式的,开源的分布式应用程序协调服务,它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
从数据读写角度解释:ZooKeeper 是一个分布式的开源协调服务,用于分布式系统。ZooKeeper 允许你读取、写入数据和发现数据更新。数据按层次结构组织在文件系统中,并复制到 ensemble(一个 ZooKeeper 服务器的集合)中所有的 ZooKeeper 服务器。对数据的所有操作都是原子的和顺序一致的。ZooKeeper 通过 Zab 一致性协议在 ensemble 的所有服务器之间复制一个状态机来确保这个特性。
2 ZooKeeper 的安装与使用
“纸上得来终觉浅,绝知此事要躬行”,学习一个新的组件,我们先通过安装使用,对配置、API 等有一个直观的认识,也为后面动手实现一些功能部署好开发环境基础。
2.1 ZooKeeper 下载与安装
(1)ZooKeeper 使用 JAVA 语言开发,使用前需要先安装 JDK(读者自行安装),安装 JDK 后可在终端命令行中使用 java -version 命令查看版本(注意:本文均在 Linux 环境下指导演示)。
(2)ZooKeeper 下载:https://zookeeper.apache.org/releases.html
在下载页面分为最新的 Release 版本和最近的稳定 Release 版本,这里生产环境使用推荐稳定版本,点击下载并上传 apache-zookeeper-3.7.0-bin.tar.gz 到 Linux 服务器上。
(3)ZooKeeper 安装:ZooKeeper 安装分为集群安装和单机安装,生产环境一般为集群安装。此处作为演示,使用一台服务器来做模拟集群,也称伪集群安装(通过三个不同的文件夹 zk1/zk2/zk3,模拟真实环境中的三台服务器实例)。
-
本篇中我们将要在本地开发机上安装三个 zk 实例(可以认为在生产集群模式中,这是三台不同的服务器),其安装位置分别如下:
/Users/newboy/ZooKeeper/zk1 /Users/newboy/ZooKeeper/zk2 /Users/newboy/ZooKeeper/zk3
-
将上文中下载的 ZooKeeper 安装包 apache-zookeeper-3.7.0-bin.tar.gz 上传到第一个实例 zk1 文件夹下,并使用如下命令进行解压:
tar -xzvf apache-zookeeper-3.7.0-bin.tar.gz
-
解压完成后在 zk1 文件夹下创建 data 和 log 目录,分别用于存储当前 zk 实例数据和日志:
mkdir data logs
此时 zk1 文件夹目录结构如下所示:
-
创建 myid 文件 在 zk1 的 data 目录下,创建 myid 文件,此文件记录节点 id,每个 zookeeper 节点都需要一个 myid 文件来记录节点在集群中的 id,此文件中只能有一个数字,这里 zk1 实例 myid 中写入一个 1 即可:
echo "1" >> /Users/newboy/ZooKeeper/zk1/data/myid // 实例zk1的myid赋值为1 echo "2" >> /Users/newboy/ZooKeeper/zk2/data/myid // 实例zk2的myid赋值为2 echo "3" >> /Users/newboy/ZooKeeper/zk3/data/myid // 实例zk3的myid赋值为3
-
进入 zk1 文件夹下 apache-zookeeper-3.7.0-bin/conf/目录,将配置文件 zoo_sample.cfg 重命名为 zoo.cfg,打开 zoo.cfg 进行配置,具体配置如下:
tickTime=2000 # 单位时间,其他时间都是以这个倍数来表示 initLimit=10 # 节点初始化时间,10倍单位时间(即十倍tickTime) syncLimit=5 # 心跳最大延迟周期 dataDir=/Users/newboy/ZooKeeper/zk1/data # 该实例对应的数据目录(上文步骤3创建) dataLogDir=/Users/newboy/ZooKeeper/zk1/logs # 该实例对应的日志目录(上文步骤3创建) clientPort=2181 # 端口(每个实例不同) // 集群配置 server.1=127.0.0.1:8881:7771 # server.id=host:port:port server.2=127.0.0.1:8882:7772 # server.id=host:port:port server.3=127.0.0.1:8883:7773 # server.id=host:port:port
集群配置中模版为 server.id=host:port:port,id 是上面 myid 文件中配置的 id;ip 是节点的 ip,第一个 port 是节点之间通信的端口,第二个 port 用于选举 leader 节点(在真正的集群模式下,不同服务器可以共用同一个 port,这里单机上演示为了避免端口冲突,选择不同的端口)。
-
zk2 和 zk3 的实例配置与 zk1 类似,为了方便我们可以直接拷贝 zk1 的配置到 zk2 和 zk3 文件夹,然后修改各自的 zoo.cfg 和 data 目录下的 myid 即可。拷贝命令:
cp -R zk1 zk2 cp -R zk1 zk3
zk2 对应的 zoo.cfg:
tickTime=2000 # 单位时间,其他时间都是以这个倍数来表示 initLimit=10 # 节点初始化时间,10倍单位时间(即十倍tickTime) syncLimit=5 # 心跳最大延迟周期 dataDir=/Users/newboy/ZooKeeper/zk2/data # 该实例对应的数据目录(上文步骤3创建) dataLogDir=/Users/newboy/ZooKeeper/zk2/logs # 该实例对应的日志目录(上文步骤3创建) clientPort=2182 # 端口(每个实例不同) // 集群配置 server.1=127.0.0.1:8881:7771 # server.id=host:port:port server.2=127.0.0.1:8882:7772 # server.id=host:port:port server.3=127.0.0.1:8883:7773 # server.id=host:port:port
zk3 对应的 zoo.cfg: