add
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package com.qiaoba.common.database.config;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.qiaoba.common.base.constants.TenantConstant;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
@ -9,13 +10,13 @@ import com.qiaoba.common.database.constants.DynamicDatasourceConstant;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import com.qiaoba.common.database.properties.DataSourceProperties;
|
||||
import com.qiaoba.common.database.service.DynamicDatasourceService;
|
||||
import com.qiaoba.common.database.utils.JdbcUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.annotation.Resource;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@ -43,6 +44,11 @@ public class DynamicDataSourceConfig {
|
||||
@Resource
|
||||
private DynamicDatasourceService dynamicDatasourceService;
|
||||
|
||||
/**
|
||||
* 数据源加载完成
|
||||
*/
|
||||
public static Boolean COMPLETE_LOAD_DATASOURCE = false;
|
||||
|
||||
/**
|
||||
* 主要数据源
|
||||
*/
|
||||
@ -53,6 +59,11 @@ public class DynamicDataSourceConfig {
|
||||
*/
|
||||
public static Map<String, List<DynamicDataSource>> BACKUP_DATASOURCE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 租户 ids
|
||||
*/
|
||||
public static List<String> TENANT_IDS = ListUtil.toList(TenantConstant.DEFAULT_TENANT_ID);
|
||||
|
||||
/**
|
||||
* 把DynamicDataSourceContext 纳入容器管理,其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象,并调用freshDataSource方法
|
||||
*/
|
||||
@ -62,6 +73,7 @@ public class DynamicDataSourceConfig {
|
||||
initDefault();
|
||||
// 加载租户数据源
|
||||
initTenant();
|
||||
COMPLETE_LOAD_DATASOURCE = true;
|
||||
}
|
||||
|
||||
public static DruidDataSource getPrimaryDataSource(String tenantId) {
|
||||
@ -90,14 +102,17 @@ public class DynamicDataSourceConfig {
|
||||
addPrimaryMap(DynamicDatasourceConstant.MASTER_PREFIX + tenantId, buildDataSource(dataSources.get(i)));
|
||||
// 去除主要数据源,剩下皆为备用数据源
|
||||
dataSources.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 备用数据源
|
||||
addBackupMap(tenantId, dataSources);
|
||||
TENANT_IDS.add(tenantId);
|
||||
}
|
||||
BaseContext.clearAllHolder();
|
||||
// 刷新数据源
|
||||
dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP);
|
||||
|
||||
}
|
||||
|
||||
public void deleteTenantDataSource(String tenantId) {
|
||||
@ -105,7 +120,18 @@ public class DynamicDataSourceConfig {
|
||||
dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP);
|
||||
}
|
||||
|
||||
private static Object buildDataSource(DynamicDataSource dynamicDataSource) {
|
||||
public void changePrimaryDatasource(String tenantId, Object datasource) {
|
||||
PRIMARY_DATASOURCE_MAP.put(DynamicDatasourceConstant.MASTER_PREFIX + tenantId, datasource);
|
||||
dataSource.freshDataSource(PRIMARY_DATASOURCE_MAP);
|
||||
}
|
||||
|
||||
public static Object buildDataSource(DynamicDataSource dynamicDataSource) {
|
||||
log.debug("正在创建数据源DataSource, 租户: {}", dynamicDataSource.getTenantId());
|
||||
boolean connect = JdbcUtil.checkConnect(dynamicDataSource.getDriver(), dynamicDataSource.getUrl(), dynamicDataSource.getUsername(), dynamicDataSource.getPassword());
|
||||
if (!connect) {
|
||||
log.error("租户: {} 数据源连接失败, Url: {}", dynamicDataSource.getTenantId(), dynamicDataSource.getUrl());
|
||||
return null;
|
||||
}
|
||||
DruidDataSource dataSource = new DruidDataSource();
|
||||
dataSource.setUrl(dynamicDataSource.getUrl());
|
||||
dataSource.setUsername(dynamicDataSource.getUsername());
|
||||
@ -114,10 +140,11 @@ public class DynamicDataSourceConfig {
|
||||
dataSource.setInitialSize(dynamicDataSource.getInitialSize());
|
||||
dataSource.setMinIdle(dynamicDataSource.getMinIdle());
|
||||
dataSource.setMaxActive(dynamicDataSource.getMaxActive());
|
||||
dataSource.setKeepAlive(false);
|
||||
try {
|
||||
dataSource.init();
|
||||
return dataSource;
|
||||
} catch (SQLException e) {
|
||||
} catch (Exception e) {
|
||||
dataSource.close();
|
||||
return null;
|
||||
}
|
||||
@ -131,7 +158,9 @@ public class DynamicDataSourceConfig {
|
||||
Set<Map.Entry<Object, Object>> entries = PRIMARY_DATASOURCE_MAP.entrySet();
|
||||
for (Map.Entry<Object, Object> entry : entries) {
|
||||
DruidDataSource dataSource = (DruidDataSource) entry.getValue();
|
||||
dataSource.close();
|
||||
if (!dataSource.isClosed()) {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,9 @@ import lombok.NoArgsConstructor;
|
||||
public class DynamicDataSource {
|
||||
|
||||
/**
|
||||
* 租户Code
|
||||
* 数据源ID
|
||||
*/
|
||||
private String tenantId;
|
||||
private String datasourceId;
|
||||
|
||||
/**
|
||||
* 主要数据源
|
||||
@ -63,4 +63,8 @@ public class DynamicDataSource {
|
||||
*/
|
||||
private Integer maxActive;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.qiaoba.common.database.interceptors;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||
import com.qiaoba.common.base.context.BaseContext;
|
||||
@ -8,6 +9,7 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@ -28,10 +30,14 @@ public class SchemaInterceptor implements InnerInterceptor {
|
||||
if (Objects.nonNull(BaseContext.isSchemaMode()) && BaseContext.isSchemaMode()) {
|
||||
// eg: use qiaoba-1;
|
||||
String sql = StrUtil.format("use `{}-{}`;", baseDatabase, BaseContext.getTenantId());
|
||||
Statement statement = null;
|
||||
try {
|
||||
conn.createStatement().execute(sql);
|
||||
statement = conn.createStatement();
|
||||
statement.execute(sql);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
IoUtil.close(statement);
|
||||
}
|
||||
}
|
||||
InnerInterceptor.super.beforePrepare(sh, conn, transactionTimeout);
|
||||
|
@ -0,0 +1,137 @@
|
||||
package com.qiaoba.common.database.monitor;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.alibaba.druid.util.JdbcUtils;
|
||||
import com.qiaoba.common.base.constants.TenantConstant;
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
||||
import com.qiaoba.common.database.constants.DynamicDatasourceConstant;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import com.qiaoba.common.database.service.DynamicDatasourceService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 监控-数据源连接监控
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023/6/14 14:48
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DatasourceConnectionMonitor {
|
||||
|
||||
private static final String CHECK_SQL = "SELECT 1";
|
||||
|
||||
@Resource
|
||||
private DynamicDataSourceConfig dynamicDataSourceConfig;
|
||||
@Resource
|
||||
private DynamicDatasourceService dynamicDatasourceService;
|
||||
|
||||
//@PostConstruct
|
||||
public void init() {
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 项目加载数据源还未完成
|
||||
if (!DynamicDataSourceConfig.COMPLETE_LOAD_DATASOURCE) {
|
||||
return;
|
||||
}
|
||||
log.debug("开始运行数据源监控线程, 时间: {}", new Date());
|
||||
for (String tenantId : DynamicDataSourceConfig.TENANT_IDS) {
|
||||
Object primary = DynamicDataSourceConfig.PRIMARY_DATASOURCE_MAP.get(DynamicDatasourceConstant.MASTER_PREFIX + tenantId);
|
||||
if (Objects.isNull(primary)) {
|
||||
// 说明初始化主要数据源的时候出错
|
||||
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
|
||||
// 切换备用数据源
|
||||
changePrimary(tenantId);
|
||||
} else {
|
||||
DruidDataSource dataSource = (DruidDataSource) primary;
|
||||
try {
|
||||
Connection connection = dataSource.getConnection();
|
||||
if (check(connection)) {
|
||||
// 说明数据源正常
|
||||
log.debug("租户[{}]-目前主数据源正常, 无需切换数据源", tenantId);
|
||||
continue;
|
||||
}
|
||||
|
||||
log.error("租户[{}]-目前主数据源异常, 开始切换备用数据源", tenantId);
|
||||
// 关闭原有异常数据源
|
||||
connection.close();
|
||||
dataSource.close();
|
||||
// 主数据源异常 切换备用数据源
|
||||
changePrimary(tenantId);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("结束运行数据源监控线程, 时间: {}", new Date());
|
||||
}
|
||||
}, 0, 1000);
|
||||
}
|
||||
|
||||
private Boolean check(Connection conn) {
|
||||
Statement stmt = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
stmt.setQueryTimeout(1);
|
||||
rs = stmt.executeQuery(CHECK_SQL);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} finally {
|
||||
JdbcUtils.close(rs);
|
||||
JdbcUtils.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换主数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
private void changePrimary(String tenantId) {
|
||||
// 备用数据源
|
||||
List<DynamicDataSource> dataSources = DynamicDataSourceConfig.BACKUP_DATASOURCE_MAP.get(tenantId);
|
||||
if (Objects.isNull(dataSources)) {
|
||||
log.error("租户:[{}]切换备用数据源失败, 原因: 没有备用数据源", tenantId);
|
||||
}
|
||||
|
||||
Integer backupIndex = null;
|
||||
|
||||
for (int i = 0; i < dataSources.size(); i++) {
|
||||
Object dynamicDataSource = DynamicDataSourceConfig.buildDataSource(dataSources.get(i));
|
||||
// 不是空,说明备用数据源有用
|
||||
if (Objects.nonNull(dynamicDataSource)) {
|
||||
dynamicDataSourceConfig.changePrimaryDatasource(tenantId, dynamicDataSource);
|
||||
backupIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Objects.nonNull(backupIndex)) {
|
||||
// 切换成功
|
||||
DynamicDataSource dynamicDataSource = dataSources.get(backupIndex);
|
||||
// 更改数据库中该数据源为主要数据源
|
||||
if (!TenantConstant.DEFAULT_TENANT_ID.equals(dynamicDataSource.getTenantId())) {
|
||||
dynamicDatasourceService.changePrimaryDatasource(dynamicDataSource.getTenantId(), dynamicDataSource.getDatasourceId());
|
||||
}
|
||||
// 备用数据源集合删除该数据源
|
||||
dataSources.remove((int) backupIndex);
|
||||
log.info("租户:[{}]切换备用数据源成功, 现主数据ID: {}", tenantId, dynamicDataSource.getDatasourceId());
|
||||
} else {
|
||||
log.error("租户:[{}]切换备用数据源失败, 原因: 备用数据源均无效", tenantId);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,4 +20,12 @@ public interface DynamicDatasourceService {
|
||||
* @return 数据源集合 key: tenantId | value: list
|
||||
*/
|
||||
Map<String, List<DynamicDataSource>> loadAllTenantDatasource();
|
||||
|
||||
/**
|
||||
* 改变主数据源
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param datasourceId 数据源ID
|
||||
*/
|
||||
void changePrimaryDatasource(String tenantId, String datasourceId);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.qiaoba.common.database.utils;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.qiaoba.common.base.exceptions.ServiceException;
|
||||
|
||||
@ -39,13 +40,7 @@ public class JdbcUtil {
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
IoUtil.close(conn);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.qiaoba.common.database.factories.DynamicDataSourceFactory,\
|
||||
com.qiaoba.common.database.monitor.DatasourceConnectionMonitor,\
|
||||
com.qiaoba.common.database.config.MybatisPlusConfig
|
||||
|
Reference in New Issue
Block a user