使用ZooKeeper实现数据发布/订阅。

来源:互联网 发布:淘宝引流软件有用吗 编辑:程序博客网 时间:2024/05/22 04:25

        • 发布订阅模式简述
        • 使用ZooKeeper实现


发布/订阅模式简述。

这是属于一种设计模式,在此不做过多的介绍,只是给出大概的概念。

发布订阅模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。

订阅/发布模式也分为两种,分别是推模式和拉模式。

在推模式中,服务器主动将数据更新发送给所有订阅的客户端。
在拉模式中,由客户端主动发起请求来获取最新数据,客户端可以采用定时轮询的形式。

假如有多个服务器都需要连接同一个数据库,我们可以让这多个数据库读取同一个配置文件,这样如果想要更换数据库的话只需要更改配置文件,然后让服务器使用新的配置信息重新连接就好。

在这种情况下就可以使用发布/订阅模式,让多个服务器共同订阅一个目标,然后当目标将最新的配置文件发布的时候,所有的订阅者都能够收到最新的消息,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。


使用ZooKeeper实现。

还是假设有多个服务器需要同时更换数据库,可以使用发布/订阅模式来完成,使用ZooKeeper来实现。最终做到只需要简单的更改一个配置文件,从而让多个服务器都能够拿到最新的信息。

其实这也是利用了ZooKeeper的Watcher监听来完成的,先在ZooKeeper中创建一个节点,然后将数据库的配置信息放在这个节点中,让其它的服务器监听这个节点的数据变化,当该节点的内容发生变化时就重新读取。监听的方法使用Curator提供的缓存策略就可以,三种缓存策略的介绍可以看看本人之前的写的文章Curator的三种缓存。

工程目录:

这里写图片描述

  • DBConfig:这是一个配置信息类,其中有了数据库的url、driver、username、password字段。
  • dbconfig.properties:这是一个配置信息文件,存放了数据库的连接信息。
  • PublishServer:发布数据用的类,读取本地的dbconfig.properties文件,然后将其内容存放到ZooKeeper中。
  • SubscribeServer:订阅数据用来的类,从ZooKeeper中获取数据库的连接信息,如果ZooKeeper中没有的话就从本地文件dbconfig.properties中读取。
  • ZKConstants:存放一些连接ZooKeeper的常量。

看两个比较重要的类:PublishServer 和

PublishServer .java

package com.leafage.zk;import com.esotericsoftware.kryo.Kryo;import com.esotericsoftware.kryo.io.Output;import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.CuratorFrameworkFactory;import org.apache.curator.retry.ExponentialBackoffRetry;import org.apache.zookeeper.CreateMode;import java.io.*;import java.util.Properties;/** * 使用ZooKeeper发布消息 * @Author Leafage * @Date 2017/12/10 17:45 **/public class PublishServer {    private static DBConfig dbConfig;    private static CuratorFramework client;    public static void main(String[] args) {        init();        readConfig();        publishInfo();    }    /**     * 创建客户端,并且如果父节点不存在则创建父节点和配置信息的节点     */    public static void init() {        client = CuratorFrameworkFactory.builder()                .connectString(ZKConstants.zkAddress)                .sessionTimeoutMs(ZKConstants.sessionTimeout)                .retryPolicy(new ExponentialBackoffRetry(1000, 3))                .build();        client.start();        try {            if (client.checkExists().forPath(ZKConstants.parentPath) == null) {                client.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(ZKConstants.parentPath);            }            if (client.checkExists().forPath(ZKConstants.configPath) == null) {                client.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(ZKConstants.configPath,"".getBytes());            }        } catch (Exception e) {            e.printStackTrace();        }    }    /**     *  读取配置文件到一个DBConfig对象中。     */    public static void readConfig() {        BufferedReader reader = null;//加载文件流        try {            reader = new BufferedReader(new FileReader("F:\\IDEA\\zookeeper\\src\\main\\resources\\dbconfig.properties"));            Properties prop = new Properties();//创建属性操作对象            prop.load(reader);//加载流            dbConfig = new DBConfig(prop.getProperty("url"),prop.getProperty("driver"),prop.getProperty("username"),prop.getProperty("password"));        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 将DBConfig使用Kryo序列化之后发布到ZooKeeper节点中     */    public static void publishInfo() {        try {            Kryo kryo = new Kryo();            Output output = new Output(1,1024);            kryo.writeObject(output, dbConfig);            output.close();            client.setData().forPath(ZKConstants.configPath, output.getBuffer());//添加到节点中        } catch (Exception e) {            e.printStackTrace();        }    }}

首先检查父节点是否存在,不存在的话进行创建,然后创建一个配置信息节点,刚开始这个节点的内容为空的,然后读取本地的dbconfig.properties文件,将其中的信息转换为一个DBConfig类,并且使用Kryo将其序列化存放到ZooKeeper中。

SubscribeServer .java

package com.leafage.zk;import com.esotericsoftware.kryo.Kryo;import com.esotericsoftware.kryo.io.Input;import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.CuratorFrameworkFactory;import org.apache.curator.framework.recipes.cache.NodeCache;import org.apache.curator.framework.recipes.cache.NodeCacheListener;import org.apache.curator.retry.ExponentialBackoffRetry;import java.io.BufferedReader;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;import java.util.Properties;/** 从ZooKeeper中读取数据库的配置信息,并注册监听,当数据库切换时,更改数据库 * @Author Leafage * @Date 2017/12/10 19:22 **/public class SubscribeServer {    private static DBConfig dbConfig;    private static CuratorFramework client;    private static NodeCache nodeCache = null;    public static void main(String[] args) throws InterruptedException {        init();        subscribeInfo();        Thread.sleep(Integer.MAX_VALUE);    }    /**     * 初始化操作     */    public static void init() {        client = CuratorFrameworkFactory.builder()                .connectString(ZKConstants.zkAddress)                .sessionTimeoutMs(ZKConstants.sessionTimeout)                .retryPolicy(new ExponentialBackoffRetry(1000, 3))                .build();        client.start();    }    /**     *  读取本地配置文件到一个DBConfig对象中。     */    public static void readConfig() {        BufferedReader reader = null;//加载文件流        System.out.println("读取本地数据库信息。。。。。。");        try {            reader = new BufferedReader(new FileReader("F:\\IDEA\\zookeeper\\src\\main\\resources\\dbconfig.properties"));            Properties prop = new Properties();//创建属性操作对象            prop.load(reader);//加载流            dbConfig = new DBConfig(prop.getProperty("url"),prop.getProperty("driver"),prop.getProperty("username"),prop.getProperty("password"));        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }        System.out.println("本地数据库配置信息为:" + dbConfig.toString());    }    /**     * 反序列化得到数据库内容     */    public static void unSerialize() {        System.out.println("读取ZooKeeper服务器数据库信息。。。。。。");        byte[] data = new byte[0];        try {            data = client.getData().forPath(ZKConstants.configPath);        } catch (Exception e) {            e.printStackTrace();        }        Kryo kryo = new Kryo();        Input input = new Input(data);        dbConfig = kryo.readObject(input, DBConfig.class);        input.close();        System.out.println("ZooKeeper中的数据为:" + dbConfig.toString());    }    /**     * 订阅ZooKeeper中的信息,也就是设置监听,如果ZooKeeper中没有对应的信息的话,就读取本地的数据库信息     */    public static void subscribeInfo() {        nodeCache = new NodeCache(client, ZKConstants.configPath);        try {            nodeCache.start(true);            if (nodeCache.getCurrentData() != null) {                if (!(new String(nodeCache.getCurrentData().getData()).equals(""))) {                    unSerialize();                } else {                    readConfig();                }            } else {//否则读取本地文件                readConfig();            }            nodeCache.getListenable().addListener(new NodeCacheListener() {                @Override                public void nodeChanged() throws Exception {                    System.out.println("数据库节点信息发生变化,读取新的数据库信息!");                    unSerialize();//反序列化得到信息                }            });        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 取消订阅     */    public static void unSubscribeInfo() {        if (nodeCache != null) {            try {                System.out.println("取消订阅!");                nodeCache.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}

使用NodeCache缓存进行监听策略,并且尝试在缓存开始的时候就获取到此时ZooKeeper中的配置信息,如果获取到的内容为空的话就读取本地的配置信息文件。添加监听,如果节点信息发生改变的话就重新获取最新的配置信息。

运行测试:

首先运行三个SubscribeServer,因为此时ZooKeeper中还没有用来存放配置信息的节点,所以会读取本地的配置信息并进行监听:

SubscribeServer的输出内容:

这里写图片描述

然后运行PublishServer,这样会在ZooKeeper中创建节点,所以其他三个SubscribeServer都会得到最新的数据信息:

SubscribeServer的最新输出内容:

这里写图片描述

当我们需要“切换数据库”的时候,只需要把dbconfig.properties中的配置信息进行修改,然后重新运行PublishServer,就可以把最新的内容更新到ZooKeeper中,从而让其他三个SubscribeServer得到最新的配置信息:

将dbconfig.properties修改,将原来的mysql换成oracle:

这里写图片描述

重新运行PublishServer之后SubscribeServer的最新输出内容:

这里写图片描述

可以从结果中看到获取到了最新的配置信息。

全部源码

原创粉丝点击