是一个开源分布式的服务平台,为应用程序提供服务。Zookeeper就像美团,可以容纳不同的商家。
Zookeeper基于观察者模式,有文件系统+通知机制。

特点

  1. 是一个leader和多个follower来组成的集群(狮群中,一头雄狮,N头母狮)
  2. 集群中只要有半数以上的节点存活,Zookeeper就能正常工作(5台服务器挂2台,没问题;4台服
    务器挂2台,就停止)
  3. 全局数据一致性,每台服务器都保存一份相同的数据副本,无论client连接哪台server,数据都是
    一致的
  4. 数据更新原子性,一次数据要么成功,要么失败(不成功便成仁)
  5. 实时性,在一定时间范围内,client能读取到最新数据
  6. 更新的请求按照顺序执行,会按照发送过来的顺序,逐一执行(发来123,执行123,而不是321
    或者别的)

数据结构

ZooKeeper数据模型的结构与linux文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode(ZookeeperNode)。
每一个ZNode默认能够存储1MB的数据(元数据),每个ZNode的路径都是唯一的元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、 资源查找、文件记录等功能 。

应用场景

提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等

配置文件参数解读

Zookeeper中的配置文件zoo.cfg中参数含义解读如下:
tickTime =2000:通信心跳数,Zookeeper服务器与客户端心跳时间,单位毫秒 Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
initLimit =10:LF初始通信时限 集群中的Follower跟随者服务器与Leader领导者服务器之间,启动时能容忍的最多心跳数 102000(10个心跳时间)如果领导和跟随者没有发出心跳通信,就视为失效的连接,领导和跟随者彻底断开
syncLimit =5:LF同步通信时限集群启动后,Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit
tickTime->10秒,Leader就认为Follwer已经死掉,会将Follwer从服务器列表中删除 dataDir:数据文件目录+数据持久化路径 主要用于保存Zookeeper中的数据。
dataLogDir:日志文件目录 clientPort =2181:客户端连接端口监听客户端连接的端口。

内部原理-选举(面试重点)

半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器
虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为 Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的
image.png

  1. Server1先投票,投给自己,自己为1票,没有超过半数,根本无法成为leader,顺水推舟将票数
    投给了id比自己大的Server2
  2. Server2也把自己的票数投给了自己,再加上Server1给的票数,总票数为2票,没有超过半数,也
    无法成为leader,也学习Server1,顺水推舟,将自己所有的票数给了id比自己大的Server3
  3. Server3得到了Server1和Server2的两票,再加上自己投给自己的一票。3票超过半数,顺利成为
    leader
  4. Server4和Server5都投给自己,但是无法改变Server3的票数,只好听天由命,承认Server3是
    leader

内部原理-节点类型

持久型(persistent):
持久化目录节点(persistent)客户端与zookeeper断开连接后,该节点依旧存在
持久化顺序编号目录节点(persistent_sequential)客户端与zookeeper断开连接后,该节点依旧存在,创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护,例如:Znode001,Znode002…
短暂型(ephemeral):
临时目录节点(ephemeral)客户端和服务器端断开连接后,创建的节点自动删除
临时顺序编号目录节点(ephemeral_sequential)客户端与zookeeper断开连接后,该节点被删除,创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增 的计数器,由父节点维护,例如:Znode001,Znode002…

内部原理-监听器(面试重点)

image.png

  1. 在main方法中创建Zookeeper客户端的同时就会创建两个线程,一个负责网络连接通信,一个负
    责监听
  2. 监听事件就会通过网络通信发送给zookeeper
  3. zookeeper获得注册的监听事件后,立刻将监听事件添加到监听列表里
  4. zookeeper监听到 数据变化 或 路径变化,就会将这个消息发送给监听线程
    常见的监听
  5. 监听节点数据的变化:get path [watch]
  6. 监听子节点增减的变化:ls path [watch]
  7. 监听线程就会在内部调用process方法(需要我们实现process方法内容)

内部原理-写数据流程

image.png

  1. Client 想向 ZooKeeper 的 Server1 上写数据,必须的先发送一个写的请求
  2. 如果Server1不是Leader,那么Server1 会把接收到的请求进一步转发给Leader。
  3. 这个Leader 会将写请求广播给各个Server,各个Server写成功后就会通知Leader。
  4. 当Leader收到半数以上的 Server 数据写成功了,那么就说明数据写成功了。
  5. 随后,Leader会告诉Server1数据写成功了。
  6. Server1会反馈通知 Client 数据写成功了,整个流程结束

命令

这部分只需要输入help即可。或者百度。确实是没什么必要单独记一份。

Java API部分

依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.apache.logging.log4j</groupId>
  4. <artifactId>log4j-core</artifactId>
  5. <version>2.8.2</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.apache.zookeeper</groupId>
  9. <artifactId>zookeeper</artifactId>
  10. <version>3.6.0</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>junit</groupId>
  14. <artifactId>junit</artifactId>
  15. <version>4.12</version>
  16. </dependency>
  17. </dependencies>
  1. log4j.properties
  1. log4j.rootLogger=INFO, stdout
  2. log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  3. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  4. log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
  5. log4j.appender.logfile=org.apache.log4j.FileAppender
  6. log4j.appender.logfile.File=target/zk.log
  7. log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
  8. log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

创建zookeeper客户端

  1. public class TestZK {
  2. private String conStr = "192.168.2.131:2181,192.168.2.132:2181,192.168.2.133:2181";
  3. private int sessionTimeOut = 60 * 1000; //超时时间,不宜设置太小。
  4. private ZooKeeper zooKeeperClient;
  5. @Test
  6. public void init() throws IOException {
  7. zooKeeperClient = new ZooKeeper(conStr, sessionTimeOut, new Watcher() {
  8. @Override
  9. public void process(WatchedEvent watchedEvent) {
  10. System.out.println("业务处理! ");
  11. }
  12. });
  13. }
  14. }
  1. 创建节点
  1. @Test
  2. public void createNode() throws InterruptedException, KeeperException {
  3. final String str = zooKeeperClient.create("/uk",
  4. "gentleman".getBytes(StandardCharsets.UTF_8),
  5. ZooDefs.Ids.OPEN_ACL_UNSAFE,
  6. CreateMode.PERSISTENT);
  7. System.out.println(str); //节点路径
  8. }
  1. 获取节点数据
  1. @Test
  2. public void getNodeData() throws InterruptedException, KeeperException {
  3. final byte[] data = zooKeeperClient.getData("/uk", false, new Stat());
  4. System.out.println(new String(data));
  5. }
  1. 修改节点的值
  1. @Test
  2. public void updateData() throws InterruptedException, KeeperException {
  3. final Stat stat = zooKeeperClient.setData("/uk",
  4. "gentlemen".getBytes(StandardCharsets.UTF_8),
  5. 0);
  6. System.out.println(stat);
  7. }
  1. 删除节点
  1. @Test
  2. public void delete() throws InterruptedException, KeeperException {
  3. zooKeeperClient.delete("/uk", 1);
  4. }
  1. 获取子节点
  1. @Test
  2. public void getChildren() throws InterruptedException, KeeperException {
  3. final List<String> children = zooKeeperClient.getChildren("/china", false);
  4. for (String child : children) {
  5. System.out.println(child);
  6. }
  7. }
  1. 监听
  1. @Test
  2. public void watchNode() throws InterruptedException, KeeperException {
  3. final List<String> children = zooKeeperClient.getChildren("/china", true);
  4. for (String child : children) {
  5. System.out.println(child);
  6. }
  7. Thread.sleep(200000);
  8. }
  1. 判断节点是否存在
  1. @Test
  2. public void exists() throws InterruptedException, KeeperException {
  3. final Stat uk = zooKeeperClient.exists("/uk", false);
  4. if (uk != null) {
  5. System.out.println("exists!");
  6. } else {
  7. System.out.println("noop!");
  8. }
  9. }

Zookeeper实现分布式锁

Redis实现的性能高,zookeeper实现的可靠性高。

image.png

  1. 所有请求进来,在/lock下创建 临时顺序节点 ,放心,zookeeper会帮你编号排序
  2. 判断自己是不是/lock下最小的节点

是,获得锁(创建节点)
否,对前面小我一级的节点进行监听

  1. 获得锁请求,处理完业务逻辑,释放锁(删除节点),后一个节点得到通知(比你年轻的死了,你
    成为最嫩的了)
  2. 重复步骤2

当然 Curator都实现好了…