一、介绍
1、所谓的数据发布/订阅,意思是发布者将数据发布到Zookeeper上的一个或一系列节点上,通过watcher机制,客户端可以监听(订阅)这些数据节点,当这些节点发生变化时,Zookeeper及时地通知客户端,从而达到动态获取数据的目的。
2、场景
配置中心
二、开源配置中心
1、Ctrip Apollo
github地址:
介绍:Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
2、Nacos
github地址:Nacos
介绍:Nacos是阿里最近才开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos致力于帮助您发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
3、Spring Cloud Config
github地址:Spring Cloud Config
介绍:Spring Cloud Config是一个基于http协议的远程配置实现方式,通过统一的配置管理服务器进行配置管理,客户端通过https协议主动的拉取服务的的配置信息,完成配置获取。
4、Disconf
github地址:disconf
介绍:专注于各种「分布式系统配置管理」的「通用组件」和「通用平台」,提供统一的「配置管理服务」。主要目标是部署极其简单、部署动态化、统一管理、一个jar包,到处运行。
三Zookeeper实现简单配置中心
1、编写一个数据库切换的配置中
2、描述:就是JDBC连接MySQL需要用的连接信息。这些连接信息将转化为JSON字符串,保存在Zookeeper上的一个节点中;应用程序(通过线程模拟的)从Zookeeper中读取这些配置信息,然后查询数据库;当修改数据库连接信息时(切换数据库),应用程序能及时的拉取新的连接信息,使用新的连接查询数据库。
3、ZKUtils工具类
package com.hao.demo.zookeeper.publishsubscribe;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
/**
* @author haojunhu
* @date 2020-06-07
* 客户端获取工具类
*/
public class ZKUtils {
private static final String zkServerIps = "localhost:2181";
public static synchronized CuratorFramework getClient() {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zkServerIps)
.sessionTimeoutMs(6000)
.connectionTimeoutMs(3000)
//.namespace("LeaderLatchTest")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
return client;
}
}
4、MysqlConfig 配置类
package com.hao.demo.zookeeper.publishsubscribe;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author haojunhu
* @date 2020-06-07
*/
@AllArgsConstructor
@Data
public class MysqlConfig {
private String url;
private String driver;
private String username;
private String password;
}
5、ConfigCenterTest 测试类
package com.hao.demo.zookeeper.publishsubscribe;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import java.sql.*;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
/**
* @author haojunhu
* @date 2020-06-07
* 配置中心示例,模拟数据库切换
*/
@Slf4j
public class ConfigCenterTest {
// test 数据库的 test1表
private static final MysqlConfig mysqlConfig_1 = new MysqlConfig("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false",
"com.mysql.jdbc.Driver", "root", "123456");
// test 数据库的 test1表
private static final MysqlConfig mysqlConfig_2 = new MysqlConfig("jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=false",
"com.mysql.jdbc.Driver", "root", "123456");
// 存储mysql配置信息的节点路径
private static final String configPath = "/testZK/jdbc/mysql";
private static final Integer clientNums = 3;
private static CountDownLatch countDownLatch = new CountDownLatch(clientNums);
public static void main(String[] args) throws Exception {
// 最开始时设置Mysql配置信息为mysqlConfig_1
setMysqlConfig(mysqlConfig_1);
// 启动 clientNums 个线程,模拟分布式系统中的节点
// 从Zookeeper中获取Mysql的配置信息,查询数据
for (int i = 0; i < clientNums; i++) {
String clientName = "client#" + i;
new Thread(() -> {
CuratorFramework client = ZKUtils.getClient();
client.start();
try {
Stat stat = new Stat();
// 如果要监听多个子节点则应该使用PathChildrenCache
final NodeCache nodeCache = new NodeCache(client, configPath, false);
nodeCache.start(true); // 表示启动时立即从Zookeeper上获取节点
byte[] nodeData = nodeCache.getCurrentData().getData();
MysqlConfig mysqlConfig = JSON.parseObject(new String(nodeData), MysqlConfig.class);
queryMysql(clientName, mysqlConfig); // 查询数据
// nodeCache.getListenable().addListener(new NodeCacheListener() {
// @Override
// public void nodeChanged() throws Exception {
// byte[] newData = nodeCache.getCurrentData().getData();
// MysqlConfig newMysqlConfig = JSON.parseObject(new String(newData), MysqlConfig.class);
// }
// });
nodeCache.getListenable().addListener(() -> {
byte[] newData = nodeCache.getCurrentData().getData();
MysqlConfig newMysqlConfig = JSON.parseObject(new String(newData), MysqlConfig.class);
});
Thread.sleep(20 * 1000); // 睡眠20s
} catch (Exception e) {
log.info("e=>{}", e.getMessage());
} finally {
client.close();
countDownLatch.countDown();
}
}).start();
}
Thread.sleep(10 * 1000);
log.info("===> 10秒钟后将MySQL配置信息修改为 mysqlConfig_2===>");
setMysqlConfig(mysqlConfig_2);
countDownLatch.await();
}
/**
* 初始化配置, 最初开始的时候Mysql 配置为mysqlConfig_1
*
* @param config
* @throws Exception
*/
public static void setMysqlConfig(MysqlConfig config) throws Exception {
CuratorFramework client = ZKUtils.getClient();
client.start();
String mysqlConfigStr = JSON.toJSONString(config);
Stat stat = client.checkExists().forPath(configPath);
if (Objects.nonNull(stat)) {
Stat resultStat = client.setData().forPath(configPath, mysqlConfigStr.getBytes());
log.info("node=>{} is exist, update data is data=>{}", configPath, mysqlConfigStr);
} else {
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(configPath, mysqlConfigStr.getBytes());
}
client.close();
}
/**
* 通过配置信息,查询Mysql数据库
*
* @param clientName
* @param mysqlConfig
* @throws ClassNotFoundException
* @throws SQLException
*/
public static synchronized void queryMysql(String clientName, MysqlConfig mysqlConfig) throws ClassNotFoundException, SQLException {
log.info("{} 查询Mysql数据,使用的Mysql配置信息为:{}", clientName, mysqlConfig);
Class.forName(mysqlConfig.getDriver());
Connection connection = DriverManager.getConnection(mysqlConfig.getUrl(), mysqlConfig.getUsername(), mysqlConfig.getPassword());
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from test1");
while (resultSet.next()) {
log.info("id=>{}, name=>{}, age=>{}", resultSet.getString(1), resultSet.getString(2),
resultSet.getString(3));
resultSet.close();
statement.close();
connection.close();
}
}
}