集群中的全部机器应该都使用root用户登录 准备jdk,版本不得低于1.6,推荐使用1.8(使用人数最多,稳定) 集群机器数量应该尽量准备奇数台
Zookeeper是Apache的一个项目是一个分布式的协调服务框架,Zookeeper可以解决分布式环境常见的问题: 如:统一命名服务,信息配置管理,数据一致性,集群管理,分布式锁等等。
将不同的系统甚至功能部署到不同的机器上,再利用多台机器并行执行同一个任务,每台机器上都有独立的程序模块及要处理的数据,即人多干活快的思想。但是,由于由原来的单一节点运算模式转变为分布式运算,所以,代码的编写、数据资源的分配策略都发生了改变,并且也引发了很多新的问题
1.容易出现死锁 2.容易活锁,处于活锁的线程都是非阻塞的,而且每个线程都抢到资源,会造成cpu的耗费。线程在执行过程中产生了碰撞——再执行——再碰撞,如此循环往复,形成活锁。 3.集群的管理问题,比如某台服务器宕机需要能够检测到 4.集群配置文件的统一管理问题 5.集群中信息状态的更新通知问题 6.管理集群的选举问题 7.分布式锁的实现,这需要用新的机制和技术来实现
zookeeper旨在在分布式应用中,提供可靠的、可扩展的、分布式的、可配置的协调机制来管理整个集群的状态
注意:在安装zookeeper之前应该先安装jdk
配置java环境,这个教程应该能帮你:https://www.runoob.com/java/java-environment-setup.html
将下载好的zookeeper压缩包,上传到linux
解压zookeeper
tar -xvf 压缩包
进入zookeeper目录下的conf目录,有一个zoosample.cfg的文件
复制一份,并重命名为zoo.cfg文件,这个名字固定写死,因为zookeeper启动会检查这个文件,根据这个配置文件里的信息来启动服务
默认我们已经在zookeeper目录,如果不知道你所在的目录可以使用pwd命令查看 cd conf mv zoosample.cfg zoo.cfg
使用vim打开zoo.cfg文件
vim zoo.cfg
将datadir的值修改,因为默认etc目录是存放临时文件的
dataDir=/home/software/zookeeper-3.4.5/data 目录自己准备
保存后进入zookeeper的bin目录
cd bin
启动zookeeper
./zkServer.sh start
连接zookeeper客户端
./zkCli.sh
查看zookeeper是否启动成功
jps
准备至少三台有java环境的机器
推荐准备奇数数量的机器
安装步骤和单机版一致,参考单机版安装
修改配置文件
vim zoo.cfg
加上如下配置
进行这一步之前请确保dataDir的值不在etc目录下
在配置文件里,需要在加上如下的配置: 有几个节点就配置几个节点 sever.后面的值一定要和myid下的值一样(后面会讲怎么设置myid的值) server.1=192.168.234.10:2888:3888 server.2=192.168.234.11:2888:3888 server.3=192.168.234.12:2888:3888
保存退出
说明:2888为原子广播端口,3888为选举端口
当然你可以自定义成其他端口,只要端口不被占用都行
zookeeper有几个节点,就配置几个server,
说明:为什么dataDir的值不能是etc目录
dataDir。这个参数是存放zookeeper集群环境配置信息的。这个参数默然是配置在 /tmp/zookeeper下的 。但是注意,tmp是一个临时文件夹,这个是linux自带的一个目录,是linux本身用于存放临时文件用的目录。但是这个目录极有可能被清空,所以,重要的文件一定不要存在这个目录下*
将集群的其他配置文件也做出修改
可以使用scp命令来远程拉取文件
scp -r 目录 远程ip地址:存放的路径
配置文件配置好以后需要在dataDir目录下创建myid
注意是配置文件里的dataDir指向的那个目录,并不是dataDir目录
vim myid 值就是配置文件里server.后面的值 如server.1=192.168.234.10:2888:3888 值就是1
保存并退出
将集群里的所有机器都配置myid
关闭防火墙
service iptables stop
启动zookeeper,
进入zookeeper的bin目录 ./zkServer.sh start
集群的机器都启动,然后查看zookeeper的信息
./zkServer.sh status
注意一定要等集群里所有的zookeeper都启动完成在查看zookeeper信息,否则会报错
这一步是为了集群之间能互相通信
打开host配置文件
vim etc/hosts
在其中添加所有服务器或虚拟机节点ip和对应的域名,如下所示:
因为我是为了搭建hadoop集群所以给机器取名hadoop
192.168.25.101 机器的名字 192.168.25.102 hadoop02 192.168.25.103 hadoop03
保存退出
然后给每台机器设置hostname,刚刚在配置文件里写的什么名字现在就设置什么名字,名字要和ip对应
如配置文件里是:192.168.25.102 hadoop02
那么192.168.25.102 这台机器的hostnam设置
hostname hadoop02
其他机器以此类推
创建密钥:
ssh-keygen 连续回车即可 注意在执行该命令之前应该检查是不是在root家目录
复制公钥到其他节点
ssh-copy-id -i .ssh/id_rsa.pub root@192.168.135.102 #复制密钥 ssh-copy-id -i .ssh/id_rsa.pub root@192.168.135.103 #复制密钥 别忘了自己也要配置 ssh-copy-id -i .ssh/id_rsa.pub root@192.168.135.101 #复制密钥
其他机器也要按照这个步骤完成配置
配置完成之后试试免密登录其他机器
ssh ip
知识点1:zk有一个 根节点(/),所有节点的注册和操作基于 根节点来实现的
知识点2:每个节点都可以拥有自己的子节点
知识点3:每个节点都称为 znode节点
知识点4:多个znode节点共同形成了一个znode树,维系在内存中,供用户快速查询数据
知识点5:每个znode节点都可以存储数据
知识点6:创建节点时,要为此节点分配初始数据
知识点7:znode节点分4类,①普通持久节点(create) ②普通临时节点(create -e)
③顺序持久节点(create -s) ④临时顺序(create -e -s )
知识点8:znode节点路径是唯一的。根据这个特性,可以实现统一命名服务(要求命名的唯一性)
知识点9:不要用zookeeper存储大量数据,从功能来讲,zookeeper做的集群的协调服务,所以存储的信息是很少量的。此外,因为znode树是维系在内存中,海量数据也会占用大量的内存。
指令 | 说明 |
---|---|
sh zkServer.sh start | 启动zk服务的 |
sh zkServer.sh stop | 停止zk服务 |
sh zkServer.sh restart | 重启zk服务 |
sh zkServer.sh status | 查看zk服务角色,有: Standalone Leader Follower Observer |
sh zkCli.sh | 进入zk客户端 |
指令 | 说明 | 示例 |
---|---|---|
ls | 查看 | ls / 查看根路径 ls /park01 查看park01路径 |
create | 创建 | create /park02 “” create /park02/node01 创建park02的子节点 |
get | 获取指定节点信息 | cZxid = 0x2 #创建此节点的事务id ctime = Wed Jan 17 10:55:25 PST 2018 #创建此节点的时间戳 mZxid = 0x2 #修改此节点的事务id mtime = Wed Jan 17 10:55:25 PST 2018 #修改此节点的时间戳 pZxid = 0x4 cversion = 1 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 #如果此节点不是临时节点,则为0 dataLength = 9 #数据长度 numChildren = 1 #子节点数量 |
set | 更新节点数据 | set /park01 hellozk |
delete | 删除子节点为空的节点 | delete /park01 |
rmr | 递归删除指定节点 | rmr /park02 |
quit (或ctrl+c) | 退出客户端 | quit |
create -e | 创建临时节点,当创建此节点的客户端下线时,节点被删除。 | cetete -e /park02 “hello” |
create -s | 创建顺序节点,每次创建节点时,会跟上一个递增的顺序号 | cetete -s /park02 “hello” |
create -e -s | 创建临时顺序节点 | cetete -e -s /park02 “hello” |
依赖
<dependencies>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
@Test
public void testCreate() throws Exception{
//初始化递减锁
CountDownLatch countDownLatch = new CountDownLatch(1);
//创建zookeeper对象
ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
//第一个参数,连接的zookeeper节点ip,
//第二个参数,会话超时时间,以毫秒为单位。比如设置1000*30 是30秒。如果30秒内zookeeper没有收到客户端节点的心跳,则断开连接
//第三个参数,Watcher,观察者,当zookeeper里有事件发生时,会通知到这个process方法里
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
countDownLatch.countDown();//递减锁的初始值减一
}
}
});
//等待
countDownLatch.await();
zk.create("/hellozookeeper","hellozook".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
重要参数
//1.path 节点路径; //2.data[] 节点数据; //3.acl 权限策略,一般设为所用用户都可以操作这个节点,并且具有所有权限,所以选:Ids.OPEN_ACL_UNSAFE; //4.createMode 创建模式: //PERSISTENT 持久 //PERSISTENT_SEQUENTITAL 持久顺序 //EPHEMERAL 临时 //EPHEMERAL_SEQUENTITAL 临时顺序
@Test
public void testSet() throws Exception{
// 初始化递减锁
final CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if(watchedEvent.getState()== Event.KeeperState.SyncConnected){
cdl.countDown();
}
}
});
cdl.await();
//-1为版本号
zk.setData("/hellozookeeper","aaa".getBytes(),-1);
}
注意://version版本号的作用:在指定路径下,有一个dataVersion,这个是数据更改的版本号,从0开始,在此节点下
//每更改一次数据,版本号递增1
//而setData(, ,version)的version的意思是:指定一个版本号,基于这个版本号来修改,但是需要注意:
//如果指定的版本号是3,而当前的dataVersion是4,即指定的版本号不存在(已过时),则会报错
//如果指定的版本号是-1,则说明无论当前dataVersion是多少,都会进行数据的更新,最常用的就是-1
@Test
public void testGetData() throws Exception{
final CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
cdl.countDown();//递减锁减一
}
});
cdl.await();
//watch,当指定节点发生监听的事件时,会触发Watcher里的方法,
//监听的事件有:1.节点数据发生变化 2.节点创建 3.节点删除 4.节点的子节点发生变化。但是监听的事件只会触发一次回调方法。
//stat,节点环境信息,一般设置为null即可。如果想要,可以传入一个空的Stat对象来接收
byte[] data=zk.getData("/hellozookeeper", new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType()== Event.EventType.NodeChildrenChanged){
System.out.println("节点数据发生变化");
}
}
},null);
System.out.println(new String(data));
Stat s = new Stat();
List<ACL> list = zk.getACL("/hellozookeeper",s);
for (ACL acl : list) {
System.out.println(acl);
}
System.out.println(s);
while (true){}
}
@Test
public void testGetChildren() throws Exception{
final CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState()== Event.KeeperState.SyncConnected){
cdl.countDown();
}
}
});
cdl.await();
List<String> list = zk.getChildren("/",null);
for (String s : list) {
System.out.println(s);
}
}
@Test
public void testDelete() throws Exception{
CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState()== Event.KeeperState.SyncConnected){
cdl.countDown();//递减锁减一
}
}
});
cdl.await();
//删除节点
zk.delete("/hello",-1);
System.out.println("删除成功");
}
@Test
public void testNotNode() throws Exception{
CountDownLatch cdl = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("192.168.19.5:2181", 3000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState()== Event.KeeperState.SyncConnected){
cdl.countDown();//递减锁减一
}
}
});
cdl.await();
Stat stat = zk.exists("/hell",null);
System.out.println(stat);
System.out.println("111:"+stat);
}
zookeeper选举分两个阶段
每台zk服务在启动时,从本地目录找自己所拥有的Zxid(最大事务id),每有一次写操作,都是一个事务,每次事务都会递增。事务id越大,事务越新
每台zk服务器都会提交一个选举协议,协议中的内容:
①自己的zxid
②选举id(myid文件里的数字)
③逻辑时钟值(和选举轮数有关),作用是确保每台zk服务处于同一论选举中
④状态—Looking(选举) | Leader | Follower | Observer
先比较Zxid,谁大谁当Leader
如果Zxid比较不出来,
再比较选举id,谁大谁当领导
注意:选举最基本的原则是满足过半性。
过半存活,比如3台服务器,挂1个可以,挂两个则集群不能工作
zk集群数量最好是奇数个,能够更好的满足过半性。(偶数也可以)
Leader选举之后
首先要做的是数据同步,目的是确保zk集群的数据一致性。一是可以保证当Leader挂掉之后,其他follower可以顶替工作。此外要确保客户端无论从哪个zk服务器获取数据都是一致的。这种实现数据一致的过程称为原子广播(Atomic Brodcast)
对于客户端的读请求,任何一个zk服务器都可以处理,但是对于写请求,会交给Leader来处理,Leader会通过原子广播端口,请写请求(事务)广播给其他的节点,还会收集每个台服务器的ack信息,然后统计是否满足过半性,如果满足,则此事务成功提交。最后反馈客户端一个确认信息。
观察者不参加投票选举,他只监听投票选举的结果 观察者和追随者一样转发这些请求到领导者,他们和追随者的区别就是只监听投票结果而步参加投票 应该在步影响投票的情况下尽可能多的设置管擦者的数量
使用观察者设置Zookeeper全员非常简单,只需要在原来的配置文件上改两个地方。
第一,在要设置的那个节点的配置文件设置为观察者,必须放置这一行: peerType=observer 这一行告诉Zookeeper的服务是一个观察者。 第二,在每个服务配置文件里,必须在观察者定义行添加:observer。例如: server.1:localhost:2181:3181:observer 这个告诉其他服务server.1是一个观察者,并且他们不需要期望他选举。
参数名 | 说明 |
---|---|
clientPort | 客户端连接server的端口,即对外服务端口,一般设置为2181吧。 |
dataDir | 存储快照文件snapshot的目录。默认情况下,事务日志也会存储在这里。 ZK会在特定条件下会触发一次快照(snapshot),将当前服务节点的状态以快照文件的形式dump到磁盘上去,即snapshot文件。此外,每生成一次快照文件,就会生成一个对应的事务日志文件 快照数据文件名为:snapshot.x,而事务日志文件对应为:log.x+1。 其中,x是生成快照时的Zxid。 |
dataLogDir | 事务日志输出目录。 正常运行过程中,针对所有事务操作,在返回客户端“事务成功”的响应前,ZK会确保已经将本次事务操作的事务日志写到磁盘上,只有这样,事务才会生效。 |
tickTime | ZK中的一个时间单元。ZK中所有时间都是以这个时间单元为基础,进行整数倍配置的。例如,session的最小超时时间是2*tickTime。 |
initLimit | Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader允许F在 initLimit 时间内完成这个工作。通常情况下,我们不用太在意这个参数的设置。如果ZK集群的数据量确实很大了,F在启动的时候,从Leader上同步数据的时间也会相应变长,因此在这种情况下,有必要适当调大这个参数了。 默认是:****10*ticktime |
syncLimit | 在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。如果L发出心跳包在syncLimit之后,还没有从F那里收到响应,那么就认为这个F已经不在线了。 默认是:****5*ticktime |
minSessionTimeout maxSessionTimeout | Session超时时间限制,如果客户端设置的超时时间不在这个范围,那么会被强制设置为最大或最小时间。默认的Session超时时间是在2 * tickTime ~ 20 * tickTime 这个范围 |
snapCount | 每进行snapCount次事务日志输出后,触发一次快照(snapshot), 此时,ZK会生成一个snapshot.文件,同时创建一个新的事务日志文件log.。默认是100000。这是一种情况 此外,在产生新Leader时,也会生成新的快照文件,(同时会生成对应的事务文件) |
autopurge.purgeInterval | 3.4.0及之后版本,ZK提供了自动清理事务日志和快照文件的功能,这个参数指定了清理频率,单位是小时,需要配置一个1或更大的整数,默认是0,表示不开启自动清理功能。 |
server.x=[hostname]:nnnnn[:nnnnn] | 这里的x是一个数字,与myid文件中的id是一致的。右边可以配置两个端口,第一个端口用于F和L之间的数据同步和其它通信,第二个端口用于Leader选举过程中投票通信。 |
jute.maxbuffer | 每个节点最大数据量,是默认是1M。 |
globalOutstandingLimit | 最大请求堆积数。默认是1000。ZK运行的时候, 尽管server已经没有空闲来处理更多的客户端请求了,但是还是允许客户端将请求提交到服务器上来,以提高吞吐性能。当然,为了防止Server内存溢出,这个请求堆积数还是需要限制下的。 |
preAllocSize | 预先开辟磁盘空间,用于后续写入事务日志。默认是64M,每个事务日志大小就是64M。 |
electionAlg | 默认为3,即 fast paxos election 选举算法。在3.4版本后,1 2对应的选举算已弃用,所以此项配置不要更改。 |
leaderServes | 默认情况下,Leader是会接受客户端连接,并提供正常的读写服务。但是,如果你想让Leader专注于集群中机器的协调,那么可以将这个参数设置为no,这样一来,会提高整个zk集群性能。 |
数据一致性
client不论连接到哪个Zookeeper,展示给它都是同一个视图,即查询的数据都是一样的。这是zookeeper最重要的性能。
对于事务决议的更新,只能是成功或者失败两种可能,没有中间状态。要么都更新成功,要么都不更新。即,要么整个集群中所有机器都成功应用了某一事务,要么都没有应用,一定不会出现集群中部分机器应用了改事务,另外一部分没有应用的情况。
一旦服务端成功的应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会一直保留下来,除非有另一个事务又对其进行了改变。
Zookeeper保证客户端将在非常短的时间间隔范围内获得服务器的更新信息,或者服务器失效的信息,或者指定监听事件的变化信息。(前提条件是:网络状况良好)
如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布。
过半性
zookeeper集群必须有半数以上的机器存活才能正常工作。因为只有满足过半数,才能满足选举机制选出Leader。因为只有过半,在做事务决议时,事务才能更新。
所以一般来说,zookeeper集群的数量最好是奇数个。
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住, 就像数据库中产生一个唯一的数字主键一样。我们利用Zookeeper 可以轻松实现这一功能——利用znode路径。因为znode路径是全局唯一的(可以用某个路径来代表服务名)。
通过zookeeper知道集群里机器的状态,实现思路:集群里每台机器都在zookeeper里注册自己的临时节点,并上传自己的运行状态,我们可以查看这些临时节点,来得知节点的数据信息,加入某个临时节点消失了,意味着这台节点挂掉了,从而也可以达到集群监控的目的。
这个最典型的就是集群配置信息要发布到集群的客户机节点上,实现配置信息的集中式管理和动态更新。
配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多台 PC Server 运行,但是它们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样非常麻烦而且容易出错。像这样的配置信息完全可以交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中。
可以联想赛马的案例,在分布式环境下,这种效果称为屏障。
实现思路:每台机器可以将自己的状态信息注册zk服务上(比如cpu,带宽,磁盘等使用率)zk可以根据这些信息做负载均衡。
可以通过顺序节点来实现。