基于 Zookeeper
实现简易版配置中心
即:当项目Web项目启动时,从
Zookeeper
进行MySQL
配置参数的拉取
要求项目通过数据库连接池访问MySQL(连接池可以自由选择熟悉的)
当 Zookeeper
配置信息变化后 Web
项目自动感知,正确释放之前连接池,创建新的连接池
总体思路:
SpringBoot
项目启动时,从
Zookeeper
拉取配置
MySQL
读取
zk
中数据,序列化为字符串,再将字符串用jackson
转为对象
创建HikariPool
,访问数据库
Zookeeper
对应节点数据变化,释放连接池 或 创建新的连接池类似重复 2. 的过程
-- mysql 版本 8.0
mysql> desc user;
+----------|-------------|------|-----|---------|-------+
| Field | Type | Null | Key | Default | Extra |
+----------|-------------|------|-----|---------|-------+
| id | int(11) | NO | PRI | NULL | |
| username | varchar(64) | NO | | | |
+----------|-------------|------|-----|---------|-------+
2 rows in set (0.01 sec)
mysql> insert into user(id, username) values (1, "haha");
Query OK, 1 row affected (0.01 sec)
maven
配置 <dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.zaxxergroupId>
<artifactId>HikariCPartifactId>
<version>3.4.5version>
<scope>compilescope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.11version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.14version>
dependency>
<dependency>
<groupId>com.101tecgroupId>
<artifactId>zkclientartifactId>
<version>0.2version>
dependency>
dependencies>
Spring Boot
代码package com.donaldy.zkpractice;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Data;
import lombok.ToString;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@SpringBootApplication
public class Application {
private static HikariDataSource hikariDataSource;
private static ZkClient zkClient;
private static ObjectMapper mapper = new ObjectMapper();
/**
* 启动服务
*
* 1. 启动 web 容器
* 2. 初始化 zookeeper
* 3. 配置数据库连接池
*
* @param args 参数
*/
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
initZk();
configHikariSource();
}
private static void initZk() {
zkClient = new ZkClient("172.16.64.121:2181");
zkClient.setZkSerializer(new ZkStrSerializer());
zkClient.subscribeDataChanges("/jdbc", new IZkDataListener() {
public void handleDataChange(String path, Object data) {
System.out.println(path + " data is changed, new data " + data);
hikariDataSource.close();
configHikariSource();
}
public void handleDataDeleted(String path) {
System.out.println(path + " is deleted!!");
hikariDataSource.close();
}
});
}
/**
* 配置数据库连接池
*
* 1. 从 zookeeper 中获取配置信息
* 2. 更新 hikari 配置
* 3. 执行测试 sql
*/
private static void configHikariSource(){
JDBCConfig myConfig = getJDBCConfig();
updateHikariConfig(myConfig);
try {
executeTestSQL();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void executeTestSQL() throws SQLException {
Connection connection = hikariDataSource.getConnection();
PreparedStatement pst = connection.prepareStatement( "SELECT id, username FROM user;" );
ResultSet rs = pst.executeQuery();
while (rs.next()) {
System.out.println("id : " + rs.getString(1) + " , username : " + rs.getString(2));
}
}
private static void updateHikariConfig(JDBCConfig myConfig) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(myConfig.getUrl());
config.setUsername(myConfig.getUsername());
config.setPassword(myConfig.getPassword());
config.addDataSourceProperty( "driverClassName" , myConfig.getDriver());
config.addDataSourceProperty( "cachePrepStmts" , "true" );
config.addDataSourceProperty( "prepStmtCacheSize" , "250" );
config.addDataSourceProperty( "prepStmtCacheSqlLimit" , "2048" );
hikariDataSource = new HikariDataSource(config);
}
private static JDBCConfig getJDBCConfig() {
Object data = zkClient.readData("/jdbc");
try {
JDBCConfig myConfig = mapper.readValue(data.toString(), JDBCConfig.class);
System.out.println(myConfig.toString());
return myConfig;
} catch (JsonProcessingException e) {
return new JDBCConfig();
}
}
}
class ZkStrSerializer implements ZkSerializer {
@Override
public byte[] serialize(Object o) throws ZkMarshallingError {
return String.valueOf(o).getBytes();
}
@Override
public Object deserialize(byte[] bytes) throws ZkMarshallingError {
return new String(bytes);
}
}
@Data
@ToString
class JDBCConfig {
private String url;
private String driver = "com.mysql.jdbc.Driver";
private String username;
private String password;
}
运行行为如下:
zk
中创建节点create /jdbc {"url":"jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false","username":"root","password":"root","driver":"com.mysql.jdbc.Driver"}
# 驱动:改变为 com.mysql.cj.jdbc.Driver
# 因为 mysql 版本使用 8.0
set /jdbc {"url":"jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false","username":"root","password":"root","driver":"com.mysql.cj.jdbc.Driver"}
delete /jdbc
运行结果为:
2020-08-31 01:59:18.845 INFO 14766 --- [(linux121:2181)] org.apache.zookeeper.ClientCnxn : Session establishment complete on server linux121/172.16.64.121:2181, sessionid = 0x100217325960011, negotiated timeout = 30000
JDBCConfig(url=jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false, driver=com.mysql.jdbc.Driver, username=root, password=root)
2020-08-31 01:59:18.879 INFO 14766 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-08-31 01:59:18.952 INFO 14766 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
id : 1 , username : haha
/jdbc data is changed, new data {"url":"jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false","username":"root","password":"root","driver":"com.mysql.cj.jdbc.Driver"}
2020-08-31 02:00:04.213 INFO 14766 --- [.16.64.121:2181] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2020-08-31 02:00:04.216 INFO 14766 --- [.16.64.121:2181] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
JDBCConfig(url=jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false, driver=com.mysql.cj.jdbc.Driver, username=root, password=root)
2020-08-31 02:00:04.218 INFO 14766 --- [.16.64.121:2181] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Starting...
2020-08-31 02:00:04.225 INFO 14766 --- [.16.64.121:2181] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Start completed.
id : 1 , username : haha
/jdbc is deleted!!
2020-08-31 02:00:28.038 INFO 14766 --- [.16.64.121:2181] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown initiated...
2020-08-31 02:00:28.057 INFO 14766 --- [.16.64.121:2181] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown completed.
运行截图如下: