zookeeper是 Apache Hadoop 项目下的一个子项目,是一个树形目录服务。
Zookeeper翻译过来就是动物园管理员,他是用来管Hadoop (大象)、Hive(蜜蜂)、Pig(小猪)的管理员。简称zk
Zookeeper是一个分布式的、开源的分布式应用程序的协调服务。
Zookeeper 提供的主要功能包括:
例如有ABC三个业务程序,每个业务服务中都有一些配置文件的信息(数据库的配置信息),如果配置信息有改动,ABC都需要去改动配置信息要重新上线,因此可引入一个配置中心,这样ABC中的业务服务都和配置中心连接起来,将共有的配置放到配置信息中进行实现
例如A需要访问一些数据,但是A也有可能被很多人访问,但是为了保证数据的完整性和正确性,我们期望A中的数据只能同时被一个人访问到,我们可以加锁来实现。但是在分布式中,如果有多个业务,部署在多个机器上同样的访问相同的数据,这样就需要引入第三方的分布式锁的概念,这样A去分布式锁中获取锁,即可访问数据,其他业务处于等待状态。直到A访问完成数据释放锁后其他业务才能进行访问
作为注册中心来使用,服务的提供方先将自己的地址给注册中心,消费在需要访问服务时就可在注册中心获取服务方的地址进行RPC的远程使用。
1、在Linux的根目录中有一个opt目录,在其中建立一个Zookeeper目录,将下载好的安装包放置在该目录中并进行解压(红线为解压前后分割线)
2、配置启动
进入conf目录拷贝一个zoo_sample.cfg为zoo.cfg并完成配置
3、修改zoo.cfg
#打开目录
cd /opt/zooKeeper/
#创建zooKeeper存储目录
mkdir zkdata
#修改zoo.cfg
vim /opt/zooKeeper/apache-zooKeeper-3.5.6-bin/conf/zoo.cfg
vim zoo.cfg配置修改存储目录:dataDir=/opt/zookeeper/zkdata
启动Zookeeper
cd /opt/zooKeeper/apache-zooKeeper-3.5.6-bin/bin/
#启动
./zkServer.sh start
查看Zookeeper状态./zkServer.sh status
,其启动默认端口为2181,其中Mode: standalone表示当前状态为单节点状态,没有搭建服务集群
ZoaKeeper是一个树形目录服务,其数据模型和Linux的文件系统目录树很类似,拥有一个层次化结构这里面的每一个节点都被称为: ZNode,每个节点上都会保存自己的数据和节点信息。
节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下。
节点可以分为四大类:
PERSISTENT持久化节点.
EPHEMERAL 临时节点:-e
PERSISTENT_SEQUENTIAL持久化顺序节点:-s
EPHEMERAL SEQUENTIAL 临时顺序节点: -es
启动Zookeeper服务./zkServer.sh start
查看Zookeeper服务状态./zkServer.sh status
停止Zookeeper服务./zkServer.sh stop
重启Zookeeper服务./zkServer.sh restart
在MobaXterm中点击sessions图标即可克隆一个新的会话窗口
连接Zookeeper服务端./zkCli.sh -server 192.168.52.128:2181
,连接成功可使用quit推出连接
断开连接quit
显示指定目录下节点ls /目录名称
设置节点值set /节点path value
查看命令帮助help
删除单个节点delete /节点path
删除带有节点的子节点deleteall /节点path
创建节点create /节点 path value
获取节点值get /节点path
以上创建都为持久化节点,下面介绍临时节点,当前会话断开时节点消除
创建临时节点create -e /节点path value
创建顺序节点create -s /节点path value
查询节点详细信息ls -s /节点path
存放了服务提供方的IP地址,地址在节点中存储,将来消费方通过这个providers服务名称,找到地址完成地址调用
Curator是Apache ZooKesper的Java客户端库。
常见的ZooKeeper Java APl :
原生Java APl
zkclient
Curator
Curator项目的目标是简化ZooKeeper客户端的使用。
Curator最初是Netfix,研发的,后来捐献了Apache基金会,目前是Apache的顶级项目。
官网: http://curator.apache.org
CuratorFrameworkFactory用来创建CuratorFramework的工厂对象
CuratorFramework这个类就是curator这个javaAPI客户端和Zookeeper Server建立连接的客户端对象
4.0.0
com.zg
curator-zk
1.0-SNAPSHOT
junit
junit
4.10
test
org.apache.curator
curator-framework
4.0.0
org.apache.curator
curator-recipes
4.0.0
org.slf4j
slf4j-api
1.7.21
org.slf4j
slf4j-log4j12
1.7.21
org.apache.maven.plugins
maven-compiler-plugin
3.1
1.8
1、第一种方式newClient
package com.zg.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.Test;
public class CuratorTest {
/*建立连接*/
@Test
public void testConnect(){
// String connectString, 连接字符串,zk server 地址和端口 192.168.52.128:2181
// 这里可以写多个用都好分割为了搭建集群使用
// int sessionTimeoutMs,会话超时时间,单位ms(会话建立起来后,在通信的过程中,多长时间没联通的时间)
// int connectionTimeoutMs,连接超时时间,单位ms(例如:3秒没连接上就不允许连接)
// RetryPolicy retryPolicy,重试策略
//重试策略,参数为休眠时间和重试次数
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);
//CuratorFramework这个类就是curator这个javaAPI客户端和Zookeeper Server建立连接的客户端对象
//CuratorFrameworkFactory用来创建CuratorFramework的工厂对象
//第一种方式newClient
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.52.128:2181", 60 * 1000, 15 * 1000, retryPolicy);
//开启连接
client.start();
}
}
2、第二种方式builder
package com.zg.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.Test;
public class CuratorTest {
/*建立连接*/
@Test
public void testConnect(){
// String connectString, 连接字符串,zk server 地址和端口 192.168.52.128:2181
// 这里可以写多个用都好分割为了搭建集群使用
// int sessionTimeoutMs,会话超时时间,单位ms(会话建立起来后,在通信的过程中,多长时间没联通的时间)
// int connectionTimeoutMs,连接超时时间,单位ms(例如:3秒没连接上就不允许连接)
// RetryPolicy retryPolicy,重试策略
//重试策略,参数为休眠时间和重试次数
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);
//CuratorFramework这个类就是curator这个javaAPI客户端和Zookeeper Server建立连接的客户端对象
//CuratorFrameworkFactory用来创建CuratorFramework的工厂对象
//CuratorFramework这个类就是curator这个javaAPI客户端和Zookeeper Server建立
//第二种方式builder
CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.52.128:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy).namespace("zgDaren").build();
client.start();
}
}
创建节点:create 持久 超时 顺序 数据
1、基本创建,如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储
@Test
public void testCreate() throws Exception {
//1、基本创建,如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储
String path = client.create().forPath("/app1");
System.out.println(path);
}
2、创建节点,带有数据,这里需要传入参数为byte数组,因此使用.getBytes()方法
@Test
public void testCreate2() throws Exception {
//2、创建节点,带有数据,这里需要传入参数为byte数组,因此使用.getBytes()方法
String path = client.create().forPath("/app2","haha".getBytes());
System.out.println(path);
}
3、设置节点的类型,默认类型为持久化节点
当在这里创建一次类型为临时的节点,在Linux的Zookeeper Client中是显示不到的
因为Zookeeper Java API和Zokeeper Client是两个不同的会话
@Test
public void testCreate3() throws Exception {
//3、设置节点的类型,默认类型为持久化节点
//当在这里创建一次类型为临时的节点,在Linux的Zookeeper Client中是显示不到的
//因为Zookeeper Java API和Zokeeper Client是两个不同的会话
String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");
System.out.println(path);
}
4、创建多级节点
创建各级节点 /app4/p1
父节点不在是不允许创建子节点的,但可以使用creatingParentsIfNeeded()来打破规则
@Test
public void testCreate4() throws Exception {
//4、创建各级节点 /app4/p1
//父节点不在是不允许创建子节点的,但可以使用creatingParentsIfNeeded()来打破规则
String path = client.create().creatingParentsIfNeeded().forPath("/app4/p1");
System.out.println(path);
}
1、修改节点数据
@Test
public void testSet() throws Exception {
client.setData().forPath("/app1", "Lzzzz_x".getBytes());
}
2、根据版本号进行修改,在stat中获取version,其目的是为了其他线程不干扰这次修改,要保证原子性操作
@Test
public void testSetForVersion() throws Exception {
//stat中有具体的信息
Stat stat = new Stat();
//先查找到版本信息,此时stat中已经有了version
client.getData().storingStatIn(stat).forPath("/app1");
int version = stat.getVersion();
System.out.println(version);
client.setData().withVersion(version).forPath("/app1","Alice".getBytes());
}
1、查询数据: client.getData().forPath()
@Test
public void testGet() throws Exception {
//1、查询数据:get
byte[] data = client.getData().forPath("/app1");
System.out.println(data);
}
2、查询子节点:getChildren().forPath(),如果指定了namespace则只能查找到zgDaren下的子节点
@Test
public void testGet2() throws Exception {
//2、查询子节点:ls
List path = client.getChildren().forPath("/app4");
System.out.println(path);
}
3、查询节点状态信息:getData().storingStatIn(状态对象stat).forPath()
@Test
public void testGet3() throws Exception {
//3、查询节点状态信息:ls - s
Stat status = new Stat();
System.out.println(status);
client.getData().storingStatIn(status).forPath("/app1");
System.out.println(status);
}
1、删除单个节点
@Test
public void testDelete() throws Exception {
//1、删除单个节点
client.delete().forPath("/app1");
}
2、删除带有子节点的节点
@Test
public void testDelete2() throws Exception {
//2、删除带有子节点的节点
client.delete().deletingChildrenIfNeeded().forPath("/app4");
}
3、必须成功的删除
如果说因为网络抖动,发送的删除操作超时了,仍然会删除数据
@Test
public void testDelete3() throws Exception {
//3、必须成功的删除
client.delete().guaranteed().forPath("/app2");
}
@Test
public void testDelete4() throws Exception {
//4、回调
client.delete().guaranteed().inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("app1被删除了");
System.out.println(curatorFramework);
System.out.println(curatorEvent);
}
}).forPath("/app1");
}
Zookeeper服务端提供了事件监听机制,其可以监听节点的变化情况(例如某个节点数据变了,就会触发监听器,监听器就会执行一些操作)
1、NodeCache:给指定一个节点注册监听器
@Test
public void testNodeCache() throws Exception {
//1、创建NodeCache对象
NodeCache nodeCache = new NodeCache(client,"/app1");
//2、注册监听
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("节点变化了");
//获取修改节点后的数据
byte[] data = nodeCache.getCurrentData().getData();
System.out.println(new String(data));
}
});
//3、开启监听,如果设置true,则开启监听器,加载缓冲数据
nodeCache.start(true);
while(true) {}
}
2、PathChildrenCache:监听某个节点的所有子节点们
/*PathChildrenCache:监听某个节点的所有子节点们*/
@Test
public void testPathChildrenCache() throws Exception {
//1、创建监听对象
PathChildrenCache pathChildrenCache = new PathChildrenCache(client,"/app2",true);
//2、绑定监听器
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
System.out.println("子节点变化了。。");
System.out.println(pathChildrenCacheEvent);
//监听子节点的数据变更,并且获取到变更的数据
//1、获取类型
PathChildrenCacheEvent.Type type = pathChildrenCacheEvent.getType();
//2、判断类型是否是update
if(type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
byte[] data = pathChildrenCacheEvent.getData().getData();
System.out.println(data);
}
}
});
//3、开启
pathChildrenCache.start();
while(true) {}
}
/*TreeCache:监听某个节点及其所有子节点们*/
@Test
public void testTreeCache() throws Exception {
//1、创建监听对象
TreeCache treeCache = new TreeCache(client, "/app2");
//2、注册监听
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
System.out.println("节点变化了");
}
});
//3、开启
treeCache.start();
while(true) {}
}
在我们进行单机应用开发,涉及并发同步的时候,我们往往采用synchronized或者Lock的方式来解决多线程间的代码同步问题,这时多线程的运行都是在同一个JVM之下,没有任何问题。
但当我们的应用是分布式集群工作的情况下,属于多JVM下的工作环境,跨JVM之间已经无法通过多线程的锁解决同步问题。那么就需要一种更加高级的锁机制,来处理种跨机器的进程之间的数据同步问题——这就是分布式锁.
核心思想:当客户端要获取锁,则创建节点,使用完锁,则删除该节点。
1、客户端获取锁时,在lock节点下创建临时顺序节点(如果客户端宕机会话断开就可以删除节点,释放锁)
2、然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
3、如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
4、如果发现比自己小的那个节点被删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。
在Curator中有五种锁方案:
InterProcessSemaphoreMutex:分布式排它锁(非可重入锁)
lnterProcessMutex:分布式可重入排它锁
lnterProcessReadWritelock:分布式读写锁
lnterProcessMultiLock:将多个锁作为单个实体管理的容器
lnterProcessSemaphoreV2:共享信号量
package com.zg.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class LockTest {
public static void main(String[] args) {
Ticket12306 ticket12306 = new Ticket12306();
//创建客户端
Thread t1 = new Thread(ticket12306,"携程");
Thread t2 = new Thread(ticket12306,"飞猪");
t1.start();
t2.start();
}
}
服务端
package com.zg.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.rules.Timeout;
import java.util.concurrent.TimeUnit;
public class Ticket12306 implements Runnable {
private int tickets = 10;
//定义分布式锁
private InterProcessMutex lock;
public Ticket12306() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.52.128:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy).build();
client.start();
lock = new InterProcessMutex(client, "/look");
}
@Override
public void run() {
while (true) {
//加锁(获取锁)
try {
lock.acquire(3, TimeUnit.SECONDS);
if (tickets > 0) {
System.out.println(Thread.currentThread() + ":" + tickets);
tickets--;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
Zookeeper作为组织者协调者,如果它是单节点一旦宕机整个服务就不可用了
真实的集群是需要部署在不同的服务器上的,但是在我们测试时同时启动很多个虚拟机内存会吃不消,所以我们通常会搭建伪集群,也就是把所有的服务都搭建在一台虚拟机上,用端口进行区分。
我们这里要求搭建一个三个节点的Zookeeper集群(伪集群)。
1、将zookeeper安装包上传至root目录下
2、解压zookeeper,建立/usr/local/zookeeper-cluster目录,将解压后的Zookeeper复制到以下三个目录
/usr/local/zookeeper-cluster/zookeeper-1
/usr/local/zookeeper-cluster/zookeeper-2
/usr/local/zookeeper-cluster/zookeeper-3
[root@localhost ~]# mkdir /usr/local/zookeeper-cluster
[root@localhost ~]# cp -r apache-zookeeper-3.5.6-bin /usr/local/zookeeper-cluster/zookeeper-1
[root@localhost ~]# cp -r apache-zookeeper-3.5.6-bin /usr/local/zookeeper-cluster/zookeeper-2
[root@localhost ~]# cp -r apache-zookeeper-3.5.6-bin /usr/local/zookeeper-cluster/zookeeper-3
3、创建data目录,并且将conf下zoo_sample.cfg文件命名为zoo.cfg
mkdir /usr/local/zookeeper-cluster/zookeeper-1/data
mkdir /usr/local/zookeeper-cluster/zookeeper-2/data
mkdir /usr/local/zookeeper-cluster/zookeeper-3/data
mv /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo_sample.cfg /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
mv /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo_sample.cfg /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
mv /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo_sample.cfg /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
4、 配置每一个Zookeeper 的dataDir 和 clientPort 分别为2181 2182 2183
修改/usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
clientPort=2181
dataDir=/usr/local/zookeeper-cluster/zookeeper-1/data
vim /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
clientPort=2182
dataDir=/usr/local/zookeeper-cluster/zookeeper-2/data
vim /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
clientPort=2183
dataDir=/usr/local/zookeeper-cluster/zookeeper-3/data
1、在每个zookeeper的 data 目录下创建一个 myid 文件,内容分别是1、2、3 。这个文件就是记录每个服务器的ID
echo 1 >/usr/local/zookeeper-cluster/zookeeper-1/data/myid
echo 2 >/usr/local/zookeeper-cluster/zookeeper-2/data/myid
echo 3 >/usr/local/zookeeper-cluster/zookeeper-3/data/myid
2、在每一个zookeeper 的 zoo.cfg配置客户端访问端口(clientPort)和集群服务器IP列表。
集群服务器IP列表如下
vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
server.1=192.168.52.128:2881:3881
server.2=192.168.52.128:2882:3882
server.3=192.168.52.128:2883:3883
server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口
这样他们三个就知道了对方的存在就构成了一个集群的环境
启动集群就是分别启动每个实例
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status
再查询第二个服务Mod 为leader表示是领导者(主)
查询第三个为跟随者(从)
(1)首先我们先测试如果是从服务器挂掉,会怎么样
把3号服务器停掉,观察1号和2号,发现状态并没有变化
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh stop
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
由此得出结论,3个节点的集群,从服务器挂掉,集群正常
(2)我们再把1号服务器(从服务器)也停掉,查看2号(主服务器)的状态,发现已经停止运行了。
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh stop
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
由此得出结论,3个节点的集群,2个从服务器都挂掉,主服务器也无法运行。因为可运行的机器没有超过集群总数量的半数。
(3)我们再次把1号服务器启动起来,发现2号服务器又开始正常工作了。而且依然是领导者。
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
(4)我们把3号服务器也启动起来,把2号服务器停掉,停掉后观察1号和3号的状态。
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh stop
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status
由此我们得出结论,当集群中的主服务器挂了,集群中的其他服务器会自动进行选举状态,然后产生新得leader
(5)我们再次测试,当我们把2号服务器重新启动起来启动后,会发生什么?2号服务器会再次成为新的领导吗?我们看结果
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status
1.处理事务请求
⒉集群内部各服务器的调度者
1.处理客户端非事务请求,转发事务请求给Leader服务器2参与Leader选举投票
1.处理客户端非事务请求,转发事务请求给Leader服务器
2.不参与Leader选举的投票,但分担 Follower的压力