文章内容输出来源:拉勾教育大数据高薪训练营
需求:基于Zookeeper实现简易版配置中心
要求实现以下功能:
创建一个Web项目,将数据库连接信息交给Zookeeper配置中心管理,即:当项目Web项目启动时,从Zookeeper进行MySQL配置参数的拉取
要求项目通过数据库连接池访问MySQL(连接池可以自由选择熟悉的)
当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
2.定义一个用于修改配置信息的接口:Publisher.java
package com.lagou.zk;
import org.I0Itec.zkclient.ZkClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Author wanglitao
* @Date 2020/8/2 3:35 下午
* @Version 1.0
* @Description 修改数据库配置信息接口
*/
public class Publisher {
Logger logger = LoggerFactory.getLogger(Publisher.class);
ZkClient zkClient = null;
public static final String serverstring = "linux121:2181,linux122:2181,linux123:2181";
//
public static final String path = "/webapp/dblinkcfg";
/**
* 获取zk连接对象
*/
private void connectZk() {
zkClient = new ZkClient(serverstring);
if (!zkClient.exists(path)) {
// 创建保存数据库连接信息的节点
zkClient.createPersistent(path, true);
}
}
/**
* 发布数据库配置信息
*
* @param cfgInfo 连接信息
*/
public void publish(String cfgInfo) {
connectZk();
zkClient.writeData(path, cfgInfo);
logger.info("发布数据库连接信息成功:" + cfgInfo);
}
}
3.定义一个监听器,用于监听zk中保存配置信息的节点(webapp/dblinkcfg)
Listener.java
package com.lagou.zk;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Properties;
/**
* @Author wanglitao
* @Date 2020/8/2 4:03 下午
* @Version 1.0
* @Description 监听器
*/
public class Listener {
private static Logger logger = LoggerFactory.getLogger(Listener.class);
// zk服务器地址信息
public static final String serverstring = "linux121:2181,linux122:2181,linux123:2181";
// 获取ZkClient对象
private static ZkClient zkClient = new ZkClient(serverstring);
// 保存数据库配置信息的节点路径
private static String path = "/webapp/dblinkcfg";
/**
* 监听
*
* @throws IOException
*/
public static void monitor() throws IOException {
zkClient.subscribeDataChanges(path, new IZkDataListener() {
public void handleDataChange(String dataPath, Object data) throws Exception {
logger.info("zk中的数据库配置信息发生修改!尝试重新获取数据库连接池...");
// 重新获取配置信息
String cfg = zkClient.readData(dataPath, true);
Properties pro = new Properties();
Utils.loadData(pro, cfg);
// 释放旧的连接池
ConnectionManager.clearPool();
// 创建新的连接池
Utils.createDbPool(pro);
}
public void handleDataDeleted(String dataPath) throws Exception {
logger.error("zk中的数据库配置信息已被删除!");
}
});
}
}
4.数据库连接管理器:ConnectionManager.java
package com.lagou.zk;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
/**
* 数据库连接管理器
*/
public class ConnectionManager {
private static List<Connection> pool = new LinkedList<Connection>();
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 ConnectionManager instance = null;
private ConnectionManager() {
init();
}
public static ConnectionManager getInstance() {
if (null == instance) {
synchronized (ConnectionManager.class) {
if (null == instance) {
return new ConnectionManager();
}
}
}
return instance;
}
public static void init() {
addConnection();
}
public static void addConnection() {
for (int i = 0; i < initCount; i++) {
try {
pool.add(createConnection());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public static Connection createConnection() throws ClassNotFoundException {
Connection conn = null;
try {
Class.forName(DRIVER);
conn = DriverManager.getConnection(Url, USERNAME, PASSWORD);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 从连接池中获取连接
*
* @return
* @throws SQLException
* @throws ClassNotFoundException
*/
public static Connection getConnection() throws SQLException, ClassNotFoundException {
synchronized (pool) {
if (pool.size() > 0) {
System.out.println("Current Connection size is:" + 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("Current Connection is Zero");
}
}
}
/**
* 清空连接池,释放连接
*/
public static void clearPool() {
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);
}
}
Utils.java
package com.lagou.zk;
import java.io.IOException;
import java.io.StringReader;
import java.util.Properties;
/**
* @Author wanglitao
* @Date 2020/8/2 6:39 下午
* @Version 1.0
* @Description 工具类
*/
public class Utils {
public static void loadData(Properties pro, String cfg) throws IOException {
pro.load(new StringReader(cfg));
}
public static void createDbPool(Properties pro) {
// 解析配置信息,创建数据库连接池
ConnectionManager.DRIVER = pro.getProperty("driverClassName");
ConnectionManager.Url = pro.getProperty("url");
ConnectionManager.USERNAME = pro.getProperty("username");
ConnectionManager.PASSWORD = pro.getProperty("password");
ConnectionManager.initCount = Integer.parseInt(pro.getProperty("initCount"));
ConnectionManager.maxCount = Integer.parseInt(pro.getProperty("maxCount"));
ConnectionManager.currentCount = Integer.parseInt(pro.getProperty("currentCount"));
ConnectionManager.init();
}
}
5.启动时初始化数据库连接池并接听
package com.lagou.zk;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
/**
* @Author wanglitao
* @Date 2020/8/2 6:36 下午
* @Version 1.0
* @Description 启动时初始化:创建数据库连接池并监听
*/
@Component
public class Startup {
@PostConstruct
public void init() throws IOException, SQLException, ClassNotFoundException {
ZkClient zkClient = new ZkClient("linux121:2181,linux122:2181,linux123:2181");
String cfg = zkClient.readData("/webapp/dblinkcfg", true);
Properties pro = new Properties();
Utils.loadData(pro, cfg);
// 创建数据库连接池
Utils.createDbPool(pro);
// 监听节点数据的变化
Listener.monitor();
// 使用连接池测试
Connection conn = ConnectionManager.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement("select * from execution_jobs");
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
System.out.println(resultSet.getString("flow_id"));
System.out.println(resultSet.getString("job_id"));
}
}
}
需要在applicationContext.xml中配置自动扫描和注解驱动
6.将应用部署到Tomcat
7.定义测试类,往zk中/webapp/dblinkcfg节点的发布数据库配置信息
package com.lagou.zk;
import org.junit.Test;
/**
* @Author wanglitao
* @Date 2020/8/2 4:45 下午
* @Version 1.0
* @Description 测试发布信息
*/
public class PublisherTest {
Publisher publisher = new Publisher();
@Test
public void publish() {
String cfg = "driverClassName=com.mysql.jdbc.Driver\n" +
"url=jdbc:mysql://linux123:3306/azkaban\n" +
"username=hive\n" +
"password=12345678\n" +
"initCount=5\n" +
"maxCount=10\n" +
"currentCount=5";
publisher.publish(cfg);
System.out.println(publisher.zkClient.readData("/webapp/dblinkcfg", true).toString());
}
}
7.启动应用,查看状态
可以看到,成功获取到了数据库连接并查询到了数据库表中的信息。
8.修改zk中/webapp/dblinkcfg节点的数据:username=hive改为username=root,可以看到日志信息
9.删除节点/webapp/dblinkcfg
完成!!!