阶段一模块四(zookeeper)
zookeeper作业
基于Zookeeper实现简易版配置中心要求实现以下功能
- 创建一个Web项目,将数据库连接信息交给Zookeeper配置中心管理,即:当项目Web项目启动时,从Zookeeper进行MySQL配置参数的拉取
- 要求项目通过数据库连接池访问MySQL(连接池可以自由选择熟悉的)
- 当Zookeeper配置信息变化后Web项目自动感知,正确释放之前连接池,创建新的连接池
思路分析
-
定义一个用于发布数据库连接信息到zookeeper的接口,用来修改数据库连接信息
-
项目启动时从zookeeper获取数据库连接信息,创建数据库连接池
-
项目要时刻监听zookeeper中数据库连接信息的变化
-
当发布数据库连接信息到zookeeper中时,如果连接信息有变化,项目会重新从zookeeper中获取数据库连接信息,释放之前的连接池,并创建新的数据库连接池
实现步骤
- 创建一个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
- 定义一个用于修改配置信息的测试类: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());
}
}
- 定义一个监听器,用于监听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中的数据库配置信息已被删除!");
}
});
}
}
- 数据库连接管理器: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);
}
}
- 启动时初始化数据库连接池并接听: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>
-
将应用部署到Tomcat
-
执行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());
}
- 启动应用,查看状态
可以看到,成功获取到了数据库连接并查询到了数据库表中的信息。
去zookeeper查看节点内容
- 执行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());
}
控制台输出
- 删除节点/mysql/con_config
rmr /mysql/con_config
控制台输出