【zookeeper学习笔记】| 九、Zookeeper实现数据发布订阅

本文详细介绍了如何利用Zookeeper实现动态配置更新,包括数据发布/订阅机制,具体展示了数据库连接配置的动态切换过程,以及Apollo、Nacos和Spring Cloud Config等开源配置中心的对比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、介绍

1、所谓的数据发布/订阅,意思是发布者将数据发布到Zookeeper上的一个或一系列节点上,通过watcher机制,客户端可以监听(订阅)这些数据节点,当这些节点发生变化时,Zookeeper及时地通知客户端,从而达到动态获取数据的目的。

2、场景

配置中心

通知
通知
通知
拉去配置
拉去配置
拉去配置
修改发布
zookeeper配置中心
应用程序1
应用程序2
应用程序3

二、开源配置中心

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();
        }
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值