05zookeeper场景应用-数据发布订阅

数据发布订阅

多个订阅者对象同时监听同一主题对象,当被监听的主题对象状态有变化时通知所有订阅者更新自身状态。发布方和订阅方独立封装、独立改变,当一个对象的改变需要同时改变其他对象,并且它不知道有多少个对象需要改变,可以使用发布订阅模式。

在分布式系统中的顶级应用有配置管理和服务发现。

配置管理

指集群中的机器拥有某些某些配置,并且这些配置信息需要动态的改变,那么我们可以使用发布订阅模式把配置信息做统一的管理,让这些机器订阅配置信息的改变,当配置信息有改变时这些机器得到通知并更新自己的配置。

服务发现

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

架构图

在这里插入图片描述

左边是zookeeper集群,右边是服务器集群。其中前面三个是工作服务器,第四个是管理服务器,最后一个是控制服务器。

config节点,用于配置管理。管理服务器(manage server)通过config下发配置信息,工作服务器(work server)通过订阅config节点的改变来更新自己的配置。

servers节点,用于服务发现。工作服务器(work server)在启动的时候,会在servers节点下创建一个临时节点,管理服务器(manage server)充当监听器(monitor),通过监听servers节点下子节点列表的变化来更新自己内存中工作服务器列表的信息。

控制服务器(control server)由command节点作为中介,向管理服务器(manage server)下发指令。控制服务器(control server)向command节点写入命令信息,管理服务器(manage server)通过订阅command节点数据的改变来监听并执行命令。

流程图

在这里插入图片描述

核心类

在这里插入图片描述

ServerConfig用于记录Work Server的配置信息;

ServerData用于记录Work Server的基本信息;

WorkServer对应架构图的Work Server;

ManageServer对应架构图的Manage Server;

SubscribeZkClient作为示例程序入口服务站启动Work Server和Manage Server

代码实现

/**
 * Created by zhangdd on 2019/12/16
 */
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 + '\'' +
                '}';
    }
}
/**
 * Created by zhangdd on 2019/12/16
 */
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 + '\'' +
                '}';
    }
}
/**
 * Created by zhangdd on 2019/12/16
 * <p>
 * 1.向servers节点下创建一个临时节点
 * 2.监听config节点配置信息的变化
 */
public class WorkServer {
    private ZkClient zkClient;
    private String configPath;
    private String serversPath;
    private ServerData serverData;
    private ServerConfig serverConfig;
    private IZkDataListener dataListener;

    public WorkServer(String configPath, String serversPath, ServerData serverData,
                      ZkClient zkClient, ServerConfig initConfig) {
        this.zkClient = zkClient;
        this.configPath = configPath;
        this.serversPath = serversPath;
        this.serverData = serverData;
        this.serverConfig = initConfig;
        this.dataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object data) throws Exception {
                String retJson = new String((byte[]) data);
                ServerConfig serverConfigLocal = (ServerConfig) JSON.parseObject(retJson, ServerConfig.class);
                updateConfig(serverConfigLocal);
                System.out.println("new Work server config is:" + serverConfig.toString());
            }

            @Override
            public void handleDataDeleted(String s) throws Exception {

            }
        };
    }

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

    public void stop() {
        System.out.println("work server stop...");
        zkClient.unsubscribeDataChanges(configPath, dataListener);
    }

    private void initRunning() {
        registMe();
        zkClient.subscribeDataChanges(configPath, dataListener);
    }

    private void registMe() {
        String mePath = serversPath.concat("/").concat(serverData.getAddress());

        try {
            zkClient.createEphemeral(mePath, JSON.toJSONString(serverData)
                    .getBytes());
        } catch (ZkNoNodeException e) {
            zkClient.createPersistent(serversPath, true);
            registMe();
        }
    }

    private void updateConfig(ServerConfig serverConfig) {
        this.serverConfig = serverConfig;
    }
}
/**
 * Created by zhangdd on 2019/12/16
 * <p>
 * 1.监听command节点数据变化,读取执行命令
 * 2.监听servers节点下子节点的变化,更新内存中可以使用的服务列表
 */
public class ManageServer {

    private String serversPath;
    private String commandPath;
    private String configPath;
    private ZkClient zkClient;
    private ServerConfig config;
    private IZkChildListener childListener;
    private IZkDataListener dataListener;
    private List<String> workServerList;

    public ManageServer(String serversPath, String commandPath,
                        String configPath, ZkClient zkClient, ServerConfig config) {
        this.serversPath = serversPath;
        this.commandPath = commandPath;
        this.zkClient = zkClient;
        this.config = config;
        this.configPath = configPath;

        this.childListener = new IZkChildListener() {
            @Override
            public void handleChildChange(String s, List<String> currentChilds) throws Exception {
                workServerList = currentChilds;

                System.out.println("work server list changed, new list is ");
                execList();
            }
        };
        this.dataListener=new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object data) throws Exception {
                String cmd = new String((byte[]) data);
                System.out.println("cmd:" + cmd);
                exeCmd(cmd);
            }

            @Override
            public void handleDataDeleted(String s) throws Exception {

            }
        };

    }

    private void initRunning() {
        zkClient.subscribeDataChanges(commandPath, dataListener);
        zkClient.subscribeChildChanges(serversPath, childListener);
    }

    /*
     * 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() {
        if (!zkClient.exists(configPath)) {
            try {
                zkClient.createPersistent(configPath, JSON.toJSONString(config)
                        .getBytes());
            } catch (ZkNodeExistsException e) {
                zkClient.writeData(configPath, JSON.toJSONString(config)
                        .getBytes());
            } catch (ZkNoNodeException e) {
                String parentDir = configPath.substring(0,
                        configPath.lastIndexOf('/'));
                zkClient.createPersistent(parentDir, true);
                execCreate();
            }
        }
    }

    private void execModify() {
        config.setDbUser(config.getDbUser() + "_modify");
        try {
            zkClient.writeData(configPath, JSON.toJSONString(config).getBytes());
        } catch (ZkNoNodeException e) {
            execCreate();
        }
    }

    public void start() {
        initRunning();
    }

    public void stop() {
        zkClient.unsubscribeChildChanges(serversPath, childListener);
        zkClient.unsubscribeDataChanges(commandPath, dataListener);
    }
}
public class SubscribeZkClient {

    private final static String CONNECTSTRING = "192.168.0.103:2181";


    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) {
        List<ZkClient> clients = new ArrayList<ZkClient>();
        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");

            ZkClient clientManage = new ZkClient(CONNECTSTRING, 5000, 5000,
                    new BytesPushThroughSerializer());
            manageServer = new ManageServer(SERVERS_PATH,
                    COMMAND_PATH, CONFIG_PATH, clientManage, initConfig);

            manageServer.start();

            for (int i = 0; i < CLIENT_QTY; i++) {
                ZkClient client = new ZkClient(CONNECTSTRING, 5000, 5000,
                        new BytesPushThroughSerializer());
                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();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            for (WorkServer workServer : workServers) {
                try {
                    workServer.stop();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            for (ZkClient client : clients) {
                try {
                    client.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值