zookeeper

zookeeper入门

概述

zookeeper是一个开源的分布式的,为分布式应用提供协调(zookkeeper:动物管理员)服务的Apache项目

zookeeper工作机制

zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架。负责储存和管理大家都关心的数据。然后接受观察者的注册,一旦这些数据发生变化,zookeeper就负责通知已经在zookeeper上注册的那些观察者。然后观察者作出相应的反应。
zookeeper=文件系统+通知机制

特点

  1. 1个leader和多个fllower组成的集群
  2. 集群中只要有半数以上的节点存活。zookeeper就能正常服务
  3. 全局数据一致:每个server保存一份相同的数据副本。Client无论连接到哪个server,数据都是一致的
  4. 更新请求顺序进行,来自同一个Client的更新请求按其发送顺序依次进行
  5. 数据更新原子性,一次数据更新要么成功,要么失败
  6. 实时性:在一定的时间范围内,Client能读到最新的数据。zk同步数据非常快(数据量小)

数据结构

整体上可以看作是一棵树。每个节点称作一个ZNode。每个ZNode默认存储1MB(数据量小)的数据。每个ZNode通过其路径唯一标识。

数据结构

应用场景

统一命名服务,统一配置管理,统一集群管理,服务器节点动态上下线,软负载均衡

统一命名服务

在分布式环境下,经常需要对应用/服务统一命名,便于识别。例如:IP不容易记住,而域名容易记住

  • /
    • /service
      • www.baidu.com
        • 192.168.22.13
        • 192.168.22.14
        • 192.168.22.15

统一配置管理

在分布环境下,配置文件同步非常常见

  1. 一般要求一个集群中,所有节点的配置信息是一致的
  2. 对配置文件修改后,希望能快速的同步到各个节点上

配置管理可由zookeeper实现

  1. 可将配置信息写入zookeeper上的一个ZNode
  2. 各个客户端服务器监听这个ZNode
  3. 一旦ZNode中的数据被修改,zookeeper将通知各个客户端服务器

统一集群管理

  1. 分布式环境中,实时掌握每个节点的状态是必要的。zk可以根据节点实时状态作出调整。
  2. zk可以实现实时掌握监控节点状态变化
    1. 可以将节点信息写入zk的znode
    2. 监听这个znode可获取他的实时状态变化

服务器动态上下线

  1. 服务端启动时注册信息(创建临时节点)
  2. 客户端获取到当前在线服务器列表。并且注册监听
  3. 服务器节点下线
  4. Zk:服务器节点上下线事件通知
  5. process()重新获取服务器列表,并且注册监听

负载均衡

在zk中记录每台服务器的访问数。让访问数最少的服务器去处理最新的客户端请求

  • /
    • /server
      • 注册登陆服务
        • 192.168.22.13:访问数60
        • 192.168.22.14:访问数50
        • 192.168.22.15:访问数65

zookeeper安装(standalone)

https://zookeeper.apache.org/

  1. 安装JDK
  2. 解压zookeeper

配置修改

#将conf这个路径下的zoo_sample.cfg修改为zoo.cfg
mv zoo_sample.cfg zoo.cfg

#打开zoo.cfg修改dataDir路径
dataDir=/home/yowai/Desktop/module/zookeeper/tmp

#创建tmp文件夹
mkdir tmp

操作zookeeper

#启动
bin/zkServer.sh start
#查看状态
bin/zkServer.sh status
#关闭
bin/zkServer.sh stop

#启动客户端
zkCli.sh
#退出客户端
quit

配置参数解读

#通信心跳数,zookeeper服务器与客户端心跳时间,单位毫秒。
tickTime=2000

#LF初始通信时限。集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。刚开始启动的时候比较耗时间,所以时间要长一点
initLimit=10

#集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
syncLimit=5

#数据文件目录+数据持久化路径
dataDir=/home/yowai/Desktop/module/zookeeper/tmp

#客户端连接端口
clientPort=2181

zookeeper内部原理

选举机制

  1. 半数机制:集群中半数以上机器存活,集群可用。所以zookeeper适合安装奇数台服务器
  2. 虽然没有在配置文件中指定master和slave。但是其工作的时候,是有一个节点为leader,其它则为follower。leader是通过内部的选举机制临时产生的

用1个简单的例子说明此过程:假设有5台服务器组成的集群。它们的id从1-5。同时它们又是同时启动的,也就是说没有历史数据,在存放数据量这个维度上都是一样的

  1. 服务器1启动,此时只有1台服务器启动,它发出的报文没有任何响应,所以它的选举状态一直是looking
  2. 服务器2启动。它与最开始启动的服务器1进行通信,互相交换自己的选举结果。由于没有历史数据,所以Id较大的服务器2胜出。但由于没有达到超过半数以上的服务器都同意选举他(此例子中半数以上是3)。所以服务器1,2还是保持looking状态
  3. 服务器3启动,根据前面的理论分析,服务器3成为1,2,3中的老大。此时有3台服务器选举了它,所以它成为此次选举的leader
  4. 服务器4启动,因为已经有服务器3为leader,所以服务器4为follower
  5. 服务器5与4同理

节点类型

持久类型(Persistent):客户端与服务器断开连接后,创建的节点不删除

短暂类型(Ephemeral):客户端与服务器断开连接后,创建的节点删除

image-20200407014749647
  1. 持久化目录节点:客户端与zookeeper断开连接后,该节点仍然存在
  2. 持久化顺序编号目录节点:客户端断开后,该节点仍然存在,只是zookeeper给该节点名称进行顺序编号。分布式系统中,顺序号可以被用于为所有事件进行全局排序,这样客户端可以通过编号推断事件的顺序
  3. 临时目录节点:客户端断开连接后,该节点被删除
  4. 临时顺序编号目录节点:客户端断开后,该节点被删除,只是zookeeper给该节点名称进行顺序编号。

分布式安装(集群)

解压ZK到module文件夹

#将zoo_sample.cfg文件复制为zoo.cfg,然后添加内容
vim /home/yowai/Desktop/module/zookeeper/conf/zoo.cfg

#修改路径
dataDir=/home/yowai/Desktop/module/zookeeper/tmp

#使用和选举用不同的端口。其中1,2,3是第几台服务器
server.1=slave2:2888:3888
server.2=slave3:2888:3888
server.3=slave4:2888:3888

配置参数解读:

server.A=B:C:D

  1. A是一个数字,表示这是第几台服务器。集群模式下配置一个文件myid。这个文件在DataDir目录下,文件里的数据就是A的值,zk启动时读取此文件,拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server
  2. B是这个服务器的IP地址
  3. C是服务器启动与集群中的Leader服务器交换信息的端口
  4. D是玩意集群中的Leader挂掉了,需要另一个端口重新进行选举出新的Leader。这个端口就是用来执行选举时服务器相互通信的端口

创建/home/yowai/Desktop/module/zookeeper/tmp文件

#在以上目录下添加1。注意:slave1 添加1;slave2 添加2;slave3 添加1;
echo 1 > myid

添加环境变量

export JAVA_HOME=/home/yowai/Desktop/module/jdk
export HADOOP_HOME=/home/yowai/Desktop/module/hadoop
export ZOOKEEPER_JOME=/home/yowai/Desktop/module/zookeeper

export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$ZOOKEEPER_HOME/bin

分发ZK

scp -r zookeeper/ slave3:`pwd`
scp -r zookeeper/ slave4:`pwd`

修改其他节点的信息

更改权重

#在/home/yowai/Desktop/module/zookeeper/tmp目录下
#slave3
echo 2 > myid

#slave4
echo 3 > myid

添加环境变量

节点都添加

export JAVA_HOME=/home/yowai/Desktop/module/jdk
export HADOOP_HOME=/home/yowai/Desktop/module/hadoop
export ZOOKEEPER_HOME=/home/yowai/Desktop/module/zookeeper

export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$ZOOKEEPER_HOME/bin

测试ZK

先启动权重为1的节点

#在bin目录下启动
./zkServer.sh start
#查看状态
./zkServer.sh status

#关闭
zkServer.sh stop

<img src="https://tva1.sinaimg.cn/large/00831rSTgy1gd92zp4htpj315e09atbj.jpg" alt="image-20200328031028311" style="zoom:50%;" />

启动权重为2的节点

权重为1的节点信息

image-20200328031456502

权重为2的节点信息

image-20200328031402453

此时即使启动权重为3的节点,它也是「fllower」,因为集群中已经有一个「leader」了,没必要再选

监听器原理

  1. 首先要有一个main()线程
  2. main()线程中创建zookeeper客户端,这是就会创建两个线程。一个负责网络连接通信(connect)一个负责监听(listener)
  3. 通过connect线程将注册的监听事件发送给zookeeper
  4. 在zookeeper的注册监听列表中将注册的监听事件添加到列表中
  5. zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程
  6. listener线程内部调用了process()方法
image-20200419041644294

常见的监听

  1. 监听节点数据的变化:get path [watch]
  2. 监听子节点增减的变化:ls path [watch]

写数据流程

image-20200419035919642
  1. Client向zookeeper的server1上写数据,发送一个写数据的请求
  2. 如果server1不是leader,那么server1会把接收的请求进一步转发给leader。leader把「写请求」广播给各个server。各个server写成功后会通知给leader
  3. 当leader收到半数以上server数据写成功,那么就说明数据写成功了。此时leader会告诉server1数据写成功了
  4. server1会进一步通知client数据写成功了,这时就认为操作成功

zookeeper实战

客户端命令操作

命令 功能
help 显示所有操作命令
ls path [watch] 使用ls命令查看当前znode中所包含的内容
ls2 path [watch] 查看当前节点数据并能看到更新次数等信息
create 普通创建;-s含有序列;-e临时
get path [watch] 获得节点的值
set 设置节点的值
stat 查看节点状态
delete 删除节点
rmr 递归删除节点

常用API

maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>zookeeper</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zook eeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
        </dependency>
    </dependencies>


</project>

历史服务配

Resources/log4j.properties

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

代码

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.List;

public class TestZookeeper {


    private String connectString="slave2:2181,slave3:2181,slave4:2181";
    private int sessionTimeOut=2000;
    private ZooKeeper zkClient;


    //连接客户端
    @Before
    public void init() throws IOException {

        zkClient=new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
              public void process(WatchedEvent watchedEvent) {
//
//                System.out.println("--------start--------");
//                List<String> children = null;
//                try {
//                    children = zkClient.getChildren("/", true);
//                    for (String child:children){
//                        System.out.println(child);
//                    }
//                    System.out.println("--------end--------");
//
//                } catch (KeeperException e) {
//                    e.printStackTrace();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//
//
//
            }
        });
    }


    //创建节点
    @Test
    public void createNode() throws KeeperException, InterruptedException {
        String path = zkClient.create("/atguigu", "dahaigezuishuai".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println(path);
    }

    //获取节点并监听节点变化
    @Test
    public void getDataAndWatch() throws KeeperException, InterruptedException {
        List<String> children = zkClient.getChildren("/", true);
        System.out.println("-------------");
        for (String child:children){
            System.out.println(child);
        }
        //不让进程结束
        Thread.sleep(Long.MAX_VALUE);

    }


    //判断节点是否存在
    @Test
    public void exist() throws KeeperException, InterruptedException {
        Stat stat = zkClient.exists("/atguigu", false);
        System.out.println(stat==null? "not exitss":"exists");
    }



}

监听服务器节点动态上下线

Server

import org.apache.zookeeper.*;

import java.io.IOException;

public class Server {
    private String connectString="slave2:2181,slave3:2181,slave4:2181";
    private int sessionTimeout=2000;
    private ZooKeeper zkClient;

    public static void main(String[] args) throws IOException, KeeperException, InterruptedException {

        Server server = new Server();
        //1,连接zk集群
        server.getConnect();
        
        //2,注册节点并写数据
        server.regist(args[0]);
        //3,业务逻辑,常常的睡,不让进程结束
        server.bussiness();
        
        
    }

    private void bussiness() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);
    }

    //短暂节点;带序号的节点
    private void regist(String hostname) throws KeeperException, InterruptedException {
        String path = zkClient.create("/servers/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println(hostname+" is online");
    }

    private void getConnect() throws IOException {
        zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            public void process(WatchedEvent watchedEvent) {

            }
        });
    }
}

Client

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Client {

    private String connectString="slave2:2181,slave3:2181,slave4:2181";
    private int sessionTimeout=2000;
    private ZooKeeper zkClient;

    public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
        Client client = new Client();


        //1,获取zk集群连接
        client.getConnect();

        //2,注册监听
        client.getChildren();

        //3,业务逻辑
        client.business();

    }

    private void getChildren() throws KeeperException, InterruptedException {
        List<String> children = zkClient.getChildren("/servers", true);
        ArrayList<String> hosts = new ArrayList<String>();
        for (String child: children) {
            byte[] data = zkClient.getData("/servers/" + child, false, null);
            hosts.add(new String(data));

        }
        //将所有在线数据打印到控制台
        System.out.println(hosts);
    }

    private void business() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);

    }

    private void getConnect() throws IOException {
       zkClient= new  ZooKeeper(connectString, sessionTimeout, new Watcher() {
            public void process(WatchedEvent watchedEvent) {
                try {
                    getChildren();
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343