Zookeeper是什么
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
1. 数据结构
Zookeeper维护一个类似文件系统的数据结构:
每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。
有四种类型的znode:
- PERSISTENT-持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在
- PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
- EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后,该节点被删除
- EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
2. 监听通知机制
客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。
Zookeeper可以做什么
Zookeeper可以用于:
-
服务注册和发现
-
配置管理
-
分布式锁
-
集群管理
…
Zookeeper的安装
单机版安装
1)安装java
2)下载、解压zookeeper
3)修改zoo_sample.conf 为zoo.conf
4)启动服务
./zkServer.sh start
其它:
./zkServer.sh stop
./zkServer.sh restart
./zkServer.sh status
5)启动客户端
./zkCli.sh
客户端命令:
查看目录 ls /
创建节点 create /name value
查看节点 get /name
修改 set /name value
删除 delete /name
退出 quit
集群安装
Step1:配置JAVA环境,检验环境:java -version
Step2:下载并解压zookeeper
- # cd /usr/local
- # wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.12.tar.gz
- # tar -zxvf zookeeper-3.4.12.tar.gz
- # cd zookeeper-3.4.12
Step3:重命名 zoo_sample.cfg文件
# cp conf/zoo_sample.cfg conf/zoo-1.cfg
Step4:修改配置文件zoo-1.cfg,原配置文件里有的,修改成下面的值,没有的则加上
1. # vim conf/zoo-1.cfg
2. dataDir=/tmp/zookeeper-1
3. clientPort=2181
4. server.1=127.0.0.1:2888:3888
5. server.2=127.0.0.1:2889:3889
6. server.3=127.0.0.1:2890:3890
Step4:再从zoo-1.cfg复制两个配置文件zoo-2.cfg和zoo-3.cfg,只需修改dataDir和clientPort不同即可
1. # cp conf/zoo-1.cfg conf/zoo-2.cfg
2. # cp conf/zoo-1.cfg conf/zoo-3.cfg
3. # vim conf/zoo-2.cfg
4. dataDir=/tmp/zookeeper-2
5. clientPort=2182
6. # vim conf/zoo-2.cfg
7. dataDir=/tmp/zookeeper-3
8. clientPort=2183
Step5:标识Server ID
创建三个文件夹/tmp/zookeeper-1,/tmp/zookeeper-2,/tmp/zookeeper-2,在每个目录中创建文件myid 文件,写入当前实例的server id,即1.2.3
1. \# cd /tmp/zookeeper-1
2. \# vim myid
3. 1
4. \# cd /tmp/zookeeper-2
5. \# vim myid
6. 2
7. \# cd /tmp/zookeeper-3
8. \# vim myid
9. 3
Step6:启动三个zookeeper实例
1. \# bin/zkServer.sh start conf/zoo-1.cfg
2. \# bin/zkServer.sh start conf/zoo-2.cfg
3. \# bin/zkServer.sh start conf/zoo-3.cfg
Step7:检测集群状态,也可以直接用命令“zkCli.sh -server IP:PORT”连接
Zookeeper面试题
问题1:为什么最好使用奇数台服务器构成 ZooKeeper 集群?
我们知道在 ZooKeeper 中 Leader 选举算法采用了 Zab 协议。Zab 核心思想是当多数 Server 写成功,则任务数据写成功:
- 如果有 3 个 Server,则最多允许 1 个 Server 挂掉。
- 如果有 4 个 Server,则同样最多允许 1 个 Server 挂掉。
既然 3 个或者 4 个 Server,同样最多允许 1 个 Server 挂掉,那么它们的可靠性是一样的。
问题2:监听原理
1)首先要有一个main()线程
2)在main线程中创建Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connet),一个负责监听(listener)。
3)通过connect线程将注册的监听事件发送给Zookeeper。
4)在Zookeeper的注册监听器列表中将注册的监听事件添加到列表中。
5)Zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程。
6)listener线程内部调用了process()方法。
2、常见的监听
1)监听节点数据的变化
get path [watch]
2)监听子节点增减的变化
ls path [watch]
问题3:Leader选举机制
1)半数机制:集群中半数以上机器存活,集群可用
2)Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的,Leader是处理事务的唯一节点,follower收到事务请求也会将事情请求转发给leader,由leader处理协调zookeeper中的其他节点。
3)以一个简单的例子来说明整个选举的过程。
假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么,如图5-8所示。
(1)服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING;
(2)服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的ID比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING
(3)服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;
(4)服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING;
(5)服务器5启动,同4一样当小弟。
Zookeeper的基本使用
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
public class ZkDemo implements Watcher {
private static CountDownLatch countDownLatch = new CountDownLatch(1);
private static ZooKeeper zooKeeper = null;
private static Stat stat = new Stat();
public static void main(String[] args) throws Exception {
String path = "/xray";
zooKeeper = new ZooKeeper("192.168.31.165:2181",5000,new ZkDemo());
countDownLatch.await();
byte[] data = zooKeeper.getData(path, true, stat);
System.out.println(new String(data));
Thread.sleep(Integer.MAX_VALUE);
}
@Override
public void process(WatchedEvent watchedEvent) {
if(Event.KeeperState.SyncConnected == watchedEvent.getState()){
if(Event.EventType.None == watchedEvent.getType() && null == watchedEvent.getPath()){
countDownLatch.countDown();
}else if(Event.EventType.NodeDataChanged == watchedEvent.getType()){
try {
System.out.println("发生修改:" + new String(zooKeeper.getData(watchedEvent.getPath(),true,stat)));
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Dubbo是什么
Dubbo是一款高性能、轻量级的开源Java RPC框架
它提供了三大核心能力:
-
透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。
-
软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。
-
服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
Dubbo的架构
- Provider: 暴露服务的服务提供方。
- Consumer: 调用远程服务的服务消费方。
- Registry: 服务注册与发现的注册中心。
- Monitor: 统计服务的调用次调和调用时间的监控中心。
- Container: 服务运行容器。
Zookeeper和Dubbo的入门案例
- 安装和启动Zookeeper
- 父项目依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
-
新建通用项目保存接口
package com.hp.service; public interface HelloService { String hello(String name); }
-
服务提供者
配置
server.port=6606
spring.application.name=provider-service
#Dubbo
dubbo.application.name=provider-service
dubbo.registry.protocol=zookeeper
dubbo.registry.address=zookeeper://192.168.31.165:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#dubbo扫描接口的包
dubbo.scan.base-packages=com.hp.provider_service.service
接口实现
package com.hp.provider_service.service.impl;
import com.hp.service.HelloService;
import org.apache.dubbo.config.annotation.Service;
@Service(version = "1.0.0",interfaceClass = HelloService.class)
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return "Hello!!" + name;
}
}
- 服务消费者
配置
server.port=7707
dubbo.application.name=consumer-service
dubbo.registry.protocol=zookeeper
dubbo.registry.address=zookeeper://192.168.31.165:2181
调用提供者接口
@RestController
public class HelloController {
@Reference(version = "1.0.0")
private HelloService helloService;
@RequestMapping("/hello")
public String hello(String name){
return helloService.hello(name);
}
}
stry.protocol=zookeeper
dubbo.registry.address=zookeeper://192.168.31.165:2181
调用提供者接口
@RestController
public class HelloController {
@Reference(version = "1.0.0")
private HelloService helloService;
@RequestMapping("/hello")
public String hello(String name){
return helloService.hello(name);
}
}