使用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的最新输出内容:
可以从结果中看到获取到了最新的配置信息。
全部源码
- 使用ZooKeeper实现数据发布/订阅。
- Zookeeper 实现数据的发布和订阅
- Zookeeper实现数据的发布和订阅
- Zookeeper: 数据发布订阅
- zookeeper订阅与发布实现
- ZooKeeper 学习 (六) ZooKeeper实现数据发布订阅(即配置中心)
- Zookeeper学习(八):Zookeeper的数据发布与订阅模式
- [SQL Server] 使用事务发布与订阅实现数据同步
- SQL Server 2005使用发布、订阅实现数据同步
- zookeeper应用场景练习(数据发布/订阅)
- zookeeper(二) 数据发布与订阅(配置中心)
- ZooKeeper的典型应用场景之数据发布/订阅。
- 利用zookeeper的发布/订阅模式实现配置动态变更
- 利用zookeeper的发布/订阅模式实现配置动态变更
- 用zookeeper实现简单的发布订阅功能
- js发布订阅实现双向数据绑定
- 使用Redis实现一个订阅/发布系统
- 使用Spring Data Redis 实现订阅/发布
- 数据库悲观锁和乐观锁
- 第7节项目4-玩数字,求三个双精度实数的和,平均值,平方和以及平方和开方
- 大学计算机基础第五版习题和课后题答案
- json学习笔记------
- Lock源码解析
- 使用ZooKeeper实现数据发布/订阅。
- 机器和任务
- Unity学习周报(一)
- Android全局查找
- 求一个数的所有因子和(包括1不包括自身)
- numpy&scipy学习中碰到的疑惑
- Linux下node环境安装与配置
- 一分钟教你知道乐观锁和悲观锁的区别
- String、String.valueOf、toString 区别总结