- zookeeper介绍
- 什么是zookeeper
Zookeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance)等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKeeper本身可以以单机模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。
1、zookeeper是为别的分布式程序服务的
2、Zookeeper本身就是一个分布式程序(只要有半数以上节点存活,zk就能正常服务)
3、Zookeeper所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理、分布式共享锁、统> 一名称服务等
4、虽然说可以提供各种服务,但是zookeeper在底层其实只提供了两个功能:
管理(存储,读取)用户程序提交的数据(类似namenode中存放的metadata);
并为用户程序提供数据节点监听服务; - Zookeeper集群机制
Zookeeper集群的角色: Leader 和 follower
只要集群中有半数以上节点存活,集群就能提供服务 - zookeeper特性
a、Zookeeper:一个leader,多个follower组成的集群
b、全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
c、分布式读写,更新请求转发,由leader实施
d、更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
e、数据更新原子性,一次数据更新要么成功,要么失败
f、实时性,在一定时间范围内,client能读到最新数据
- Zookeeper数据结构
a、层次化的目录结构,命名符合常规文件系统规范(类似文件系统)
b、每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识
c、节点Znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点)
- Zookeeper节点的类型
PERSISTENT 持久化节点
PERSISTENT_SEQUENTIAL 持久化顺序节点
EPHEMERAL 临时节点
EPHEMERAL_SEQUENTIAL 临时顺序节点 - zookeeper的应用场景
数据发布与订阅(配置中心)
命名服务(Naming Service)
分布式通知/协调
集群管理与Master选举
分布式锁
- 事件监听
事件监听器(Watcher)是ZooKeeper非常重要的特性,我们可以在节点上注册Watcher,并且在一些特性事件触发时候,服务器将事件通知到客户端上
- windows环境安装
- 下载, 地址:http://zookeeper.apache.org/
直接下载到本地 - 解压
解压后如下: - 设置配置文件
因为zookeeper默认读取的是zoo.cfg配置,而且下载得没有,所有这个文件得我们自己去手动配置
进入conf目录,直接将zoo_sample.cfg文件copy后改成zoo.cfg文件,内容不变
- 启动zookeeper
进入zookeeper得bin目录点击'zkServer.cmd'启动
这里zookeeper安装完成 - zookeeper可视化工具使用
下载ZooInspector,地址:https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip, 解压,
进入ZooInspector的build目录,双击'zookeeper-dev-ZooInspector.jar',启动
输入zookeeper的链接IP
链接成功如下:这里我们查看到zookeeper的数据节点
这里可视化工具安装完成! - 使用zookeeper客户端操作
进入zookeeper的bin目录,双击'zkCli.cmd',启动客户端。zookeeper常用命令如下:
help 显示所有操作命令
a、查看当前节点中所包含的内容: ls /
b、查看当前节点内容和详细信息: ls2 /
c、获取节点的值:get /zookeeper
d、创建节点:create /节点名称 节点value
e、创建临时节点: create -e /节点名称 节点value --使用quit退出,该节点也会消失
f、修改节点: set /name xiaoming
g、删除节点:delete /name
h、递归删除节点: --递归删除所有的节点,包括子节点
rmr /name
i、查看节点状态:
stat /name
j、监听节点值的变化: --只要这个节点值变化,就会有且只有一次响应,即节点值改变一次之后就不会再监听了
get /name watch
k、监听节点的子节点变化(路径变化) --只要路径变化就会相应,就会响应一次
ls /name watch
l、查看所有操作命令: help
- linux安装zookeeper
本次安装环境是jdk1.8,记得安装前关闭防火墙。
- 关闭防火墙
service iptables stop
- 进入目录
cd /usr/local
- 下载
wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
- 解压
tar -zxvf zookeeper-3.4.14.tar.gz
- 重命令解压目录
mv zookeeper-3.4.14 zookeeper
- 重命名配置文件
cd /usr/local/zookeeper/conf mv zoo_sample.cfg zoo.cfg // 重新命令文件
- 编辑文件zoo.cfg,修改dataDir的指定路径
vi zoo.cfg ######################修改zoo.cfg的内容如下############################### dataDir=/usr/local/zookeeper/data
创建dataDir所指定的目录
mkdir /usr/local/zookeeper/data // 在zookeeper下创建data目录
- 配置环境变量
vi /etc/profile ####################在文件末尾添加配置如下######### export ZOO_HOME=/usr/local/zookeeper export PATH=$ZOO_HOME/bin:$PATH
source /etc/profile
- 启动
bin/zkServer.sh start // 启动 jps // 查看是否启动成功
-
集群规划
- 准备3台服务器137、138、139,安装上述步骤先安装好zookeeper,先停止zookeeper服务。
- 编辑137的zoo.cfg文件
cd /usr/local/zookeeper/conf vi zoo.cfg ##############在文件的最后追加内容######################## server.0=192.168.5.137:2888:3888 #===对应myid文件内容为0 server.1=192.168.5.138:2888:3888 #===对应myid文件内容为1 server.2=192.168.5.139:2888:3888 #===对应myid文件内容为2
- 在dataDir=/usr/local/zookeeper/data目录下,创建myid文件,内容按上面的说明
cd /usr/local/zookeeper/data vim myid ##############添加137:'0', 138:'1', 139;'2'内容如下################# 0
查看 cat myid
各个服务器的myid文件内容不一样,137为0,138为1,139为2,上面也有说明 - 按照上面的步骤138、139的也配置一遍。
- 启动137、138、139的zookeeper并查看状态 。这里已经就完成集群搭建了
[root@localhost bin]# ./zkServer.sh start # 启动 [root@localhost bin]# ./zkServer.sh status # 查看状态
137
138
139
此时,138为主服务,137、139为从服务。
- 错误信息
我的启动报错如下,如果一样,可以参考下
我的是因为我配置了3台服务,但我只启动了137这台,只要138、139启起来,再去查看自然就好了。。[root@localhost bin]# ./zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [root@localhost bin]# ./zkServer.sh status ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg Error contacting service. It is probably not running
- zookeeper配置文件的讲解
- zoo.cfg配置文件内容如下:
# The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial # synchronization phase can take initLimit=10 # The number of ticks that can pass between # sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. dataDir=/usr/local/zookeeper/data # the port at which the clients will connect clientPort=2181 # the maximum number of client connections. # increase this if you need to handle more clients #maxClientCnxns=60 # # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. # # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # # The number of snapshots to retain in dataDir #autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature #autopurge.purgeInterval=1 server.0=192.168.5.137:2888:3888 server.1=192.168.5.138:2888:3888 server.2=192.168.5.139:2888:3888
- 配置文件说明
tickTime:心跳时间,为了确保连接存在的,以毫秒为单位,最小超时时间为两个心跳时间, 若服务挂了,自动轮询下一台 initLimit:多少个心跳时间内,允许其他server连接并初始化数据,如果ZooKeeper管理的数据较大,则应相应增大这个值 clientPort:服务的监听端口 dataDir:用于存放内存数据库快照的文件夹,同时用于集群的myid文件也存在这个文件夹里(注意:一个配置文件只能包含一个dataDir字样,即使它被注释掉了。) dataLogDir:用于单独设置transaction log的目录,transaction log分离可以避免和普通log还有快照的竞争 syncLimit:多少个tickTime内,允许follower同步,如果follower落后太多,则会被丢弃。 server.A=B:C:D: A是一个数字,表示这个是第几号服务器,B是这个服务器的ip地址 C第一个端口用来集群成员的信息交换,表示的是这个服务器与集群中的Leader服务器交换信息的端口 D是在leader挂掉时专门用来进行选举leader所用
- zookeeper的常用命令
启动zookeeper bin/zkServer.sh start 查看进程是否启动 jps 查看状态 bin/zkServer.sh status 停止zookeeper bin/zkServer.sh stop 查看启动错误信息 cat bin/zookeeper.out 重启 bin/zkServer.sh restart 启动客户端 bin/zkCli.sh 退出客户端 quit - 使用java语言操作zookeeper
- 创建maven项目,在pom.xml中引入依赖
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.6</version> </dependency>
- 创建测试类
import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.io.IOException; import java.util.List; /** * Zookeeper Wathcher操作节点 * Create by wangxb * 2019-06-25 6:46 */ public class ZkWatcher implements Watcher { /** * zookeeper集群连接地址 */ private static final String CONNECT_ADDR = "192.168.5.137:2181,192.168.5.138:2181,192.168.5.139:2181"; /** * session超时时间 */ private static final int SESSION_OUTTIME = 2000; private ZooKeeper zooKeeper; // 创建连接 public void createConnection(String address, int sessionTimeout){ try { zooKeeper = new ZooKeeper(address, sessionTimeout, this); } catch (IOException e) { e.printStackTrace(); } } // 关闭连接 public void close(){ try { if (zooKeeper != null) zooKeeper.close(); } catch (InterruptedException e) { e.printStackTrace(); } } @Override // 对zk节点的新增、修改、删除进行监听 public void process(WatchedEvent event) { // 获取节点的创建和类型 System.out.println("接收服务端的Watcher通知............."); Event.KeeperState keeperState = event.getState(); // 连接状态 Event.EventType eventType = event.getType(); // 连接类型 String path = event.getPath(); // 节点路径 // 判断是否在建立连接 if (Event.KeeperState.SyncConnected == keeperState) { // 连接类型 if (Event.EventType.None == eventType) { System.out.println("zk 建立连接成功"); } // 创建类型 if (Event.EventType.NodeCreated == eventType) { System.out.println("当前事件属于:创建 节点, 路径为"+path); } // 修改类型 if (Event.EventType.NodeDataChanged == eventType) { System.out.println("当前事件属于:修改 节点, 路径为"+path); } // 删除类型 if (Event.EventType.NodeDeleted == eventType) { System.out.println("当前事件属于:删除 节点, 路径为"+path); } }else{ System.out.println("zk 连接失败: 请检查......"); } } // 创建节点 public void createNode(String path, String data) throws KeeperException, InterruptedException { // zooKeeper.exists(path, true); 表示开启事件通知 由于zookeeper的监控都是一次性的所以 每次必须设置监控 zooKeeper.exists(path, true); // CreateMode.EPHEMERAL:临时节点,只在当前会话存在 CreateMode.PERSISTENT:持久节点,永久存在 zooKeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); System.out.println("创建节点成功......."); } // 修改节点 public void updateNode(String path, String data) throws KeeperException, InterruptedException { zooKeeper.exists(path, true); // 参数3为传入的版本号,这里默认-1 zooKeeper.setData(path, data.getBytes(), -1); System.out.println("修改节点成功......."); } // 删除节点 public void deleteNode(String path) throws KeeperException, InterruptedException { zooKeeper.exists(path, true); // Stat stat=new Stat(); zooKeeper.delete(path, -1); // -1表示版本号 System.out.println("删除节点成功......."); } // 获取节点数据 public void getData(String path, boolean watch) throws KeeperException, InterruptedException { String result = new String(zooKeeper.getData(path, watch, null)); System.out.println("获取节点数据成功....."+result); } // 获取子节点 public List<String> getChildren(String path, boolean needWatch) throws KeeperException, InterruptedException { return zooKeeper.getChildren(path, needWatch); // 默认true; } // 判断节点是否存在 public Stat exists(String path, boolean needWatch) throws KeeperException, InterruptedException { return zooKeeper.exists(path, needWatch); // 参数默认true; 返回null则不存在 } public static void main(String[] args) throws Exception { String path = "/aaa"; ZkWatcher zk = new ZkWatcher(); zk.createConnection(ZkWatcher.CONNECT_ADDR, ZkWatcher.SESSION_OUTTIME); // zk.createNode(path, "456"); // zk.updateNode(path, "456"); // zk.getData(path, true); // List<String> children = zk.getChildren(path, true); Stat stat = zk.exists(path, true); System.out.println(stat); } }
- 测试完成
- 相关方法讲解
// zooKeeper.exists(path, true); 表示开启事件通知 由于zookeeper的监控都是一次性的所以 每次必须设置监控
param1: 节点路径(名称),创建时父节点必须存在。必须以"/"开头zk.create(param1, param2, param3, param4)
param2: 节点内容: 要求类型是字节数组(不支持序列化方式,如果需要实现序列化,可使用java相关序列化框架。)
param3: 节点权限: 一般默认都使用Ids.OPEN_ACL_UNSAFE开放权限即可。
param4: 节点类型, 有四种可选,用的最多的是EPHEMERAL、PERSISTENT1、PERSISTENT(持久节点) // 数据持久存在
2、PERSISTENT SEQUENTIAL(持久顺序节点)
3、EPHEMERAL(临时节点) // 数据只在当前会话有效
4、EPHEMERAL SEQUENTAL(临时顺序节点)、 -
关于Watcher
在ZooKeeper中,接口类Watcher用于表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包含KeeperState和 EventType两个枚举类,分别代表了通知状态和事件类型,同一个事件类型在不同的通知状态中代表的含义有所不同, 同时定义了事件的回调方法:process(WatchedEvent event)。
process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调,从而实现对事件的处理。process方法的定义如下:
abstract public void process(WatchedEvent event)
相关方法
KeeperState
EventType
触发条件
说明
None
(-1)客户端与服务端成功建立连接
SyncConnected
(0)NodeCreated
(1)Watcher监听的对应数据节点被创建
NodeDeleted
(2)Watcher监听的对应数据节点被删除
此时客户端和服务器处于连接状态
NodeDataChanged
(3)Watcher监听的对应数据节点的数据内容发生变更
NodeChildChanged
(4)Wather监听的对应数据节点的子节点列表发生变更
Disconnected
(0)None
(-1)客户端与ZooKeeper服务器断开连接
此时客户端和服务器处于断开连接状态
Expired
(-112)Node
(-1)会话超时
此时客户端会话失效,通常同时也会受到SessionExpiredException异常
AuthFailed
(4)None
(-1)通常有两种情况,1:使用错误的schema进行权限检查 2:SASL权限检查失败
通常同时也会收到AuthFailedException异常
- zookeeper实现分布式锁
- 分布式锁的3种实现方式
1、基于数据实现分布式锁 IO操作性能较差,容易出现单点故障 锁没有失效事件,容易死锁。 非阻塞式,不可重入
2、基于缓存实现分布式锁 锁没有失效事件,容易死锁 非阻塞式 不可重入
3、基于Zookeeper实现分布式锁 实现相对简单 可靠性高 性能较好
- 分布式锁案例
场景描述
在线程高并发场景下,生成唯一的订单编号 ,如:2017-10-14-20-52-33-01
实现思路: 使用zookeeper的临时节点(会话内有效),和路径不可重复。
在zk上创建临时节点,使临时节点作为锁且节点不可以重复,
如果创建失败,则继续等待。
如果能创建界节点成功,则生成订单号。zk临时会话结束,释放锁。其他节点继续生成订单号
- 创建一个maven项目,在pom.xml种引入依赖
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency>
- 定义一个接口Lock.java
public interface Lock { // 手动上锁 void getLock(); // 手动释放锁 void unLock(); }
- 定义一个抽象类ZookeeperAbstractLock.java实现该接口
import org.I0Itec.zkclient.ZkClient; /** * Create by wangxb * 2019-06-27 7:51 */ public abstract class ZookeeperAbstractLock implements Lock{ private static final String CONNECT_ADDR = "192.168.5.137:2181,192.168.5.138:2181,192.168.5.139:2181"; protected static final String PATH = "/lock"; protected ZkClient zkClient = new ZkClient(CONNECT_ADDR); @Override public void getLock() { // 通过创建锁资源, 如果创建成功返回true, 否则false if (tryLock()){ System.out.println("获取锁资源成功......."); }else{ waitLock(); // 等待 getLock();; // 重新获取锁 } } protected abstract boolean tryLock(); protected abstract void waitLock(); @Override public void unLock() { // 关闭链接,释放锁 if (zkClient != null){ zkClient.close(); System.out.println("释放锁资源成功......."); } } }
- 定义一个类ZookeeperLockImpl.java继承该抽象类
import org.I0Itec.zkclient.IZkDataListener; import java.util.concurrent.CountDownLatch; /** * Create by wangxb * 2019-06-27 20:44 */ public class ZookeeperLockImpl extends ZookeeperAbstractLock { private CountDownLatch countDownLatch = null; @Override protected boolean tryLock() { try { zkClient.createEphemeral(PATH); // 通过创建临时节点, 如果成功返回true 否则false return true; } catch (Exception e){ return false; } } @Override protected void waitLock() { // 如果存在则需要等待 IZkDataListener iZkDataListene = new IZkDataListener() { @Override // 节点改变事件通知,临时节点创建成功后删除, 唤醒线程 public void handleDataChange(String path, Object o) throws Exception { } @Override // 删除事件通知,临时节点创建成功后删除, 唤醒线程 public void handleDataDeleted(String path) throws Exception { if (countDownLatch != null) countDownLatch.countDown(); } }; // 注册到zkClient监听 zkClient.subscribeDataChanges(PATH, iZkDataListene); if (zkClient.exists(PATH)){ countDownLatch = new CountDownLatch(1); try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } zkClient.unsubscribeDataChanges(PATH, iZkDataListene); } }
- 定义一个类OrderNumber.java生成订单号
import java.text.SimpleDateFormat; import java.util.Date; /** * Create by wangxb * 2019-06-27 7:22 */ public class OrderNumber { private static int count = 0; /** * 生成订单号 * @return */ public String createOrdereNumber(){ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-Mm-dd-HH-mm-ss"); String format = simpleDateFormat.format(new Date()); return format+ "-"+ ++count; } }
- 定义一个OrderService.java测试生成订单
/** * Create by wangxb * 2019-06-27 7:22 */ public class OrderService implements Runnable { OrderNumber orderNumber = new OrderNumber(); public static Object obj = new Object(); Lock lock = new ZookeeperLockImpl() ; @Override public void run() { getNumber(); } public void getNumber(){ lock.getLock(); // synchronized (obj) String number = orderNumber.createOrdereNumber(); System.out.println(Thread.currentThread().getName()+"生成订单号---->"+number); lock.unLock(); } public static void main(String[] args) { for(int i=0; i<100; i++){ new Thread(new OrderService()).start(); } } }
- 启动OrderService.java,测试
- Zookeeper实现服务的接口网关
- 未完待续...................................