ZooKeeper - 分布式服务管理

概述

  • ZooKeeper是一个分布式的,开源的分布式应用程序协调服务,是Hadoop、Hbase、kafka、dubbo等重要组件。
  • ZooKeeper是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。ZooKeeper包含一个简单的原语集,提供Java和C的接口。
  • 特点
    • 简单:ZooKeeper的核心是一个精简的文件系统 ,它支持一些简单的操作和一些抽象操作
    • 丰富:ZooKeeper的操作是很丰富的,可实现一些协调数据结构和协议。
    • 高可靠:ZooKeeper支持集群模式,可以很容易的解决单点故障问题
    • 松耦合交互:不同进程间的交互不需要了解彼此,甚至可以不必同时存在,某进程在ZooKeeper中留下消息后,该进程结束后其它进程还可以读这条消息
    • 资源库:ZooKeeper实现了一个关于通用协调模式的开源共享存储库,能使开发者免于编写这类通用协议

数据结构

  • ZooKeeper的角色
    • leader(领导者):负责进行投票的发起和决议,更新系统状态
    • learner (学习者):包括跟随者(follower)和观察者(observer),follower用于接受客户端请求并向客户端返回结果,在选举过程中参与投票。Observer可以接受客户端连接,将写请求转发给leader,但observer不参与投票过程,只同步leader的状态,observe r的目的是为了扩展系统,提高读取速度
    • 客户端(client):请求发起方
  • ZooKeeper数据模型
    • 层次化的目录结构,命名符合常规文件系统规范
    • 每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识,节点znode可以包含数据和子节点,但是EPHEMERAL类型的节点不能有子节点
    • znode中的数据可以有多个版本,比如某一个路径下存有多个数据版本,那么查询这个路径下的数据就需要带上版本
    • 客户端应用可以在节点上设置监视器,节点不支持部分读写,而是一次性完整读写
  • ZooKeeper的节点
    • zookeeper中包含两个类型的节点
      • 临时节点(ephemeral):在节点客户端会话结束时,ephemeral节点自动删除,没有子节点
      • 持久节点(persistent):不依赖与客户端会话,只有当客户端明确要删除该persistent节点时才会被删除
    • znode的类型在创建时确定并且之后不能再修改,znode默认指定类型是临时节点
    • ZooKeeper的客户端和服务器通信采用长连接方式 ,每个客户端和服务器通过心跳来保持连接,这个连接状态称之为session ,如果znode是临时节点,这个seesion失效,znode也就删除了

应用场景

  • 提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。
  • 统一命名服务
    • 在分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务
      • 类似于域名与ip之间对应关系,ip不容易记住,而域名容易记住
      • 通过名称来获取资源或服务的地址,提供者等信息
  • 统一配置管理
    • 分布式环境下,配置文件管理和同步是一个常见问题
      • 一个集群中,所有节点的配置信息是一致的,比如 Hadoop 集群
      • 对配置文件修改后,希望能够快速同步到各个节点上
    • 配置管理可交由ZooKeeper实现
      • 可将配置信息写入ZooKeeper上的一个Znode
      • 各个节点监听这个Znode
      • 一旦Znode中的数据被修改,ZooKeeper将通知各个节点
  • 统一集群管理
    • 分布式环境中,实时掌握每个节点的状态是必要的
      • 可根据节点实时状态做出一些调整
    • 可交由ZooKeeper实现
      • 可将节点信息写入ZooKeeper上的一个Znode
      • 监听这个Znode可获取它的实时状态变化
    • 典型应用
      • HBase中Master状态监控与选举
  • 服务器动态上下线
    • 客户端能实时洞察到服务器上下线的变化
  • 软负载均衡

ZooKeeper 内部原理

ZooKeeper中的选举机制

  • 服务器的ID
    • 分别1,2,3,编号越大在选择算法中的权重越大
  • 数据的ID
    • 服务器中存放的最大数据ID,值越大说明数据越新,在选举算法中数据越新权重越大
  • 编辑时钟
    • 投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断
  • 选举状态
    • LOOKING,竞选状态
    • FOLLOWING,随从状态,同步leader状态,参与投票
    • OBSERVING,观察状态,同步leader状态,不参与投票
    • LEADING,领导者状态
  • 选举信息的内容
    • 在投票完成后,需要将投票信息发送给集群中的所有服务器,它包含如下内容
      • 服务器ID
      • 数据ID
      • 逻辑时钟
      • 选举状态
  • 选举机制的结果:zk启动之后通过选举机制,来选举出来一个leader
  • 半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器
  • Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的
  • 选举过程举例
    • 服务器1启动,此时只有它一台服务器启动了,它发出去的报文没有任何响应,所以它的选举状态一直是LOOKING状态
    • 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1、2还是继续保持LOOKING状态
    • 服务器3启动,根据前面的理论分析,服务器3成为服务器1、2、3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的Leader
    • 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1、2、3、4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了
    • 服务器5启动,同4一样当小弟
      ZooKeeper - 分布式服务管理_第1张图片

Stat结构体

  • czxid:创建节点的事务zxid
    • 每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
  • ctime:znode被创建的毫秒数(从1970年开始)
  • mzxid:znode最后更新的事务zxid
  • mtime:znode最后修改的毫秒数(从1970年开始)
  • pZxid:znode最后更新的子节点zxid
  • cversion:znode子节点变化号,znode子节点修改次数
  • dataversion:znode数据变化号
  • aclVersion:znode访问控制列表的变化号
  • ephemeralOwner:如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0
  • dataLength:znode的数据长度
  • numChildren:znode子节点数量

监听器原理

ZooKeeper - 分布式服务管理_第2张图片

  • 监听原理
    • 首先要有一个main()线程
    • 在main线程中创建Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connet),一个负责监听(listener)
    • 通过connect线程将注册的监听事件发送给Zookeeper
    • 在Zookeeper的注册监听器列表中将注册的监听事件添加到列表中
    • Zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程
    • listener线程内部调用了process()方法
  • 常见的监听
    • 监听节点数据的变化:get path [watch]
    • 监听子节点增减的变化:ls path [watch]

写数据流程

  • Client 向 ZooKeeper 的 Server1 上写数据,发送一个写请求
  • 如果Server1不是Leader,那么Server1 会把接受到的请求进一步转发给Leader,因为每个ZooKeeper的Server里面有一个是Leader。这个Leader 会将写请求广播给各个Server,比如Server1和Server2,各个Server写成功后就会通知Leader
  • 当Leader收到大多数 Server 数据写成功了,那么就说明数据写成功了。如果这里三个节点的话,只要有两个节点数据写成功了,那么就认为数据写成功了。写成功之后,Leader会告诉Server1数据写成功了
  • Server1会进一步通知 Client 数据写成功了,这时就认为整个写操作成功。ZooKeeper 整个写数据流程就是这样的
    ZooKeeper - 分布式服务管理_第3张图片

Zookeeper实战

分布式安装部署

  • 集群规划:在hadoop102、hadoop103和hadoop104三个节点上部署Zookeeper
  • 解压安装
    • 解压Zookeeper安装包到/opt/module/目录下
    • 同步/opt/module/zookeeper-3.4.10目录内容到hadoop103、hadoop104:xsync zookeeper-3.4.10/
  • 配置服务器编号
    • 在/opt/module/zookeeper-3.4.10/这个目录下创建zkData
    • 在/opt/module/zookeeper-3.4.10/zkData目录下创建一个myid的文件
    • 编辑myid文件,在文件中添加与server对应的编号
    • 拷贝配置好的zookeeper到其他机器上,并分别在hadoop102、hadoop103上修改myid文件中内容为3、4
  • 配置zoo.cfg文件
    • 重命名/opt/module/zookeeper-3.4.10/conf这个目录下的zoo_sample.cfg为zoo.cfg
    • 打开zoo.cfg文件,修改数据存储路径配置,dataDir=/opt/module/zookeeper-3.4.10/zkData
      • 增加如下配置
    #######################cluster##########################
    server.2=hadoop102:2888:3888
    server.3=hadoop103:2888:3888
    server.4=hadoop104:2888:3888
    
    • 同步zoo.cfg配置文件:xsync zoo.cfg
    • 配置参数解读:Server.A=B:C:D
      • A是一个数字,表示这个是第几号服务器
      • B是这个服务器的ip地址
      • C是这个服务器与集群中的Leader服务器交换信息的端口
      • D是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口
    • 集群模式下配置一个文件myid,这个文件在dataDir目录下,这个文件里面有一个数据就是A的值,Zookeeper启动时读取此文件,拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server
  • 集群操作
    • 在/opt/module/zookeeper-3.4.10/zkData目录下创建一个myid的文件
    • 编辑myid文件:vim myid
    • 拷贝配置好的zookeeper到其他机器上
    • 分别启动zookeeper
    [gc@hadoop102 zookeeper-3.4.10]$ bin/zkServer.sh start
    [gc@hadoop103 zookeeper-3.4.10]$ bin/zkServer.sh start
    [gc@hadoop104 zookeeper-3.4.10]$ bin/zkServer.sh start
    

客户端命令行操作

  • bin/zkCli.sh:启动客户端,例:zkCli.sh -server mini05:2181
  • help:显示所有操作命令
  • ls path [watch]:查看当前znode中所包含的内容
  • ls2 path [watch]:查看当前节点数据并能看到更新次数等数据
  • create -s 路径 节点名:普通创建
    • -s 含有序列
    • -e 临时(重启或者超时消失)
  • get path [watch]:获得节点的值
  • set:设置节点的具体值
  • stat:查看节点状态
  • delete:删除节点
  • rmr:递归删除节点
  • quit:退出当前客户端
  • setquota -b 长度配额:设置节点长度配额
  • setquota -n 数量配额:设置节点数量配额
  • listquoat path:列出配额
  • delquota path:删除配额
    • zookeeper中的配额管理超出配额的大小之后依然可以进行操作

zookeeper原生的API操作

public class ZookeeperTest {

    private ZooKeeper zooKeeper;
    
    /**
     * 创建ZooKeeper客户端
     */
    @Before
    public void init() throws Exception {
        String connStr = "mini03:2181,mini04:2181,mini05:2181";
        zooKeeper = new ZooKeeper(connStr, 3000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("watch..." + watchedEvent.getType());
            }
        });
    }

    /**
     * 创建节点
     */
    @Test
    public void testCreateZNode() throws Exception {
        String path = "/test01";
        zooKeeper.exists(path, true);
        String ret = zooKeeper.create(path, "HELLO2".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println(ret);
    }
    
    /**
     * 获取节点
     */
    @Test
	public void getChildrenZNode() throws Exception {
		List children = zooKeeper.getChildren("/", true);
		for (String child : children) {
			System.out.println(child);
		}
		// 延时阻塞
		Thread.sleep(Long.MAX_VALUE);
	}
	
    /**
     * 判断节点
     */
	@Test
	public void exist() throws Exception {
		Stat stat = zkClient.exists("/eclipse", false);
		System.out.println(stat == null ? "not exist" : "exist");
	}

    @Test
    public void testSetZnode() throws KeeperException, InterruptedException {
        zooKeeper.setData("/test02", "mini02".getBytes(), 1);
    }

    @Test
    public void testGetZnode() throws KeeperException, InterruptedException {
        byte[] data = zooKeeper.getData("/test02", true, null);
        System.out.println(new String(data, 0, data.length));
    }

    @Test
    public void testDeleteZnode() throws KeeperException, InterruptedException {
        zooKeeper.delete("/test02", -1);
    }

    @After
    public void destory() throws Exception {
        zooKeeper.close();
    }
}

你可能感兴趣的:(java框架,zookeeper)