ZooKeeper - 数据发布/订阅

数据发布/订阅

发布/订阅模式是一对多的关系,多个订阅者对象同时监听某一主题对象,这个主题对象在自身状态发生变化时会通知所有的订阅者对象。使它们能自动的更新自己的状态。发布/订阅可以使得发布方和订阅方独立封装、独立改变。当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象需要改变时可以使用发布/订阅模式。发布/订阅模式在分布式系统中的典型应用有配置管理和服务发现、注册。
  
配置管理是指如果集群中的机器拥有某些相同的配置并且这些配置信息需要动态的改变,我们可以使用发布/订阅模式把配置做统一集中管理,让这些机器格子各自订阅配置信息的改变,当配置发生改变时,这些机器就可以得到通知并更新为最新的配置。

服务发现、注册是指对集群中的服务上下线做统一管理。每个工作服务器都可以作为数据的发布方向集群注册自己的基本信息,而让某些监控服务器作为订阅方,订阅工作服务器的基本信息,当工作服务器的基本信息发生改变如上下线、服务器角色或服务范围变更,监控服务器可以得到通知并响应这些变化。

配置管理

所谓的配置中心,顾名思义就是发布者将数据发布到 ZooKeeper 的一个或一系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
发布/订阅系统一般有两种设计模式,分别是推(Push)模式和拉(Pull)模式。
推模式 - 服务端主动将数据更新发送给所有订阅的客户端。
拉模式 - 客户端通过采用定时轮询拉取。
ZooKeeper采用的是推拉相结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知之后,需要主动到服务端获取最新的数据。如果将配置信息存放到ZK上进行集中管理,那么通常情况下,应用在启动的时候会主动到ZK服务器上进行一次配置信息的获取,同时,在指定上注册一个Watcher监听,这样一来,但凡配置信息发生变更,服务器都会实时通知所有订阅的客户端,从而达到实时获取最新配置信息的目的。

下面我们通过一个“配置管理”的实际案例来展示ZK在“数据发布/订阅”场景下的使用方式。
在我们平常的应用系统开发中,经常会碰到这样的需求:系统中需要使用一些通用的配置信息,例如机器列表信息、运行时的开关配置、数据库的配置信息等。这些全局配置信息通常具备以下特性:
  1)、数据量通常比较小
  2)、数据内容在运行时会发生变化
  3)、集群中各机器共享、配置一致
对于这类配置信息,一般的做法通常可以选择将其存储的本地配置文件或是内存变量中。无论采取哪种配置都可以实现相应的操作。但是一旦遇到集群规模比较大的情况的话,两种方式就不再可取。而我们还需要能够快速的做到全部配置信息的变更,同时希望变更成本足够小,因此我们需要一种更为分布式的解决方案。

接下来我们以“数据库切换”的应用场景展开,看看如何使用ZK来实现配置管理。

配置存储
在进行配置管理之前,首先我们需要将初始化配置存储到ZK上去,一般情况下,我们可以在ZK上选取一个数据节点用于配置的存储,我们将需要集中管理的配置信息写入到该数据节点中去。

配置获取
集群中每台机器在启动初始化阶段,首先会从上面提到的ZK的配置节点上读取数据库信息,同时,客户端还需要在该配置节点上注册一个数据变更的Watcher监听,一旦发生节点数据变更,所有订阅的客户端都能够获取数据变更通知。  

配置变更
在系统运行过程中,可能会出现需要进行书局切换的情况,这个时候就需要进行配置变更。借助ZK,我们只需要对ZK上配置节点的内容进行更新,ZK就能够帮我们将数据变更的通知发送到各个客户端,每个客户端在接收到这个变更通知后,就可以重新进行最新数据的获取。

服务发现、注册

监视器监听服务注册节点变动,当有服务程序注册至ZK节点,ZK能将该事件通知到监视器,监视器获取最新的工作服务器列表。

综合例子

架构:

Manage Server 程序主体流程:

Work Server 程序主体流程:

 系统的核心类:

程序代码实现

package xyx.tuny.subscribe;

public class ServerConfig {

    private String dbUrl;
    private String dbPwd;
    private String dbUser;

    public String getDbUrl() {
        return dbUrl;
    }

    public void setDbUrl(String dbUrl) {
        this.dbUrl = dbUrl;
    }

    public String getDbPwd() {
        return dbPwd;
    }

    public void setDbPwd(String dbPwd) {
        this.dbPwd = dbPwd;
    }

    public String getDbUser() {
        return dbUser;
    }

    public void setDbUser(String dbUser) {
        this.dbUser = dbUser;
    }

    @Override
    public String toString() {
        return "ServerConfig [dbUrl=" + dbUrl + ", dbPwd=" + dbPwd + ", dbUser=" + dbUser + "]";
    }

}
View Code
package xyx.tuny.subscribe;

public class ServerData {

    private String address;
    private Integer id;
    private String name;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "ServerData [address=" + address + ", id=" + id + ", name=" + name + "]";
    }
}
View Code
package xyx.tuny.subscribe;

import com.alibaba.fastjson.JSON;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.zookeeper.CreateMode;

public class WorkServer {

    private CuratorFramework client;
    private String configPath;
    private String serversPath;
    private ServerData serverData;
    private ServerConfig serverConfig;
    private NodeCacheListener dataListener;
    private NodeCache cache;

    public WorkServer(String configPath, String serversPath, ServerData serverData, CuratorFramework client, ServerConfig initConfig) {
        this.client = client;
        this.configPath = configPath;
        this.serversPath = serversPath;
        this.serverData = serverData;
        this.serverConfig = initConfig;

        this.cache = new NodeCache(client,configPath,false);
        this.dataListener = new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                String retJson = new String(cache.getCurrentData().getData());
                ServerConfig serverConfigLocal = (ServerConfig) JSON.parseObject(retJson, ServerConfig.class);
                updateConfig(serverConfigLocal);
                System.out.println("new Work server config is:" + serverConfig.toString());
            }
        };


    }

    public void start() {
        System.out.println("work server start...");
        client.start();
        initRunning();
    }

    public void stop() {
        System.out.println("work server stop...");
        cache.getListenable().removeListener(dataListener);
    }

    private void initRunning() {
        registMe();
        cache.getListenable().addListener(dataListener);
    }

    private void registMe() {
        String mePath = serversPath.concat("/").concat(serverData.getAddress());
        System.out.println("registe workserver:" + mePath);
        try {
            client.create().withMode(CreateMode.PERSISTENT).forPath(mePath,JSON.toJSONString(serverData).getBytes());
        } catch (Exception e) {
            try {
                client.create().withMode(CreateMode.PERSISTENT).forPath(serversPath);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

    private void updateConfig(ServerConfig serverConfig) {
        this.serverConfig = serverConfig;
    }
}
package xyx.tuny.subscribe;

import java.util.List;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import com.alibaba.fastjson.JSON;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;

public class ManageServer {

    private String serversPath;
    private String commandPath;
    private String configPath;
    private CuratorFramework client;
    private ServerConfig config;
    private PathChildrenCacheListener childListener;
    private NodeCacheListener dataListener;
    private List<String> workServerList;
    private NodeCache cache;
    private PathChildrenCache childCache;

    public ManageServer(final String serversPath, String commandPath,
                        String configPath, CuratorFramework client, ServerConfig config) {
        this.serversPath = serversPath;
        this.commandPath = commandPath;
        this.client = client;
        this.config = config;
        this.configPath = configPath;

    }

    private void initRunning() {
        this.client.start();

        this.childCache = new PathChildrenCache(client, serversPath, true);
        try {
            childCache.start(StartMode.POST_INITIALIZED_EVENT);
        } catch (Exception e) {
            e.printStackTrace();
        }
        this.childListener = new PathChildrenCacheListener() {
            public void childEvent(CuratorFramework client,
                                   PathChildrenCacheEvent event) throws Exception {
                switch(event.getType()){
                    case CHILD_ADDED:
                        workServerList = client.getChildren().forPath(serversPath);
                        System.out.println("work server list changed add, new list is ");
                        execList();
                        break;
                    case CHILD_REMOVED:
                        workServerList = client.getChildren().forPath(serversPath);
                        System.out.println("work server list changed remove, new list is ");
                        execList();
                        break;
                }
            }
        };
        this.childCache.getListenable().addListener(childListener);

        this.cache = new NodeCache(client,commandPath,false);

        this.dataListener = new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                String cmd = new String(cache.getCurrentData().getData());
                System.out.println("cmd:" + cmd);
                exeCmd(cmd);
            }
        };
        this.cache.getListenable().addListener(dataListener);
        try {
            cache.start();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /*
     * 1: list 2: create 3: modify
     */
    private void exeCmd(String cmdType) {
        if ("list".equals(cmdType)) {
            execList();

        } else if ("create".equals(cmdType)) {
            execCreate();
        } else if ("modify".equals(cmdType)) {
            execModify();
        } else {
            System.out.println("error command!" + cmdType);
        }
    }

    private void execList() {
        System.out.println(workServerList.toString());
    }

    private void execCreate() {
            try {
                if (client.checkExists().forPath(configPath) == null) {
                    client.create().withMode(CreateMode.PERSISTENT).forPath(configPath, JSON.toJSONString(config).getBytes());
                    client.setData().forPath(configPath, JSON.toJSONString(config).getBytes());
                }
            } catch (Exception e) {
                String parentDir = configPath.substring(0, configPath.lastIndexOf('/'));
                try {
                    client.create().withMode(CreateMode.PERSISTENT).forPath(parentDir);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
                execCreate();
            }

    }

    private void execModify() {
        config.setDbUser(config.getDbUser() + "_modify");

        try {
            client.setData().forPath(configPath, JSON.toJSONString(config).getBytes());
        } catch (Exception e) {
            execCreate();
        }
    }

    public void start() {
        initRunning();
    }

    public void stop() {
        cache.getListenable().removeListener(dataListener);
        childCache.getListenable().removeListener(childListener);
    }
}
package xyx.tuny.subscribe;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class SubscribeClient {

    private static final int CLIENT_QTY = 5;

    private static final String CONFIG_PATH = "/config";
    private static final String COMMAND_PATH = "/command";
    private static final String SERVERS_PATH = "/servers";

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

        List<CuratorFramework> clients = new ArrayList<CuratorFramework>();
        List<WorkServer> workServers = new ArrayList<WorkServer>();
        ManageServer manageServer = null;

        try {
            ServerConfig initConfig = new ServerConfig();
            initConfig.setDbPwd("123456");
            initConfig.setDbUrl("jdbc:mysql://localhost:3306/mydb");
            initConfig.setDbUser("root");
            CuratorFramework clientManage = CuratorFrameworkFactory.builder()
                    .connectString("127.0.0.1:2181")
                    .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                    .sessionTimeoutMs(5000)
                    .build();
            manageServer = new ManageServer(SERVERS_PATH, COMMAND_PATH, CONFIG_PATH, clientManage, initConfig);
            manageServer.start();

            for (int i = 0; i < CLIENT_QTY; ++i) {
                CuratorFramework client = CuratorFrameworkFactory.builder()
                        .connectString("127.0.0.1:2181")
                        .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                        .sessionTimeoutMs(5000)
                        .build();
                clients.add(client);
                ServerData serverData = new ServerData();
                serverData.setId(i);
                serverData.setName("WorkServer#" + i);
                serverData.setAddress("192.168.1." + i);

                WorkServer workServer = new WorkServer(CONFIG_PATH, SERVERS_PATH, serverData, client, initConfig);
                workServers.add(workServer);
                workServer.start();
            }
            System.out.println("敲回车键退出!\n");
            new BufferedReader(new InputStreamReader(System.in)).readLine();

        } finally {
            System.out.println("Shutting down...");

            for (WorkServer workServer : workServers) {
                try {
                    workServer.stop();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            for (CuratorFramework client : clients) {
                try {
                    client.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
View Code

启动测试:

在zookeeper客户端上输出命令

create /command list

输出变化:

 

转载:https://www.cnblogs.com/sky-sql/p/6685531.html

posted on 2018-08-20 22:25  TrustNature  阅读(44)  评论(0编辑  收藏  举报