阶段一模块四(zookeeper)

zookeeper作业

基于Zookeeper实现简易版配置中心要求实现以下功能

  1. 创建一个Web项目,将数据库连接信息交给Zookeeper配置中心管理,即:当项目Web项目启动时,从Zookeeper进行MySQL配置参数的拉取
  2. 要求项目通过数据库连接池访问MySQL(连接池可以自由选择熟悉的)
  3. 当Zookeeper配置信息变化后Web项目自动感知,正确释放之前连接池,创建新的连接池

思路分析

  1. 定义一个用于发布数据库连接信息到zookeeper的接口,用来修改数据库连接信息

  2. 项目启动时从zookeeper获取数据库连接信息,创建数据库连接池

  3. 项目要时刻监听zookeeper中数据库连接信息的变化

  4. 当发布数据库连接信息到zookeeper中时,如果连接信息有变化,项目会重新从zookeeper中获取数据库连接信息,释放之前的连接池,并创建新的数据库连接池

实现步骤

  1. 创建一个spring web项目,添加需要的依赖到pom文件。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.lagou</groupId>
    <artifactId>zookeeper-web</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.14</version>
        </dependency>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
    </dependencies>

    <!-- JVM 运行环境 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

resources目录下新建日志配置文件:log4j.properties

log4j.rootLogger=INFO, Console
# Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%-5p - %m%n
  1. 定义一个用于修改配置信息的测试类:PublishTest.java
package com.lagou;

import org.junit.Test;

import java.io.IOException;

/**
 * 测试发布信息
 */
public class PublishTest {

    @Test
    public void publish1() throws InterruptedException, IOException {
        String cfg = "driverClassName=com.mysql.jdbc.Driver\n" +
                "url=jdbc:mysql://hadoop03:3306/test?useSSL=false\n" +
                "username=root\n" +
                "password=12345678\n" +
                "initCount=5\n" +
                "maxCount=10\n" +
                "currentCount=5";
        ZkConfig.publish(cfg);
        System.out.println(ZkConfig.zkClient.readData("/mysql/con_config", true).toString());
    }

    @Test
    public void publish2() throws InterruptedException, IOException {
        String cfg = "driverClassName=com.mysql.jdbc.Driver\n" +
                "url=jdbc:mysql://hadoop03:3306/test?useSSL=false\n" +
                "username=hive\n" +
                "password=12345678\n" +
                "initCount=5\n" +
                "maxCount=10\n" +
                "currentCount=5";
        ZkConfig.publish(cfg);
        System.out.println(ZkConfig.zkClient.readData("/mysql/con_config", true).toString());
    }
}
  1. 定义一个监听器,用于监听zk中保存配置信息的节点(mysql/con_config)

Listener.java

package com.lagou;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

/**
 * 对配置文件节点添加监听器,用于监听配置文件的变化
 */
public class Listener {

    private static ZkClient zkClient = new ZkClient("hadoop01:2181, hadoop02:2181");
    private static String path = "/mysql/con_config";

    // 监听方法
    public static void monitor() {
        zkClient.subscribeDataChanges(path, new IZkDataListener() {
            public void handleDataChange(String s, Object o) throws Exception {
                System.out.println("zookeeper中数据库配置信息发生变化");
                // 尝试重新获取配置文件
                String config = zkClient.readData(path, true);
            }

            public void handleDataDeleted(String s) throws Exception {
                ZkConfig.logger.error("zk中的数据库配置信息已被删除!");
            }
        });
    }
}
  1. 数据库连接管理器:ConnManager.java
package com.lagou;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

/**
 * 数据库连接管理类
 */
public class ConnManager {

    // 数据库连接池
    public static List<Connection> pool = new LinkedList<>();
    public static String url = "";
    public static String username = "";
    public static String password = "";
    public static String driver = "";
    // 初始化的数量
    public static int initCount;
    public static int maxCount;
    public static int currentCount;
    private static volatile ConnManager instance = null;

    /**
     * 在构造对象的时候就创建好指定数量的连接
     */
    private ConnManager() {
        init();
    }

    public static void init() {
        addConnection();
    }

    public static void createPool(Properties pro) {
        // 解析配置信息,创建数据库连接池
        driver = pro.getProperty("driverClassName");
        url = pro.getProperty("url");
        username = pro.getProperty("username");
        password = pro.getProperty("password");
        initCount = Integer.parseInt(pro.getProperty("initCount"));
        maxCount = Integer.parseInt(pro.getProperty("maxCount"));
        currentCount = Integer.parseInt(pro.getProperty("currentCount"));
        init();
    }

    //获取ConnManager唯一的实例对象
    public static ConnManager getInstance() {
        if (null == instance){
            synchronized (ConnManager.class){
                if (null == instance){
                    return new ConnManager();
                }
            }

        }
        return instance;
    }

    public static void addConnection() {
        for (int i = 0; i < initCount; i++) {
            pool.add(createConnection());
        }
    }

    // 新建连接
    public static Connection createConnection() {
        Connection connection = null;

        try {
            Class.forName(driver);
            connection  = DriverManager.getConnection(url, username, password);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return connection;

    }

    // 从连接池中获取连接
    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        synchronized (pool) {
            if (pool.size() > 0) {
                System.out.println("当前连接的数量是:" + pool.size());
                return pool.get(0);
            }
            else if (currentCount < maxCount) {
                Class.forName(driver);
                Connection conn = createConnection();
                pool.add(conn);
                currentCount++;
                return conn;
                }
            else {
                throw new SQLException("当前连接数是0");
            }
        }
    }

    // 清空连接池
    public static void cleanPool() {
        for (Connection connection : pool) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        pool = new LinkedList<Connection>();
    }

    // 释放资源
    public static void release(Connection conn) {
        pool.remove(conn);
    }
}
  1. 启动时初始化数据库连接池并接听:StartUp.java
package com.lagou;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

/**
 * 启动时初始化连接池,并监听
 */

@Component
public class StartUp {

    //@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行
    @PostConstruct
    public void init() throws SQLException, ClassNotFoundException {
        String cfg = ZkConfig.zkClient.readData(ZkConfig.configPath, true);
        Properties properties = new Properties();
        try {
            properties.load(new StringReader(cfg));
            // 创建数据库连接池
            ConnManager.createPool(properties);
            // 监听节点数据变化
            Listener.monitor();
        }catch (IOException e) {
            e.printStackTrace();
        }

        // 测试连接池
        Connection connection = ConnManager.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement("select * from config");
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            System.out.println("username = " + resultSet.getString("username") +
                    ", password = " + resultSet.getString("password"));
        }
    }
}

需要在applicationContext.xml中配置自动扫描和注解驱动

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.lagou"></context:component-scan>
    <context:annotation-config></context:annotation-config>
    <mvc:default-servlet-handler></mvc:default-servlet-handler>
</beans>
  1. 将应用部署到Tomcat

  2. 执行PublishTest测试类中的publish1()方法,往zk中/mysql/con_config节点的发布数据库配置信息

@Test
    public void publish1() throws InterruptedException, IOException {
        String cfg = "driverClassName=com.mysql.jdbc.Driver\n" +
                "url=jdbc:mysql://hadoop03:3306/test?useSSL=false\n" +
                "username=root\n" +
                "password=12345678\n" +
                "initCount=5\n" +
                "maxCount=10\n" +
                "currentCount=5";
        ZkConfig.publish(cfg);
        System.out.println(ZkConfig.zkClient.readData("/mysql/con_config", true).toString());
    }
  1. 启动应用,查看状态

可以看到,成功获取到了数据库连接并查询到了数据库表中的信息。

去zookeeper查看节点内容

  1. 执行PublishTest测试类中的publish2()方法,修改zk中/webapp/dblinkcfg节点的数据:username=hive改为username=root
@Test
public void publish2() throws InterruptedException, IOException {
    String cfg = "driverClassName=com.mysql.jdbc.Driver\n" +
        "url=jdbc:mysql://hadoop03:3306/test?useSSL=false\n" +
        "username=hive\n" +
        "password=12345678\n" +
        "initCount=5\n" +
        "maxCount=10\n" +
        "currentCount=5";
    ZkConfig.publish(cfg);
    System.out.println(ZkConfig.zkClient.readData("/mysql/con_config", true).toString());
}

控制台输出

  1. 删除节点/mysql/con_config
rmr /mysql/con_config

控制台输出

posted @ 2021-01-23 10:53  凯尔哥  阅读(387)  评论(0编辑  收藏  举报