(转载)Zookeeper实现分布式应用系统服务器上下线动态感知

来到一家新公司,要求做zookeeper注册中心的服务器上线下线的动态感知。结果把要求看成了zookepper注册中心的动态感知。哎,好尴尬呀!结果看到这篇文章我才恍然大悟。讲的也很细致!特发表一篇文章做为收藏。

我们现在有一个分布式应用系统提供服务,服务器有很多台,服务器根据我们的负载会增加或减少,这样服务器就会有动态上下线的情况。这样我们的客户端怎么知道我们的服务器有哪几天,服务器宕机我们怎么知道?

所以我们可以利用zookeeper集群实现这个需求(客户端能实时洞察到服务器上下线的变化)

实现方法

  1. 我们的服务器一启动的时候就去zookeeper去注册,zookeeper记录注册服务器的IP等信息。
  2. 去zookeeper注册的节点必须是临时节点,这样当服务器宕机下线的时候,zookeeper会把这个节点删除掉,这样才会产生事件,客户端才能监听到。
  3. 客户端一启动的时候就去连接zookeeper,然后去getChildren并且注册监听,获取当前在线服务器列表,然后选择服务器进行连接(可以获取到服务器的IP或者服务器的连接数量,这样还可以通过连接数量来进行负载均衡)
  4. 一旦服务器触发上下线的事件,就会通知到注册监听的客户端,客户端的监听器的process()方法就会调用,然后在process()方法内又可以继续getChildren并且注册监听(重新获取服务器列表并注册监听) 
    这里写图片描述

服务端代码

package cn.itcast.bigdata.zkdist;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class DistributedServer {
    private static final String connectString = "mini1:2181,mini2:2181,mini3:2181";
    private static final int sessionTimeout = 2000;
    private static final String parentNode = "/servers";

    private ZooKeeper zk = null;

    /**
     * 创建到zk的客户端连接
     * 
     * @throws Exception
     */
    public void getConnect() throws Exception {

        zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                // 收到事件通知后的回调函数(应该是我们自己的事件处理逻辑)
                System.out.println(event.getType() + "---" + event.getPath());
            }
        });

    }

    /**
     * 向zk集群注册服务器信息
     * 
     * @param hostname
     * @throws Exception
     */
    public void registerServer(String hostname) throws Exception {

        String create = zk.create(parentNode + "/server", hostname.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println(hostname + "is online.." + create);

    }

    /**
     * 业务功能
     * 
     * @throws InterruptedException
     */
    public void handleBussiness(String hostname) throws InterruptedException {
        System.out.println(hostname + "start working.....");
        Thread.sleep(Long.MAX_VALUE);
    }

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

        // 获取zk连接
        DistributedServer server = new DistributedServer();
        server.getConnect();

        // 利用zk连接注册服务器信息
        server.registerServer(args[0]);

        // 启动业务功能
        server.handleBussiness(args[0]);

    }

}

客户端代码

package cn.itcast.bigdata.zkdist;

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

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

public class DistributedClient {

    private static final String connectString = "mini1:2181,mini2:2181,mini3:2181";
    private static final int sessionTimeout = 2000;
    private static final String parentNode = "/servers";
    // 注意:加volatile的意义何在?
    private volatile List<String> serverList;
    private ZooKeeper zk = null;

    /**
     * 创建到zk的客户端连接
     * 
     * @throws Exception
     */
    public void getConnect() throws Exception {

        zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                // 收到事件通知后的回调函数(应该是我们自己的事件处理逻辑)
                try {
                    //重新更新服务器列表,并且注册了监听
                    getServerList();

                } catch (Exception e) {
                }
            }
        });

    }

    /**
     * 获取服务器信息列表
     * 
     * @throws Exception
     */
    public void getServerList() throws Exception {

        // 获取服务器子节点信息,并且对父节点进行监听
        List<String> children = zk.getChildren(parentNode, true);

        // 先创建一个局部的list来存服务器信息
        List<String> servers = new ArrayList<String>();
        for (String child : children) {
            // child只是子节点的节点名
            byte[] data = zk.getData(parentNode + "/" + child, false, null);
            servers.add(new String(data));
        }
        // 把servers赋值给成员变量serverList,已提供给各业务线程使用
        serverList = servers;

        //打印服务器列表
        System.out.println(serverList);

    }

    /**
     * 业务功能
     * 
     * @throws InterruptedException
     */
    public void handleBussiness() throws InterruptedException {
        System.out.println("client start working.....");
        Thread.sleep(Long.MAX_VALUE);
    }




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

        // 获取zk连接
        DistributedClient client = new DistributedClient();
        client.getConnect();
        // 获取servers的子节点信息(并监听),从中获取服务器信息列表
        client.getServerList();

        // 业务线程启动
        client.handleBussiness();

    }

}

注意:加volatile的意义何在? 
这里写图片描述

成员变量serverList在堆内存里面,每个线程工作的时候都有自己的工作栈空间,每个线程操作serverList的时候都会拷贝一份副本到自己的工作栈中,然后对副本进行操作,操作完之后再把副本同步回去。如果在副本同步回去的过程中,另一个线程拷贝了一份副本到自己的线程工作栈,这时候这个线程的serverList的服务器列表就会少一个。加了volatile之后,volatile保证了serverList的可见性,也就是说在每个线程中对serverList操作之后,在其他线程中都能立刻看到修改后的值。相当于这些线程都不拷贝副本而是直接去主存中操作serverList。因为现在我们getServerList()方法的线程和业务线程不在同一个线程里面;所以不加volatile的话,业务线程看到的serverList服务列表可能会不一样。 
这里写图片描述

测试

我们可以把客户端打一个jar包,把服务器打一个jar包。 
这里写图片描述

这里写图片描述

这里写图片描述

现在我们来启动server.jar,启动服务器mini1: 
这里写图片描述
然后我们再把mini2和mini3的服务器也启动了: 
这里写图片描述

现在可以开启客户端了: 
这里写图片描述

然后我们开启新的mini4服务器,我们客户端也可以查看到服务器上线: 
这里写图片描述 
关掉服务器min1之后,客户端也可以查看到服务器下线: 
这里写图片描述

这就是我们服务器的动态上下线感知。

猜你喜欢

转载自blog.csdn.net/ganjing222/article/details/82218742