美文网首页
Zookeeper开箱体验

Zookeeper开箱体验

作者: superxcp | 来源:发表于2018-12-11 20:49 被阅读0次
nubia x.jpg

一.概述:

Zookeeper是一个高可用的分布式管理与协调框架,底层实现是基于ZAB(原子消息广播协议,类似paxos
算法)算法。该框架能够很好的保证分布式环境中数据的一致性,因此基于这样的特性,使得Zookeeper成为了一款解决分布式一致性的利器。

为了实现分布式环境中的最终一致性,Zookeeper有几个特点:
1.简单的数据结构:Zookeeper以简单的树形结构来进行相互协调;
2.可以构架集群:一般集群通常由一组机器构成,只要集群中超过半数以上的机器能够正常工作,整个集群就能正常的对外提供服务;
3.顺序访问:对于每个客户端的请求,只有一台leader服务器响应,会为该请求分配一个事务id,应用程序使用该特性更可以实现更高层次的同步;
4.高性能:Zookeeper将所有数据存储在内存中,因此在读操作为主的场景下,性能非常突出。

Zookeeper典型的应用场景:
配置管理、集群管理、发布与订阅、数据库切换、分布式日志收集、分布式锁、队列管理等等。

注:读完本文可以学到基本的Zookeeper的使用方法

二.环境搭建及客户端使用:

下载并解压安装包,我这里用的是zookeeper-3.4.5.tar.gz版本,放在/usr/local文件下,然后重命名为zookeeper:

tar -zxvf zookeeper-3.4.5.tar.gz
mv zookeeper-3.4.5.tar.gz zookeeper

then,修改下环境变量,使后边的操作命令敲起来简单点:

vi /etc/profile
export ZOOKEEPER_HOME=/usr/local/zookeeper
export PATH=.:$HADOOP_HOME/bin:$ZOOKEEPER_HOME/bin:$JAVA_HOME/...
source /etc/profile

then,重命名下配置文件,将zookeeper的配置文件zoo_sample.cfg改为zoo.cfg

cd  /usr/local/zookeeper/conf
mv zoo_sample.cfg zoo.cfg

then,重头戏来了,修改配置文件,一共两处:dataDir用于存放zookeeper产生的数据,第二个是对应的机器集群,以第一条为例,0表示该机器是第几号机器,10.206.63.102是该机器的ip地址,是我的内网机器ip,各位可填写自己的机器ip,2888表示用2888本机器与leader服务器交换信息的端口,3888表示如果集群中的leader机器挂了,需要一个端口重新选举出新的leader,3888就是这个选举端口。zookeeper还有一个对外提供服务的默认端口是2181。

1. dataDir=/usr/local/zookeeper/data
2. server.0=10.206.63.102:2888:3888
   server.1=10.206.63.101:2888:3888
   server.2=10.206.63.171:2888:3888

既然配了data文件夹,那就要在三台机器指定的位置创建这个data文件夹以存储数据(mkdir data),然后在data文件夹下创建myid文件(vim myid)并填写内容分别问0/1/2(例如10.206.63.102机器data文件夹下的myid文件内容为0,改内容英语标识服务器)。
OK,至此,我们的zookeeper就已经配好了,下面我们进入zookeeper的bin目录下启动zookeeper:

zkServer.sh start
zkServer.sh status //用于查看启动状态

可以看到,三个节点中,有的是leader,有的是follower,说明启动成功。在这个过程中如果遇到问题,可以结合bin下面的zookeeper.out日志去查找错误,网上有很多博客,自己去找啦:


image.png

三.Zookeeper数据操作:

Zookeeper的有4种节点,1)PERSISTENT(永久);2)PERSISTENT_SEQUENTIAL(永久且有序);3)EPHEMERAL(临时);4)EPHEMERAL_SEQUENTIAL(临时且有序)。

数据是树状结构的,类似linux的数据结构,命令就几个,zkCli.sh 进入zookeeper客户端,根据提示命令进行操作:
查找:ls
创建并赋值: create /third good
获取:get /third
设值:set /third superxcp
递归删除节点:rmr /third
删除指定节点:delete /third/child
tips:这里大家可以使用工具查看数据,比如ZooInspector,Eclipse自带的查看插件
比如我往/third节点下set了superxcp的数据,get之后看到值和详细信息,包括事务zxid,time,内容长度,子节点、版本等信息。关于事务zxid说一下,Zookeeper状态的每一次改变,都对应着一个递增的Transaction id,称为zxid,由于zxid的递增性质,创建/删除/更新任意节点,都会导致zxid的改变。http://coolxing.iteye.com/blog/1871328

image.png

四.Java操作Zookeeper:

API操作Zookeeper有三种方式,官方提供的javaAPI/zkClient/curator,curator是最简单好用的一种,我们这里也只介绍curator的使用方式。
我们只需要引入下面这个包(基于maven):

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.0.0</version>
</dependency>

使用方法非常简单,不多说,直接上代码:

public static void main(String[] args) throws Exception {

//创建zookeeper的客户端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);

CuratorFramework client = CuratorFrameworkFactory.newClient("10.206.63.102:2181,10.206.63.101:2181,10.206.63.171:2181", retryPolicy);

client.start();

//创建分布式锁, 锁空间的根节点路径为/curator/lock
InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");

mutex.acquire();

//获得了锁, 进行业务流程
System.out.println("实现我们自己的业务!");

//完成业务流程, 释放锁
mutex.release();

//关闭客户端
client.close();
}

可以看到关键的核心操作就只有mutex.acquire()和mutex.release()。

五.Zookeeper实现分布式锁:

下面用一个场景介绍下分布式锁的原理:

  • 临时节点:数据属于某个session的数据,当会话存在时创建,会话结束时删除,Zookeeper正是利用临时性数据的特性实现分布式锁。
  • 有序节点:假如当前的父节点为/lock,我们可以在这个父节点下面创建子节点,zookeeper提供了一个可选的有序特性,例如我们可以创建子节点“/lock/node-”并且指明有序,那么zookeeper在生成子节点时会根据当前的子节点数量自动添加整数序号,也就是说如果是第一个创建的子节点,那么生成的子节点为/lock/node-0000000000,下一个节点则为/lock/node-0000000001,依次类推。
  • 事件监听:在读取数据时,我们可以同时对节点设置事件监听,客户端和zookeeper之间会保持一个心跳连接,当节点数据或结构变化时,zookeeper会通知客户端。当前zookeeper有如下四种事件:1)节点创建;2)节点删除;3)节点数据修改;4)子节点变更。


    image.png

每个zookeeper节点都有各自的客户端链接,假设锁空间的根节点为/lock,分布式锁的实现的流程是:
1.客户端连接zookeeper,并在/lock下创建临时有序的子节点,假设客户端C1对应子节点为/lock/lock-0000000000,C2对应子节点为为/lock/lock-0000000001...
2.客户端获取/lock子节点列表,判断自己的子节点是否为当前子节点列表中序号最小的,如果是,则判定为获取到锁,不是则监听子节点的变更消息,获取子节点变更通知然后重复判断自己是否是最小子节点。(事实上,为了不让一个节点释放锁时唤醒所有子节点,一个客户端只需要监听比自己小的序号的子节点的节点变化就可以了,比如C2只需要监听C1子节点的变化,如果监听所有节点,那么一个节点值的变化会让zookeeper通知所有客户端,这会阻塞其他的操作,成为“羊群效应”)。
3.执行自己的业务代码,完成后删除对应自己建的子节点释放锁。

这里可能会有几个问题:
比如:
Q:客户端C1获取锁之后,客户端机器挂了,客户端没有删除子节点,岂不是这个锁永远得不到释放?
A:客户端创建的是临时节点,与zookeeper之间是有心跳连接的,过一段时间zookeeper收不到客户端的心跳连接,会判定连接失效,会直接删掉该客户端对应的节点,从而释放锁。
再比如:
Q:客户端C1对应子节点为/lock/lock-0000000000,客户端C2子节点为/lock/lock-0000000001,C2在获取锁时发现自己不是最小的,但给C2注册监听之前C1的子节点被删除了,那么C2岂不是永远都监听不到C1的变化,从而一直等待?
A:设置监听器的操作与读操作是原子执行的,也就是说在读子节点列表时同时设置监听器,所以不存在这种情况。

详细的源码可以看下这篇文章【https://blog.csdn.net/qiangcuo6087/article/details/79067136】,看个两三遍就能懂了,讲的非常浅显易懂,我画图不好,上面的图也是引用的这篇文章的。

六. Watcher监听机制:

image.png
  • ZooKeeper :部署在远程主机上的 ZooKeeper 集群。
  • Client :分布在各处的 ZooKeeper 的 jar 包程序,被引用在各个独立应用程序中。
  • WatchManager :用于管理各个监听器的一个接口,只有一个方法 materialize(),返回一个 Set<Watcher> 。

Watcher监听机制总体来讲可以这样描述:客户端向Zookeeper注册watcher监听,同时会将watcher对象存储在客户端的watchManager中。当Zookeeper服务器服务器触发了watcher事件之后,会向客户端发通知,客户端根据节点和触发的事件从watchManager中取出对应的watcher对象来调用process()方法完成回调。

值得一谈的是,无论是服务端还是客户端,一旦一个watcher事件被触发,服务器会将该watcher存储中移除,每个注册和时间返回都是一个完整的过程,因此在watcher使用上,需要反复注册。当然,客户端在完成回调方法时也会在watcherManager的set<Watcher>中移除,因此在客户端和服务端,监听都是一次性的,这样可以减轻服务端的压力,而且服务端向客户端发监听事件时,只发了状态和事件名,具体的数据变化需要客户端自己去再次请求获取。另外,客户端在处理watcher回调的过程是一个串行同步的过程,一定不能因为一个Watcher 的处理逻辑影响了整个客户端的 Watcher 回调。
想要看watcher机制源码的同学可以参考这篇文章,有深度:https://blog.csdn.net/hohoo1990/article/details/78617336

后记:由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!

相关文章

网友评论

      本文标题:Zookeeper开箱体验

      本文链接:https://www.haomeiwen.com/subject/stenqqtx.html