zookeeper分布式服务集群与负载监控讲解

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

对于zookeeper的安装,大家可以先按照zookeeper官网上的介绍进行安装,因为直接在网上找到的安装步骤可能会存在问题,所以大家应该养成一个良好的习惯,尽量在官方网站获取最权威的介绍与知识,就向我们都源码是一样的。官方的价值是最高的。

重点zookeeper是一个开源的进行协调服务的中间件,高性能,分布式。它能做什么呢?数据存储、服务注册与发现、集群管理、分布式锁等。而且zookeeper的部署支持单机和集群,具有一定的灵活性。

zookeeper的底层数据结构是树形结构,每一个节点成为数据节点,注意,节点存储的是数据,而不是文件或者其他。数据节点成为znode,它是zookeeper结构的最小组成单元。

创建节点的命令是:

  -s : 有序节点  无须节点

 -e : 临时节点 持久节点

create -s -e path data acl

节点的特性有两种:一个是临时性节点,它的生命周期与当前该节点所属的会话相关联,在会话结束后的一段时间后,临时性节点会自动删除。这里注意的一点是为什么是会话结束一段时间之后才删除节点呢?是因为设计者在设计的时候考虑的网络抖动的问题,这可能不是人为的结束会话,而是由于网络的故障或者其他的故障,当值当前的会话被迫结束,如果立即删除调zookeeper中存储的节点数据,显然,这不是我们想要的,所以,在设计的时候,考虑到网络心跳的问题,在会话结束的一段时间后,数据节点才会通过某些算法执行自动删除。在临时行节点的数据中有一个属性字段是EphemeralOwner,它存储的是该节点所属会话的会话id,是一个唯一的值。另一个是持久化节点,它在创建之后,不会随着会话的声明周期而影响自己的生命周期。当创建节点时,不写 -s -e 参数的时候,默认创建的是持久化节点。

另外需要注意的是,只有持久化节点才可以创建子节点,临时节点是不可以创建子节点的。zookeeper的树形结构在深度上是无限制的,在广度上一般也没有限制。节点名称在同一级目录下必须唯一。

通过get命令,我们可以获取到该节点的数据信息。

下面看一下这些属性都分别表示什么:

cZxid : 表示创建事务id
ctime :表是常见时间
mzxid : 表示修改事务时间 
pzxid : 只有子节点列表变更才会更新
cversion : 与乐观锁相关  任何客户端对数据库字段修改之后 对应的字段递增

下面通过程序来实现一下结构的构建与服务的调用:

目前通过java api 连接zookeeper服务端的方式有两种 : zkClient 和 curator 我们采用第一种,代码如下:

服务提供者 A 与 B 代码相同 :

package com.jd.zk;

import com.sun.xml.internal.ws.resources.ProviderApiMessages;
import org.I0Itec.zkclient.ZkClient;

import java.io.IOException;

public class ProviderA {
    private String serviceName = "serviceA";
    private final String ROOT = "/configcenter";
    public void init(){
        // 产生连接 判断根节点是否存在  不存在则创建
        String zkServer = "192.168.11.142:2181";
        ZkClient zkClient = new ZkClient(zkServer);
        if(!zkClient.exists(ROOT)){
            zkClient.createPersistent(ROOT);
        }
        // 启动服务 判断当前服务节点是否注册过
        if(!zkClient.exists(ROOT + "/" + serviceName)){
            zkClient.createPersistent(ROOT + "/" + serviceName);  // 注意是全路径
        }
        String ip = "192.128.11.130:8080";
        zkClient.createEphemeral(ROOT + "/" + serviceName + "/" + ip);
        System.out.println("providerA服务启动成功");
    }

    public static void main(String[] args) throws IOException {
        ProviderA providerA = new ProviderA();
        providerA.init();
        System.in.read();
    }
}

消费者调用服务:

package com.jd.zk;


import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 消费者 调用配置中心的地址  来访问生产者
 *  从根节点开始读取
 *
 */

public class Consumer {
    private List<String> serverList = new ArrayList<String>();
    private String serviceName = "serviceA";

    public void init() throws Exception {
        String zkServer = "192.168.11.142:2181";
        ZkClient zkClient = new ZkClient(zkServer);
        String servicePath = "/configcenter/" + serviceName;
        boolean isExist = zkClient.exists(servicePath);
        if(isExist){
            serverList = zkClient.getChildren(servicePath);
        }else{
            throw new Exception("Errow");
        }
        // 实现服务注册监听发现
        zkClient.subscribeChildChanges(servicePath, new IZkChildListener() {
            public void handleChildChange(String s, List<String> list) throws Exception {
                System.out.println("服务节点发生变化,节点的信息 : " + list);
                serverList = list;
            }
        });
    }
    // 实现负载均衡与监听  只不过这里采取的是随机负载
    public void consumer(){
        Random random = new Random();
        int i = random.nextInt(serverList.size());
        System.out.println(i);
        System.out.println("调用 :" + serverList.get(i) + "提供服务" );
    }

    public static void main(String[] args) throws Exception {
        Consumer consumer = new Consumer();
        consumer.init();
        consumer.consumer();
        System.in.read();
    }
}

先启动providerA 和 providerB ,然后执行Consumer 效果如下:

猜你喜欢

转载自blog.csdn.net/IBLiplus/article/details/85004840
今日推荐