add
This commit is contained in:
@ -11,16 +11,16 @@ qiaoba:
|
|||||||
min-idle: 10
|
min-idle: 10
|
||||||
#最大连接池数量
|
#最大连接池数量
|
||||||
max-active: 20
|
max-active: 20
|
||||||
# - driver: com.mysql.cj.jdbc.Driver
|
- driver: com.mysql.cj.jdbc.Driver
|
||||||
# url: jdbc:mysql://192.168.0.203:3306/${spring.application.name}?databaseTerm=SCHEMA&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
|
url: jdbc:mysql://192.168.0.203:3306/${spring.application.name}?databaseTerm=SCHEMA&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
|
||||||
# username: root
|
username: root
|
||||||
# password: Root123456789.
|
password: Root123456789.
|
||||||
# #连接池初始化大小
|
#连接池初始化大小
|
||||||
# initial-size: 5
|
initial-size: 5
|
||||||
# #最小空闲线程数
|
#最小空闲线程数
|
||||||
# min-idle: 10
|
min-idle: 10
|
||||||
# #最大连接池数量
|
#最大连接池数量
|
||||||
# max-active: 20
|
max-active: 20
|
||||||
# - driver: org.postgresql.Driver
|
# - driver: org.postgresql.Driver
|
||||||
# url: jdbc:postgresql://192.168.0.202:5432/mydb?currentSchema=qiaoba-boot&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
|
# url: jdbc:postgresql://192.168.0.202:5432/mydb?currentSchema=qiaoba-boot&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowMultiQueries=true
|
||||||
# username: postgres
|
# username: postgres
|
||||||
|
@ -2,7 +2,7 @@ server:
|
|||||||
port: 8080
|
port: 8080
|
||||||
spring:
|
spring:
|
||||||
main:
|
main:
|
||||||
#新创建的bean覆盖旧的bean
|
# 新创建的bean覆盖旧的bean
|
||||||
allow-bean-definition-overriding: true
|
allow-bean-definition-overriding: true
|
||||||
application:
|
application:
|
||||||
# DbName / FileDirName / SchemaPrefix
|
# DbName / FileDirName / SchemaPrefix
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.qiaoba.common.base.constant;
|
package com.qiaoba.common.base.constant;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BaseConstant
|
* BaseConstant
|
||||||
*
|
*
|
||||||
@ -79,4 +81,13 @@ public class BaseConstant {
|
|||||||
*/
|
*/
|
||||||
public static final Integer HANDLE_ERROR = 0;
|
public static final Integer HANDLE_ERROR = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务SN
|
||||||
|
*/
|
||||||
|
public static String SERVER_SN;
|
||||||
|
|
||||||
|
static {
|
||||||
|
SERVER_SN = UUID.fastUUID().toString(true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import com.qiaoba.common.database.monitor.NotOnlineDatasourceMonitor;
|
|||||||
import com.qiaoba.common.database.properties.DataSourceProperties;
|
import com.qiaoba.common.database.properties.DataSourceProperties;
|
||||||
import com.qiaoba.common.database.service.DynamicDatasourceService;
|
import com.qiaoba.common.database.service.DynamicDatasourceService;
|
||||||
import com.qiaoba.common.database.util.DatasourceUtil;
|
import com.qiaoba.common.database.util.DatasourceUtil;
|
||||||
import com.qiaoba.common.database.util.JdbcUtil;
|
import com.qiaoba.common.redis.service.RedisService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@ -25,10 +25,7 @@ import javax.annotation.PostConstruct;
|
|||||||
import javax.annotation.PreDestroy;
|
import javax.annotation.PreDestroy;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多数据源配置
|
* 多数据源配置
|
||||||
@ -50,6 +47,10 @@ public class DynamicDataSourceConfig {
|
|||||||
private DataSourceProperties dataSourceProperties;
|
private DataSourceProperties dataSourceProperties;
|
||||||
@Resource
|
@Resource
|
||||||
private DynamicDatasourceService dynamicDatasourceService;
|
private DynamicDatasourceService dynamicDatasourceService;
|
||||||
|
@Resource
|
||||||
|
private RedisService redisService;
|
||||||
|
@Resource
|
||||||
|
private BackupDatasourceContext backupDatasourceContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据源加载完成
|
* 数据源加载完成
|
||||||
@ -80,26 +81,36 @@ public class DynamicDataSourceConfig {
|
|||||||
|
|
||||||
private void initDefault() {
|
private void initDefault() {
|
||||||
List<DynamicDataSource> dataSources = dataSourceProperties.getDataSources();
|
List<DynamicDataSource> dataSources = dataSourceProperties.getDataSources();
|
||||||
for (int i = 0; i < dataSources.size(); i++) {
|
List<DynamicDataSource> delDataSources = new ArrayList<>();
|
||||||
// 0索引作为主数据源
|
for (DynamicDataSource dynamicDataSource : dataSources) {
|
||||||
Object dataSource = DatasourceUtil.buildDataSource(TenantConstant.DEFAULT_TENANT_ID, dataSources.get(i));
|
// 设置数据源的租户ID(主数据源租户ID null)
|
||||||
|
dynamicDataSource.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
|
||||||
|
Object dataSource = DatasourceUtil.buildDataSource(TenantConstant.DEFAULT_TENANT_ID, dynamicDataSource);
|
||||||
if (Objects.isNull(dataSource)) {
|
if (Objects.isNull(dataSource)) {
|
||||||
// 默认的主数据源挂了
|
// 默认的主数据源挂了
|
||||||
// 加入到错误数据源Map 等待重试
|
// 加入到错误数据源Map 等待重试
|
||||||
NotOnlineDatasourceMonitor.addErrorDatasource(TenantConstant.DEFAULT_TENANT_ID, dataSources.get(i));
|
redisService.addHashList(NotOnlineDatasourceMonitor.ERROR_DATASOURCE_KEY, TenantConstant.DEFAULT_TENANT_ID, dynamicDataSource, DynamicDataSource.class);
|
||||||
dataSources.remove(i);
|
// 从备用数据源删除, 因为它挂了
|
||||||
|
delDataSources.add(dynamicDataSource);
|
||||||
} else {
|
} else {
|
||||||
addPrimaryMap(TenantConstant.DEFAULT_TENANT_ID, dataSource);
|
addPrimaryMap(TenantConstant.DEFAULT_TENANT_ID, dataSource);
|
||||||
dataSources.remove(i);
|
// 从备用数据源删除, 因为它是主数据源
|
||||||
|
delDataSources.add(dynamicDataSource);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CollUtil.isEmpty(PrimaryDatasourceContext.getAll())) {
|
if (CollUtil.isEmpty(PrimaryDatasourceContext.getAll())) {
|
||||||
log.error("主系统配置数据源全部无效, 请检查 yml 中相关配置");
|
log.error("主系统配置数据源全部无效, 请检查 yml 中相关配置");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他数据源备用
|
// 其他数据源备用
|
||||||
addBackupMap(TenantConstant.DEFAULT_TENANT_ID, dataSources);
|
dataSources.removeAll(delDataSources);
|
||||||
|
for (DynamicDataSource dynamicDataSource : dataSources) {
|
||||||
|
dynamicDataSource.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
|
||||||
|
}
|
||||||
|
backupDatasourceContext.addBackupMap(TenantConstant.DEFAULT_TENANT_ID, dataSources);
|
||||||
// 刷新数据源
|
// 刷新数据源
|
||||||
this.dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
|
this.dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
|
||||||
}
|
}
|
||||||
@ -107,31 +118,32 @@ public class DynamicDataSourceConfig {
|
|||||||
private void initTenant() {
|
private void initTenant() {
|
||||||
setDefaultSetting();
|
setDefaultSetting();
|
||||||
Map<String, List<DynamicDataSource>> tenantDatasource = dynamicDatasourceService.loadAllTenantDatasource();
|
Map<String, List<DynamicDataSource>> tenantDatasource = dynamicDatasourceService.loadAllTenantDatasource();
|
||||||
|
BaseContext.clearAllHolder();
|
||||||
for (String tenantId : tenantDatasource.keySet()) {
|
for (String tenantId : tenantDatasource.keySet()) {
|
||||||
List<DynamicDataSource> dataSources = tenantDatasource.get(tenantId);
|
List<DynamicDataSource> dataSources = tenantDatasource.get(tenantId);
|
||||||
for (int i = 0; i < dataSources.size(); i++) {
|
List<DynamicDataSource> delDataSources = new ArrayList<>();
|
||||||
DynamicDataSource dynamicDataSource = dataSources.get(i);
|
for (DynamicDataSource dynamicDataSource : dataSources) {
|
||||||
if (BaseEnum.YES.getCode().equals(dynamicDataSource.getIsPrimary())) {
|
if (BaseEnum.YES.getCode().equals(dynamicDataSource.getIsPrimary())) {
|
||||||
Object dataSource = DatasourceUtil.buildDataSource(dataSources.get(i).getTenantId(), dataSources.get(i));
|
Object dataSource = DatasourceUtil.buildDataSource(dynamicDataSource.getTenantId(), dynamicDataSource);
|
||||||
if (Objects.isNull(dataSource)) {
|
if (Objects.isNull(dataSource)) {
|
||||||
// 默认的主数据源挂了
|
// 默认的主数据源挂了
|
||||||
|
log.info("租户[{}]主数据异常, 数据源ID: {}", dynamicDataSource.getTenantId(), dynamicDataSource.getDatasourceId());
|
||||||
// 加入到错误数据源Map 等待重试
|
// 加入到错误数据源Map 等待重试
|
||||||
NotOnlineDatasourceMonitor.addErrorDatasource(tenantId, dataSources.get(i));
|
redisService.addHashList(NotOnlineDatasourceMonitor.ERROR_DATASOURCE_KEY, tenantId, dynamicDataSource, DynamicDataSource.class);
|
||||||
// 在数据源集合中删除, 防止将错误的数据源加载到备用数据源中
|
|
||||||
dataSources.remove(i);
|
|
||||||
} else {
|
} else {
|
||||||
|
// 加入到主要数据源
|
||||||
addPrimaryMap(tenantId, dataSource);
|
addPrimaryMap(tenantId, dataSource);
|
||||||
// 去除主要数据源,剩下皆为备用数据源
|
|
||||||
dataSources.remove(i);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
// 去除主要数据源,剩下皆为备用数据源
|
||||||
|
delDataSources.add(dynamicDataSource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 备用数据源
|
// 备用数据源
|
||||||
addBackupMap(tenantId, dataSources);
|
dataSources.removeAll(delDataSources);
|
||||||
|
backupDatasourceContext.addBackupMap(tenantId, dataSources);
|
||||||
TENANT_IDS.add(tenantId);
|
TENANT_IDS.add(tenantId);
|
||||||
}
|
}
|
||||||
BaseContext.clearAllHolder();
|
|
||||||
// 刷新数据源
|
// 刷新数据源
|
||||||
dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
|
dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
|
||||||
|
|
||||||
@ -165,12 +177,6 @@ public class DynamicDataSourceConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBackupMap(String tenantId, List<DynamicDataSource> dataSources) {
|
|
||||||
if (CollUtil.isNotEmpty(dataSources)) {
|
|
||||||
BackupDatasourceContext.set(tenantId, dataSources);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDefaultSetting() {
|
public void setDefaultSetting() {
|
||||||
BaseContext.setDataSource(TenantConstant.DEFAULT_TENANT_ID);
|
BaseContext.setDataSource(TenantConstant.DEFAULT_TENANT_ID);
|
||||||
BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
|
BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
|
||||||
|
@ -3,10 +3,12 @@ package com.qiaoba.common.database.context;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.collection.ListUtil;
|
import cn.hutool.core.collection.ListUtil;
|
||||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备用(未在使用)数据源
|
* 备用(未在使用)数据源
|
||||||
@ -15,12 +17,15 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
* @version 1.0
|
* @version 1.0
|
||||||
* @since 2023/6/28 10:36
|
* @since 2023/6/28 10:36
|
||||||
*/
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public class BackupDatasourceContext {
|
public class BackupDatasourceContext {
|
||||||
|
|
||||||
/**
|
private static final String BACKUP_DATASOURCE_KET = "backup_datasource:";
|
||||||
* 备用数据源
|
|
||||||
*/
|
@Resource
|
||||||
private static Map<String, List<DynamicDataSource>> BACKUP_DATASOURCE_MAP = new ConcurrentHashMap<>();
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,8 +34,8 @@ public class BackupDatasourceContext {
|
|||||||
* @param tenantId 租户ID
|
* @param tenantId 租户ID
|
||||||
* @return 数据源集合
|
* @return 数据源集合
|
||||||
*/
|
*/
|
||||||
public static List<DynamicDataSource> get(String tenantId) {
|
public List<DynamicDataSource> get(String tenantId) {
|
||||||
return BACKUP_DATASOURCE_MAP.get(tenantId);
|
return (List<DynamicDataSource>) redisTemplate.opsForValue().get(BACKUP_DATASOURCE_KET + tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,51 +44,57 @@ public class BackupDatasourceContext {
|
|||||||
* @param tenantId 租户ID
|
* @param tenantId 租户ID
|
||||||
* @param list 数据源集合
|
* @param list 数据源集合
|
||||||
*/
|
*/
|
||||||
public static void set(String tenantId, List<DynamicDataSource> list) {
|
private void set(String tenantId, List<DynamicDataSource> list) {
|
||||||
BACKUP_DATASOURCE_MAP.put(tenantId, list);
|
redisTemplate.opsForValue().set(BACKUP_DATASOURCE_KET + tenantId, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加租户备用数据源
|
* 添加租户备用数据源
|
||||||
*
|
*
|
||||||
* @param tenantId 租户ID
|
|
||||||
* @param dataSource 数据源
|
* @param dataSource 数据源
|
||||||
*/
|
*/
|
||||||
public static void addBackupMap(String tenantId, DynamicDataSource dataSource) {
|
public void addBackupMap(DynamicDataSource dataSource) {
|
||||||
List<DynamicDataSource> dataSourceList = get(tenantId);
|
log.info("添加租户备用数据源, 租户ID:{}, 数据源IP:{}", dataSource.getTenantId(), dataSource.getUrl());
|
||||||
|
List<DynamicDataSource> dataSourceList = get(dataSource.getTenantId());
|
||||||
if (CollUtil.isEmpty(dataSourceList)) {
|
if (CollUtil.isEmpty(dataSourceList)) {
|
||||||
set(tenantId, ListUtil.toList(dataSource));
|
set(dataSource.getTenantId(), ListUtil.toList(dataSource));
|
||||||
} else {
|
} else {
|
||||||
dataSourceList.add(dataSource);
|
dataSourceList.add(dataSource);
|
||||||
|
set(dataSource.getTenantId(), dataSourceList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void deleteBackupMap(DynamicDataSource dynamicDataSource) {
|
||||||
* 删除租户备用数据源
|
log.info("移除租户备用数据源, 租户ID:{}, 数据源IP:{}", dynamicDataSource.getTenantId(), dynamicDataSource.getUrl());
|
||||||
*
|
List<DynamicDataSource> dataSourceList = get(dynamicDataSource.getTenantId());
|
||||||
* @param tenantId 租户ID
|
if (CollUtil.isNotEmpty(dataSourceList)) {
|
||||||
* @param dataSourceId 数据源ID
|
for (DynamicDataSource dataSource : dataSourceList) {
|
||||||
*/
|
if (dataSource.getUrl().equals(dynamicDataSource.getUrl())) {
|
||||||
public static void delBackupMap(String tenantId, String dataSourceId) {
|
dataSourceList.remove(dataSource);
|
||||||
List<DynamicDataSource> dataSourceList = get(tenantId);
|
set(dataSource.getTenantId(), dataSourceList);
|
||||||
if (CollUtil.isEmpty(dataSourceList)) {
|
|
||||||
for (DynamicDataSource dynamicDataSource : dataSourceList) {
|
|
||||||
if (dataSourceId.equals(dynamicDataSource.getDatasourceId())) {
|
|
||||||
dataSourceList.remove(dynamicDataSource);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加租户备用数据源
|
||||||
|
*
|
||||||
|
* @param tenantId 租户ID
|
||||||
|
* @param dataSources 数据源集合
|
||||||
|
*/
|
||||||
|
public void addBackupMap(String tenantId, List<DynamicDataSource> dataSources) {
|
||||||
|
set(tenantId, dataSources);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改租户备用数据源
|
* 修改租户备用数据源
|
||||||
*
|
*
|
||||||
* @param tenantId 租户ID
|
|
||||||
* @param dataSource 数据源
|
* @param dataSource 数据源
|
||||||
*/
|
*/
|
||||||
public static void updateBackupMap(String tenantId, DynamicDataSource dataSource) {
|
public void updateBackupMap(DynamicDataSource dataSource) {
|
||||||
List<DynamicDataSource> dataSourceList = get(tenantId);
|
List<DynamicDataSource> dataSourceList = get(dataSource.getTenantId());
|
||||||
if (CollUtil.isNotEmpty(dataSourceList)) {
|
if (CollUtil.isNotEmpty(dataSourceList)) {
|
||||||
for (DynamicDataSource dynamicDataSource : dataSourceList) {
|
for (DynamicDataSource dynamicDataSource : dataSourceList) {
|
||||||
if (dataSource.getDatasourceId().equals(dynamicDataSource.getDatasourceId())) {
|
if (dataSource.getDatasourceId().equals(dynamicDataSource.getDatasourceId())) {
|
||||||
@ -93,5 +104,6 @@ public class BackupDatasourceContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
set(dataSource.getTenantId(), dataSourceList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,4 +71,9 @@ public class DynamicDataSource implements Serializable {
|
|||||||
* 租户ID
|
* 租户ID
|
||||||
*/
|
*/
|
||||||
private String tenantId;
|
private String tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器SN
|
||||||
|
*/
|
||||||
|
private String serverSn;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.qiaoba.common.database.factory;
|
package com.qiaoba.common.database.factory;
|
||||||
|
|
||||||
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
||||||
|
import com.qiaoba.common.database.context.BackupDatasourceContext;
|
||||||
import com.qiaoba.common.database.context.DynamicDataSourceContext;
|
import com.qiaoba.common.database.context.DynamicDataSourceContext;
|
||||||
import com.qiaoba.common.database.properties.DataSourceProperties;
|
import com.qiaoba.common.database.properties.DataSourceProperties;
|
||||||
import com.qiaoba.common.database.properties.TenantSchema;
|
import com.qiaoba.common.database.properties.TenantSchema;
|
||||||
@ -43,4 +44,9 @@ public class DynamicDataSourceFactory {
|
|||||||
return new DataSourceProperties();
|
return new DataSourceProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public BackupDatasourceContext backupDatasourceContext() {
|
||||||
|
return new BackupDatasourceContext();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package com.qiaoba.common.database.listener;
|
package com.qiaoba.common.database.listener;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.qiaoba.common.base.constant.BaseConstant;
|
||||||
|
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||||
|
import com.qiaoba.common.database.util.DatasourceUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.redis.connection.Message;
|
import org.springframework.data.redis.connection.Message;
|
||||||
import org.springframework.data.redis.connection.MessageListener;
|
import org.springframework.data.redis.connection.MessageListener;
|
||||||
@ -7,6 +11,7 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换主要的数据源
|
* 切换主要的数据源
|
||||||
@ -28,10 +33,17 @@ public class SwitchPrimaryListener implements MessageListener {
|
|||||||
byte[] messageBody = message.getBody();
|
byte[] messageBody = message.getBody();
|
||||||
// 使用值序列化器转换
|
// 使用值序列化器转换
|
||||||
Object msgObj = redisTemplate.getValueSerializer().deserialize(messageBody);
|
Object msgObj = redisTemplate.getValueSerializer().deserialize(messageBody);
|
||||||
// 渠道名称转换
|
if (Objects.nonNull(msgObj)) {
|
||||||
String channel = new String(pattern);
|
DynamicDataSource dynamicDataSource = JSONUtil.toBean(msgObj.toString(), DynamicDataSource.class);
|
||||||
log.info("channel: {}", channel);
|
if (!BaseConstant.SERVER_SN.equals(dynamicDataSource.getServerSn())) {
|
||||||
log.info("msg: {}", msgObj);
|
String tenantId = dynamicDataSource.getTenantId();
|
||||||
|
log.info("切换租户主数据源, 租户ID:{}, 数据源Url:{}", tenantId, dynamicDataSource.getUrl());
|
||||||
|
// 删除原有数据源
|
||||||
|
DatasourceUtil.removePrimaryDatasource(tenantId);
|
||||||
|
// 切换数据源
|
||||||
|
DatasourceUtil.changePrimaryDatasource(tenantId, DatasourceUtil.buildDataSource(tenantId, dynamicDataSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,8 @@ public class NotOnlineDatasourceMonitor {
|
|||||||
private LockTemplate lockTemplate;
|
private LockTemplate lockTemplate;
|
||||||
@Resource
|
@Resource
|
||||||
private RedisService redisService;
|
private RedisService redisService;
|
||||||
|
@Resource
|
||||||
|
private BackupDatasourceContext backupDatasourceContext;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
@ -74,15 +75,14 @@ public class NotOnlineDatasourceMonitor {
|
|||||||
for (String tenantId : map.keySet()) {
|
for (String tenantId : map.keySet()) {
|
||||||
List<DynamicDataSource> errorDatasourceList = map.get(tenantId);
|
List<DynamicDataSource> errorDatasourceList = map.get(tenantId);
|
||||||
List<DynamicDataSource> delList = new ArrayList<>();
|
List<DynamicDataSource> delList = new ArrayList<>();
|
||||||
for (int i = 0; i < errorDatasourceList.size(); i++) {
|
for (DynamicDataSource errorDatasource : errorDatasourceList) {
|
||||||
DynamicDataSource errorDatasource = errorDatasourceList.get(i);
|
|
||||||
// 说明连接成功
|
// 说明连接成功
|
||||||
boolean check = JdbcUtil.checkConnect(errorDatasource.getDriver(), errorDatasource.getUrl(), errorDatasource.getUsername(), errorDatasource.getPassword());
|
boolean check = JdbcUtil.checkConnect(errorDatasource.getDriver(), errorDatasource.getUrl(), errorDatasource.getUsername(), errorDatasource.getPassword());
|
||||||
if (check) {
|
if (check) {
|
||||||
log.info("数据源重连成功, Url: {}", errorDatasource.getUrl());
|
log.info("数据源重连成功, Url: {}", errorDatasource.getUrl());
|
||||||
delList.add(errorDatasource);
|
delList.add(errorDatasource);
|
||||||
// 加入到备用Map中
|
// 加入到备用Map中
|
||||||
BackupDatasourceContext.addBackupMap(tenantId, errorDatasource);
|
backupDatasourceContext.addBackupMap(errorDatasource);
|
||||||
log.info("租户断线数据源重连成功, 已重新加入备用数据源,租户ID: {}", tenantId);
|
log.info("租户断线数据源重连成功, 已重新加入备用数据源,租户ID: {}", tenantId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,12 @@ package com.qiaoba.common.database.monitor;
|
|||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.alibaba.druid.pool.DruidDataSource;
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
import com.baomidou.lock.LockInfo;
|
import com.baomidou.lock.LockInfo;
|
||||||
import com.baomidou.lock.LockTemplate;
|
import com.baomidou.lock.LockTemplate;
|
||||||
|
import com.qiaoba.common.base.constant.BaseConstant;
|
||||||
import com.qiaoba.common.base.constant.TenantConstant;
|
import com.qiaoba.common.base.constant.TenantConstant;
|
||||||
import com.qiaoba.common.base.enums.DataBaseEnum;
|
import com.qiaoba.common.base.enums.DataBaseEnum;
|
||||||
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
||||||
@ -34,7 +37,6 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public class OnlineDatasourceMonitor {
|
public class OnlineDatasourceMonitor {
|
||||||
|
|
||||||
private static final String LOCK_KEY = "lock4j:onlineDatasourceMonitor";
|
private static final String LOCK_KEY = "lock4j:onlineDatasourceMonitor";
|
||||||
@ -47,6 +49,8 @@ public class OnlineDatasourceMonitor {
|
|||||||
private LockTemplate lockTemplate;
|
private LockTemplate lockTemplate;
|
||||||
@Resource
|
@Resource
|
||||||
private DynamicDatasourceService dynamicDatasourceService;
|
private DynamicDatasourceService dynamicDatasourceService;
|
||||||
|
@Resource
|
||||||
|
private BackupDatasourceContext backupDatasourceContext;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
@ -132,7 +136,7 @@ public class OnlineDatasourceMonitor {
|
|||||||
*/
|
*/
|
||||||
private Boolean backToPrimary(String tenantId) {
|
private Boolean backToPrimary(String tenantId) {
|
||||||
// 备用数据源
|
// 备用数据源
|
||||||
List<DynamicDataSource> dataSources = BackupDatasourceContext.get(tenantId);
|
List<DynamicDataSource> dataSources = backupDatasourceContext.get(tenantId);
|
||||||
if (CollUtil.isEmpty(dataSources)) {
|
if (CollUtil.isEmpty(dataSources)) {
|
||||||
log.error("租户:[{}]切换备用数据源失败, 原因: 没有备用数据源", tenantId);
|
log.error("租户:[{}]切换备用数据源失败, 原因: 没有备用数据源", tenantId);
|
||||||
return false;
|
return false;
|
||||||
@ -141,10 +145,15 @@ public class OnlineDatasourceMonitor {
|
|||||||
Integer backupIndex = null;
|
Integer backupIndex = null;
|
||||||
|
|
||||||
for (int i = 0; i < dataSources.size(); i++) {
|
for (int i = 0; i < dataSources.size(); i++) {
|
||||||
Object dynamicDataSource = DatasourceUtil.buildDataSource(tenantId, dataSources.get(i));
|
DynamicDataSource backDatasource = dataSources.get(i);
|
||||||
|
Object dynamicDataSource = DatasourceUtil.buildDataSource(tenantId, backDatasource);
|
||||||
// 不是空,说明备用数据源有用
|
// 不是空,说明备用数据源有用
|
||||||
if (Objects.nonNull(dynamicDataSource)) {
|
if (Objects.nonNull(dynamicDataSource)) {
|
||||||
DatasourceUtil.changePrimaryDatasource(tenantId, dynamicDataSource);
|
DatasourceUtil.changePrimaryDatasource(tenantId, dynamicDataSource);
|
||||||
|
backDatasource.setServerSn(BaseConstant.SERVER_SN);
|
||||||
|
redisService.convertAndSend("test", JSONUtil.toJsonStr(backDatasource));
|
||||||
|
// 延迟1s钟 等待其他服务器完成切换
|
||||||
|
ThreadUtil.sleep(1000);
|
||||||
backupIndex = i;
|
backupIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -160,6 +169,7 @@ public class OnlineDatasourceMonitor {
|
|||||||
}
|
}
|
||||||
// 备用数据源集合删除该数据源
|
// 备用数据源集合删除该数据源
|
||||||
dataSources.remove((int) backupIndex);
|
dataSources.remove((int) backupIndex);
|
||||||
|
backupDatasourceContext.addBackupMap(tenantId, dataSources);
|
||||||
log.info("租户:[{}]切换备用数据源成功, 现主数据ID: {}", tenantId, dynamicDataSource.getDatasourceId());
|
log.info("租户:[{}]切换备用数据源成功, 现主数据ID: {}", tenantId, dynamicDataSource.getDatasourceId());
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,6 +13,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DatasourceUtil
|
* DatasourceUtil
|
||||||
@ -52,15 +53,18 @@ public class DatasourceUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void removePrimaryDatasource(String tenantId) {
|
public static void removePrimaryDatasource(String tenantId) {
|
||||||
// 删除数据源类型
|
Object oldDatasource = PrimaryDatasourceContext.get(tenantId);
|
||||||
TenantDbTypeContext.remove(tenantId);
|
if (Objects.nonNull(oldDatasource)) {
|
||||||
// 关闭数据源
|
// 删除数据源类型
|
||||||
((DruidDataSource)PrimaryDatasourceContext.get(tenantId)).close();
|
TenantDbTypeContext.remove(tenantId);
|
||||||
// 删除数据源
|
// 关闭数据源
|
||||||
PrimaryDatasourceContext.remove(tenantId);
|
((DruidDataSource)oldDatasource).close();
|
||||||
// 刷新数据源
|
// 删除数据源
|
||||||
dynamicDataSourceContext.freshDataSource(PrimaryDatasourceContext.getAll());
|
PrimaryDatasourceContext.remove(tenantId);
|
||||||
log.info("移除租户[{}]主数据源成功",tenantId);
|
// 刷新数据源
|
||||||
|
dynamicDataSourceContext.freshDataSource(PrimaryDatasourceContext.getAll());
|
||||||
|
log.info("移除租户[{}]主数据源成功",tenantId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object buildDataSource(String tenantId, DynamicDataSource dynamicDataSource) {
|
public static Object buildDataSource(String tenantId, DynamicDataSource dynamicDataSource) {
|
||||||
|
@ -5,6 +5,7 @@ import com.qiaoba.api.tenant.entity.SysTenant;
|
|||||||
import com.qiaoba.api.tenant.entity.SysTenantDatasource;
|
import com.qiaoba.api.tenant.entity.SysTenantDatasource;
|
||||||
import com.qiaoba.api.tenant.entity.param.SysTenantDatasourceParam;
|
import com.qiaoba.api.tenant.entity.param.SysTenantDatasourceParam;
|
||||||
import com.qiaoba.api.tenant.entity.param.SysTenantParam;
|
import com.qiaoba.api.tenant.entity.param.SysTenantParam;
|
||||||
|
import com.qiaoba.common.base.constant.TenantConstant;
|
||||||
import com.qiaoba.common.base.enums.BaseEnum;
|
import com.qiaoba.common.base.enums.BaseEnum;
|
||||||
import com.qiaoba.common.base.exception.ServiceException;
|
import com.qiaoba.common.base.exception.ServiceException;
|
||||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||||
@ -36,9 +37,10 @@ public class DynamicDatasourceServiceImpl implements DynamicDatasourceService {
|
|||||||
Map<String, List<DynamicDataSource>> datasourceMap = new LinkedHashMap<>();
|
Map<String, List<DynamicDataSource>> datasourceMap = new LinkedHashMap<>();
|
||||||
List<SysTenant> sysTenants = sysTenantService.selectList(SysTenantParam.buildNormalSelectParam());
|
List<SysTenant> sysTenants = sysTenantService.selectList(SysTenantParam.buildNormalSelectParam());
|
||||||
for (SysTenant sysTenant : sysTenants) {
|
for (SysTenant sysTenant : sysTenants) {
|
||||||
List<SysTenantDatasource> datasourceList = sysTenantDatasourceService.selectList(new SysTenantDatasourceParam(sysTenant.getTenantId()));
|
if (!TenantConstant.DEFAULT_TENANT_ID.equals(sysTenant.getTenantId())) {
|
||||||
datasourceMap.put(sysTenant.getTenantId(), transformList(datasourceList));
|
List<SysTenantDatasource> datasourceList = sysTenantDatasourceService.selectList(new SysTenantDatasourceParam(sysTenant.getTenantId()));
|
||||||
|
datasourceMap.put(sysTenant.getTenantId(), transformList(datasourceList));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return datasourceMap;
|
return datasourceMap;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.qiaoba.module.tenant.service.impl;
|
|||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.alibaba.druid.pool.DruidDataSource;
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.qiaoba.api.tenant.entity.SysTenantDatasource;
|
import com.qiaoba.api.tenant.entity.SysTenantDatasource;
|
||||||
@ -17,6 +18,7 @@ import com.qiaoba.common.database.context.PrimaryDatasourceContext;
|
|||||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||||
import com.qiaoba.common.database.util.DatasourceUtil;
|
import com.qiaoba.common.database.util.DatasourceUtil;
|
||||||
import com.qiaoba.common.database.util.JdbcUtil;
|
import com.qiaoba.common.database.util.JdbcUtil;
|
||||||
|
import com.qiaoba.common.redis.service.RedisService;
|
||||||
import com.qiaoba.module.tenant.mapper.SysTenantDatasourceMapper;
|
import com.qiaoba.module.tenant.mapper.SysTenantDatasourceMapper;
|
||||||
import com.qiaoba.module.tenant.service.SysTenantDatasourceService;
|
import com.qiaoba.module.tenant.service.SysTenantDatasourceService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -38,6 +40,8 @@ import java.util.Objects;
|
|||||||
public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceService {
|
public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceService {
|
||||||
|
|
||||||
private final SysTenantDatasourceMapper sysTenantDatasourceMapper;
|
private final SysTenantDatasourceMapper sysTenantDatasourceMapper;
|
||||||
|
private final BackupDatasourceContext backupDatasourceContext;
|
||||||
|
private final RedisService redisService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SysTenantDatasource selectPrimary(String tenantId) {
|
public SysTenantDatasource selectPrimary(String tenantId) {
|
||||||
@ -71,7 +75,7 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic
|
|||||||
int result = sysTenantDatasourceMapper.insert(sysTenantDatasource);
|
int result = sysTenantDatasourceMapper.insert(sysTenantDatasource);
|
||||||
if (result > BaseConstant.HANDLE_ERROR) {
|
if (result > BaseConstant.HANDLE_ERROR) {
|
||||||
// 添加到备用数据源
|
// 添加到备用数据源
|
||||||
BackupDatasourceContext.addBackupMap(sysTenantDatasource.getTenantId(), dynamicDataSource);
|
backupDatasourceContext.addBackupMap(dynamicDataSource);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -87,7 +91,7 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic
|
|||||||
int result = sysTenantDatasourceMapper.updateById(sysTenantDatasource);
|
int result = sysTenantDatasourceMapper.updateById(sysTenantDatasource);
|
||||||
if (result > BaseConstant.HANDLE_ERROR) {
|
if (result > BaseConstant.HANDLE_ERROR) {
|
||||||
// 更新备用数据源
|
// 更新备用数据源
|
||||||
BackupDatasourceContext.updateBackupMap(sysTenantDatasource.getTenantId(), dynamicDataSource);
|
backupDatasourceContext.updateBackupMap(dynamicDataSource);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -138,12 +142,17 @@ public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceServic
|
|||||||
Object oldDataSource = PrimaryDatasourceContext.get(tenantId);
|
Object oldDataSource = PrimaryDatasourceContext.get(tenantId);
|
||||||
// 更新新得数据源为租户主要数据源
|
// 更新新得数据源为租户主要数据源
|
||||||
DatasourceUtil.changePrimaryDatasource(tenantId, dataSource);
|
DatasourceUtil.changePrimaryDatasource(tenantId, dataSource);
|
||||||
|
// 通知其他服务更换主数据源
|
||||||
|
dynamicDataSource.setServerSn(BaseConstant.SERVER_SN);
|
||||||
|
redisService.convertAndSend("test", JSONUtil.toJsonStr(dynamicDataSource));
|
||||||
|
// 从备用数据源中移除
|
||||||
|
backupDatasourceContext.deleteBackupMap(dynamicDataSource);
|
||||||
// 将老的租户数据源设置为备用
|
// 将老的租户数据源设置为备用
|
||||||
if (Objects.nonNull(oldDataSource)) {
|
if (Objects.nonNull(oldDataSource)) {
|
||||||
DruidDataSource druidDataSource = ((DruidDataSource) oldDataSource);
|
DruidDataSource druidDataSource = ((DruidDataSource) oldDataSource);
|
||||||
String ip = DataBaseEnum.getIp(druidDataSource.getUrl(), druidDataSource.getDriverClassName());
|
String ip = DataBaseEnum.getIp(druidDataSource.getUrl(), druidDataSource.getDriverClassName());
|
||||||
SysTenantDatasource sysTenantDatasource = sysTenantDatasourceMapper.selectByIp(tenantId, ip);
|
SysTenantDatasource sysTenantDatasource = sysTenantDatasourceMapper.selectByIp(tenantId, ip);
|
||||||
BackupDatasourceContext.addBackupMap(tenantId, transform(sysTenantDatasource));
|
backupDatasourceContext.addBackupMap(transform(sysTenantDatasource));
|
||||||
druidDataSource.close();
|
druidDataSource.close();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Reference in New Issue
Block a user