refactor: 简化路由逻辑,直接使用 tenantId 匹配实例
问题:tenantGroup 是 tenantId 的简单转换,造成冗余 tenantGroup = "TENANT_" + tenantId.toUpperCase() 解决方案: 1. 直接使用 tenantId 匹配实例 - 移除 tenantGroup 概念 - 负载均衡器直接匹配 metadata.tenant-id 2. 简化配置 - JWT 只需 tenantId 一个字段 - 实例元数据只有 tenant-id 3. 前端简化 - 请求头只需 X-Tenant-Id - 不再需要 X-Tenant-Group 路由规则: 共享实例: metadata.tenant-id = "" (空) VIP实例: metadata.tenant-id = "VIP_001" 匹配逻辑: 找到匹配实例 → VIP专属,找不到 → 共享实例
This commit is contained in:
parent
e52e2ba801
commit
5a2154c1a1
@ -184,8 +184,8 @@ services:
|
|||||||
REDIS_HOST: redis
|
REDIS_HOST: redis
|
||||||
REDIS_PORT: 6379
|
REDIS_PORT: 6379
|
||||||
JAVA_OPTS: -Xms256m -Xmx512m
|
JAVA_OPTS: -Xms256m -Xmx512m
|
||||||
# 租户组 - 共享实例(空值,所有租户可用)
|
# 租户 ID - 共享实例(空值,所有租户可用)
|
||||||
TENANT_GROUP: ""
|
TENANT_ID: ""
|
||||||
ports:
|
ports:
|
||||||
- "8100:8100"
|
- "8100:8100"
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -227,8 +227,8 @@ services:
|
|||||||
REDIS_HOST: redis
|
REDIS_HOST: redis
|
||||||
REDIS_PORT: 6379
|
REDIS_PORT: 6379
|
||||||
JAVA_OPTS: -Xms256m -Xmx512m
|
JAVA_OPTS: -Xms256m -Xmx512m
|
||||||
# 租户组 - VIP_001 专属实例
|
# 租户 ID - VIP_001 专属实例
|
||||||
TENANT_GROUP: "TENANT_VIP_001"
|
TENANT_ID: "VIP_001"
|
||||||
ports:
|
ports:
|
||||||
- "8101:8101"
|
- "8101:8101"
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -270,8 +270,8 @@ services:
|
|||||||
# REDIS_HOST: redis
|
# REDIS_HOST: redis
|
||||||
# REDIS_PORT: 6379
|
# REDIS_PORT: 6379
|
||||||
# JAVA_OPTS: -Xms256m -Xmx512m
|
# JAVA_OPTS: -Xms256m -Xmx512m
|
||||||
# # 租户组 - VIP_002 专属实例
|
# # 租户 ID - VIP_002 专属实例
|
||||||
# TENANT_GROUP: "TENANT_VIP_002"
|
# TENANT_ID: "VIP_002"
|
||||||
# ports:
|
# ports:
|
||||||
# - "8102:8102"
|
# - "8102:8102"
|
||||||
# depends_on:
|
# depends_on:
|
||||||
|
|||||||
@ -9,17 +9,17 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* 多租户路由配置属性
|
* 多租户路由配置属性
|
||||||
*
|
*
|
||||||
* <p>租户路由基于 Nacos 服务实例的 metadata.tenant-group 元数据进行匹配</p>
|
* <p>租户路由基于 Nacos 服务实例的 metadata.tenant-id 元数据进行匹配</p>
|
||||||
*
|
*
|
||||||
* <h3>工作原理:</h3>
|
* <h3>工作原理:</h3>
|
||||||
* <pre>
|
* <pre>
|
||||||
* 1. 服务实例注册到 Nacos 时,在 metadata 中声明 tenant-group
|
* 1. 服务实例注册到 Nacos 时,在 metadata 中声明 tenant-id
|
||||||
* - 共享实例: tenant-group 为空或不存在
|
* - 共享实例: tenant-id 为空或不存在
|
||||||
* - VIP 实例: tenant-group = "TENANT_VIP_001"
|
* - VIP 实例: tenant-id = "VIP_001"
|
||||||
*
|
*
|
||||||
* 2. Gateway 从请求中提取租户组,负载均衡器匹配实例元数据
|
* 2. 负载均衡器根据请求中的 tenantId 匹配实例
|
||||||
* - 请求 tenantGroup = "TENANT_VIP_001" → 路由到匹配的 VIP 实例
|
* - 找到匹配的 tenant-id → VIP 专属实例
|
||||||
* - 无匹配实例 → 回退到共享实例
|
* - 找不到 → 回退到共享实例
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <h3>配置示例:</h3>
|
* <h3>配置示例:</h3>
|
||||||
@ -28,11 +28,7 @@ import java.util.List;
|
|||||||
* routing:
|
* routing:
|
||||||
* enabled: true
|
* enabled: true
|
||||||
* tenant-header: X-Tenant-Id
|
* tenant-header: X-Tenant-Id
|
||||||
* default-tenant-id: "1"
|
|
||||||
* fallback-to-shared: true
|
* fallback-to-shared: true
|
||||||
* shared-services:
|
|
||||||
* - fund-gateway
|
|
||||||
* - fund-report
|
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@ -45,12 +41,6 @@ public class TenantRoutingProperties {
|
|||||||
/** 租户 ID 请求头 */
|
/** 租户 ID 请求头 */
|
||||||
private String tenantHeader = "X-Tenant-Id";
|
private String tenantHeader = "X-Tenant-Id";
|
||||||
|
|
||||||
/** 租户组请求头 */
|
|
||||||
private String tenantGroupHeader = "X-Tenant-Group";
|
|
||||||
|
|
||||||
/** 服务组分隔符 */
|
|
||||||
private String groupSeparator = "TENANT_";
|
|
||||||
|
|
||||||
/** 默认租户 ID(当未指定时使用) */
|
/** 默认租户 ID(当未指定时使用) */
|
||||||
private String defaultTenantId = "1";
|
private String defaultTenantId = "1";
|
||||||
|
|
||||||
@ -80,22 +70,6 @@ public class TenantRoutingProperties {
|
|||||||
this.tenantHeader = tenantHeader;
|
this.tenantHeader = tenantHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTenantGroupHeader() {
|
|
||||||
return tenantGroupHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTenantGroupHeader(String tenantGroupHeader) {
|
|
||||||
this.tenantGroupHeader = tenantGroupHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGroupSeparator() {
|
|
||||||
return groupSeparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroupSeparator(String groupSeparator) {
|
|
||||||
this.groupSeparator = groupSeparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDefaultTenantId() {
|
public String getDefaultTenantId() {
|
||||||
return defaultTenantId;
|
return defaultTenantId;
|
||||||
}
|
}
|
||||||
@ -120,24 +94,8 @@ public class TenantRoutingProperties {
|
|||||||
this.fallbackToShared = fallbackToShared;
|
this.fallbackToShared = fallbackToShared;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建租户组名称
|
|
||||||
*
|
|
||||||
* @param tenantId 租户 ID
|
|
||||||
* @return 租户组名称,如 "TENANT_VIP_001"
|
|
||||||
*/
|
|
||||||
public String buildTenantGroup(String tenantId) {
|
|
||||||
if (tenantId == null || tenantId.isEmpty()) {
|
|
||||||
return "DEFAULT";
|
|
||||||
}
|
|
||||||
return getGroupSeparator() + tenantId.toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断是否为共享服务
|
* 判断是否为共享服务
|
||||||
*
|
|
||||||
* @param serviceName 服务名
|
|
||||||
* @return 是否为共享服务
|
|
||||||
*/
|
*/
|
||||||
public boolean isSharedService(String serviceName) {
|
public boolean isSharedService(String serviceName) {
|
||||||
return sharedServices != null && sharedServices.contains(serviceName);
|
return sharedServices != null && sharedServices.contains(serviceName);
|
||||||
|
|||||||
@ -24,17 +24,21 @@ import java.util.stream.Collectors;
|
|||||||
*
|
*
|
||||||
* <p>根据租户 ID 进行服务实例路由,支持:</p>
|
* <p>根据租户 ID 进行服务实例路由,支持:</p>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>租户专属实例优先</li>
|
* <li>租户专属实例优先(metadata.tenant-id 匹配)</li>
|
||||||
* <li>共享实例回退</li>
|
* <li>共享实例回退(metadata.tenant-id 为空)</li>
|
||||||
* <li>轮询负载均衡</li>
|
* <li>随机负载均衡</li>
|
||||||
* <li>混合模式(VIP 租户专属 + 普通租户共享)</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h3>使用场景</h3>
|
* <h3>路由规则:</h3>
|
||||||
* <pre>
|
* <pre>
|
||||||
* 混合模式部署:
|
* 1. 查找 metadata.tenant-id == 请求.tenantId 的实例 → VIP 专属实例
|
||||||
* - VIP 客户:独立部署服务实例(带 tenant-group 标签)
|
* 2. 找不到 → 回退到共享实例(metadata.tenant-id 为空)
|
||||||
* - 普通客户:共享服务实例(无 tenant-group 标签)
|
* </pre>
|
||||||
|
*
|
||||||
|
* <h3>实例配置:</h3>
|
||||||
|
* <pre>
|
||||||
|
* 共享实例: metadata.tenant-id = "" (空)
|
||||||
|
* VIP 实例: metadata.tenant-id = "VIP_001"
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalancer {
|
public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalancer {
|
||||||
@ -61,9 +65,8 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
|
|||||||
return getDefaultResponse();
|
return getDefaultResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从请求上下文获取租户信息
|
// 从请求上下文获取租户 ID
|
||||||
String tenantId = getTenantIdFromRequest(request);
|
String tenantId = getTenantIdFromRequest(request);
|
||||||
String tenantGroup = buildTenantGroup(tenantId);
|
|
||||||
|
|
||||||
if (supplierProvider == null) {
|
if (supplierProvider == null) {
|
||||||
logger.warn("[TenantLB] ServiceInstanceListSupplier 未提供");
|
logger.warn("[TenantLB] ServiceInstanceListSupplier 未提供");
|
||||||
@ -77,7 +80,7 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
|
|||||||
}
|
}
|
||||||
|
|
||||||
return supplier.get().next()
|
return supplier.get().next()
|
||||||
.map(instances -> filterByTenantGroup(instances, tenantGroup))
|
.map(instances -> filterByTenantId(instances, tenantId))
|
||||||
.map(this::getInstanceResponse);
|
.map(this::getInstanceResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,34 +126,20 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建租户组名称
|
* 根据租户 ID 过滤服务实例
|
||||||
*/
|
|
||||||
String buildTenantGroup(String tenantId) {
|
|
||||||
if (routingProperties != null) {
|
|
||||||
return routingProperties.buildTenantGroup(tenantId);
|
|
||||||
}
|
|
||||||
// 默认逻辑
|
|
||||||
if (tenantId == null || tenantId.isEmpty()) {
|
|
||||||
return "DEFAULT";
|
|
||||||
}
|
|
||||||
return "TENANT_" + tenantId.toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据租户组过滤服务实例
|
|
||||||
*
|
*
|
||||||
* <p>路由策略:</p>
|
* <p>路由策略:</p>
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>优先选择租户专属实例(metadata.tenant-group 匹配)</li>
|
* <li>优先选择租户专属实例(metadata.tenant-id 匹配)</li>
|
||||||
* <li>回退到共享实例(无 tenant-group 标签)</li>
|
* <li>回退到共享实例(metadata.tenant-id 为空或不存在)</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*/
|
*/
|
||||||
List<ServiceInstance> filterByTenantGroup(List<ServiceInstance> instances, String tenantGroup) {
|
List<ServiceInstance> filterByTenantId(List<ServiceInstance> instances, String tenantId) {
|
||||||
if (instances == null || instances.isEmpty()) {
|
if (instances == null || instances.isEmpty()) {
|
||||||
return instances;
|
return instances;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("[TenantLB] 租户组:{},候选实例数:{}", tenantGroup, instances.size());
|
logger.debug("[TenantLB] 租户 ID:{},候选实例数:{}", tenantId, instances.size());
|
||||||
|
|
||||||
// 检查是否为共享服务(不需要租户路由)
|
// 检查是否为共享服务(不需要租户路由)
|
||||||
if (routingProperties != null && routingProperties.isSharedService(serviceId)) {
|
if (routingProperties != null && routingProperties.isSharedService(serviceId)) {
|
||||||
@ -159,23 +148,25 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 优先选择租户专属实例
|
// 优先选择租户专属实例
|
||||||
|
if (tenantId != null && !tenantId.isEmpty()) {
|
||||||
List<ServiceInstance> tenantInstances = instances.stream()
|
List<ServiceInstance> tenantInstances = instances.stream()
|
||||||
.filter(inst -> {
|
.filter(inst -> {
|
||||||
Map<String, String> metadata = inst.getMetadata();
|
Map<String, String> metadata = inst.getMetadata();
|
||||||
if (metadata == null) return false;
|
if (metadata == null) return false;
|
||||||
String instanceGroup = metadata.get("tenant-group");
|
String instanceTenantId = metadata.get("tenant-id");
|
||||||
boolean match = tenantGroup.equals(instanceGroup);
|
boolean match = tenantId.equals(instanceTenantId);
|
||||||
if (match) {
|
if (match) {
|
||||||
logger.debug("[TenantLB] 匹配租户实例:{}:{}", inst.getHost(), inst.getPort());
|
logger.debug("[TenantLB] 匹配租户专属实例:{}:{}", inst.getHost(), inst.getPort());
|
||||||
}
|
}
|
||||||
return match;
|
return match;
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (!tenantInstances.isEmpty()) {
|
if (!tenantInstances.isEmpty()) {
|
||||||
logger.info("[TenantLB] 找到 {} 个租户专属实例,租户组:{}", tenantInstances.size(), tenantGroup);
|
logger.info("[TenantLB] 找到 {} 个租户专属实例,租户 ID:{}", tenantInstances.size(), tenantId);
|
||||||
return tenantInstances;
|
return tenantInstances;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否启用回退到共享实例
|
// 检查是否启用回退到共享实例
|
||||||
boolean fallbackEnabled = true;
|
boolean fallbackEnabled = true;
|
||||||
@ -188,11 +179,11 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
|
|||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回退到共享实例(无 tenant-group 标签)
|
// 回退到共享实例(metadata.tenant-id 为空或不存在)
|
||||||
List<ServiceInstance> sharedInstances = instances.stream()
|
List<ServiceInstance> sharedInstances = instances.stream()
|
||||||
.filter(inst -> {
|
.filter(inst -> {
|
||||||
Map<String, String> metadata = inst.getMetadata();
|
Map<String, String> metadata = inst.getMetadata();
|
||||||
return metadata == null || !metadata.containsKey("tenant-group");
|
return metadata == null || metadata.get("tenant-id") == null || metadata.get("tenant-id").isEmpty();
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
@ -209,10 +200,9 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc
|
|||||||
return new EmptyResponse();
|
return new EmptyResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 随机选择
|
|
||||||
int index = ThreadLocalRandom.current().nextInt(instances.size());
|
int index = ThreadLocalRandom.current().nextInt(instances.size());
|
||||||
ServiceInstance chosen = instances.get(index);
|
ServiceInstance chosen = instances.get(index);
|
||||||
logger.info("[TenantLB] 选择实例:{}:{} (index={})", chosen.getHost(), chosen.getPort(), index);
|
logger.info("[TenantLB] 选择实例:{}:{}", chosen.getHost(), chosen.getPort());
|
||||||
return new DefaultResponse(chosen);
|
return new DefaultResponse(chosen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,21 +13,21 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* Nacos 服务注册元数据配置
|
* Nacos 服务注册元数据配置
|
||||||
*
|
*
|
||||||
* <p>服务启动时自动注册租户组标签到 Nacos,支持租户感知的负载均衡</p>
|
* <p>服务启动时自动注册租户 ID 到 Nacos,支持租户感知的负载均衡</p>
|
||||||
*
|
*
|
||||||
* <h3>租户组配置规则:</h3>
|
* <h3>租户 ID 配置规则:</h3>
|
||||||
* <pre>
|
* <pre>
|
||||||
* 共享实例: tenant-group 为空(或不配置)→ 所有租户都可使用
|
* 共享实例: tenant-id 为空(或不配置)→ 所有租户都可使用
|
||||||
* VIP实例: tenant-group = "TENANT_VIP_001" → 仅该租户组可用
|
* VIP实例: tenant-id = "VIP_001" → 仅该租户可用
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <h3>配置方式:</h3>
|
* <h3>配置方式:</h3>
|
||||||
* <pre>
|
* <pre>
|
||||||
* # 方式1: 环境变量(Docker/K8s 推荐)
|
* # 方式1: 环境变量(Docker/K8s 推荐)
|
||||||
* TENANT_GROUP=TENANT_VIP_001
|
* TENANT_ID=VIP_001
|
||||||
*
|
*
|
||||||
* # 方式2: 配置文件
|
* # 方式2: 配置文件
|
||||||
* spring.cloud.nacos.discovery.metadata.tenant-group=TENANT_VIP_001
|
* spring.cloud.nacos.discovery.metadata.tenant-id=VIP_001
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -40,11 +40,11 @@ public class NacosMetadataConfig {
|
|||||||
private String applicationName;
|
private String applicationName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户组,优先级:环境变量 > 配置文件
|
* 租户 ID,优先级:环境变量 > 配置文件
|
||||||
* 为空表示共享实例,供所有租户使用
|
* 为空表示共享实例,供所有租户使用
|
||||||
*/
|
*/
|
||||||
@Value("${TENANT_GROUP:${spring.cloud.nacos.discovery.metadata.tenant-group:}}")
|
@Value("${TENANT_ID:${spring.cloud.nacos.discovery.metadata.tenant-id:}}")
|
||||||
private String tenantGroup;
|
private String tenantId;
|
||||||
|
|
||||||
private final Registration registration;
|
private final Registration registration;
|
||||||
|
|
||||||
@ -57,21 +57,21 @@ public class NacosMetadataConfig {
|
|||||||
if (registration != null && registration.getMetadata() != null) {
|
if (registration != null && registration.getMetadata() != null) {
|
||||||
Map<String, String> metadata = registration.getMetadata();
|
Map<String, String> metadata = registration.getMetadata();
|
||||||
|
|
||||||
// 添加租户组元数据
|
// 添加租户 ID 元数据
|
||||||
if (tenantGroup != null && !tenantGroup.isEmpty()) {
|
if (tenantId != null && !tenantId.isEmpty()) {
|
||||||
metadata.put("tenant-group", tenantGroup);
|
metadata.put("tenant-id", tenantId);
|
||||||
logger.info("[Nacos] {} 注册为 VIP 专属实例,租户组:{}", applicationName, tenantGroup);
|
logger.info("[Nacos] {} 注册为 VIP 专属实例,租户 ID:{}", applicationName, tenantId);
|
||||||
} else {
|
} else {
|
||||||
logger.info("[Nacos] {} 注册为共享实例,供所有租户使用", applicationName);
|
logger.info("[Nacos] {} 注册为共享实例,供所有租户使用", applicationName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTenantGroup() {
|
public String getTenantId() {
|
||||||
return tenantGroup;
|
return tenantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVipInstance() {
|
public boolean isVipInstance() {
|
||||||
return tenantGroup != null && !tenantGroup.isEmpty();
|
return tenantId != null && !tenantId.isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,13 +4,10 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||||
import org.springframework.cloud.client.ServiceInstance;
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
import org.springframework.cloud.client.loadbalancer.Request;
|
|
||||||
import org.springframework.cloud.client.loadbalancer.RequestData;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户负载均衡器测试
|
* 租户负载均衡器测试
|
||||||
@ -25,73 +22,70 @@ class TenantAwareLoadBalancerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testBuildTenantGroup() {
|
void testFilterByTenantId() {
|
||||||
// 测试租户组名称构建
|
|
||||||
String group1 = invokeBuildTenantGroup(loadBalancer, "1");
|
|
||||||
assertEquals("TENANT_1", group1);
|
|
||||||
|
|
||||||
String group2 = invokeBuildTenantGroup(loadBalancer, "tenant_001");
|
|
||||||
assertEquals("TENANT_TENANT_001", group2);
|
|
||||||
|
|
||||||
String group3 = invokeBuildTenantGroup(loadBalancer, null);
|
|
||||||
assertEquals("DEFAULT", group3);
|
|
||||||
|
|
||||||
String group4 = invokeBuildTenantGroup(loadBalancer, "");
|
|
||||||
assertEquals("DEFAULT", group4);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testFilterByTenantGroup() {
|
|
||||||
// 创建测试实例
|
// 创建测试实例
|
||||||
List<ServiceInstance> instances = Arrays.asList(
|
List<ServiceInstance> instances = Arrays.asList(
|
||||||
createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-group", "TENANT_1")),
|
createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001")),
|
||||||
createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-group", "TENANT_1")),
|
createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-id", "VIP_001")),
|
||||||
createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-group", "TENANT_2")),
|
createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-id", "VIP_002")),
|
||||||
createInstance("fund-sys", "192.168.1.4", 8103, Collections.emptyMap()) // 共享实例
|
createInstance("fund-sys", "192.168.1.4", 8103, Collections.emptyMap()) // 共享实例
|
||||||
);
|
);
|
||||||
|
|
||||||
// 测试租户 1 过滤
|
// 测试 VIP_001 过滤
|
||||||
List<ServiceInstance> tenant1Instances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_1");
|
List<ServiceInstance> vip1Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_001");
|
||||||
assertEquals(2, tenant1Instances.size());
|
assertEquals(2, vip1Instances.size());
|
||||||
assertTrue(tenant1Instances.stream().allMatch(i -> "TENANT_1".equals(i.getMetadata().get("tenant-group"))));
|
assertTrue(vip1Instances.stream().allMatch(i -> "VIP_001".equals(i.getMetadata().get("tenant-id"))));
|
||||||
|
|
||||||
// 测试租户 2 过滤
|
// 测试 VIP_002 过滤
|
||||||
List<ServiceInstance> tenant2Instances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_2");
|
List<ServiceInstance> vip2Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_002");
|
||||||
assertEquals(1, tenant2Instances.size());
|
assertEquals(1, vip2Instances.size());
|
||||||
|
|
||||||
// 测试未知租户(回退到共享实例)
|
// 测试未知租户(回退到共享实例)
|
||||||
List<ServiceInstance> unknownTenantInstances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_UNKNOWN");
|
List<ServiceInstance> unknownInstances = invokeFilterByTenantId(loadBalancer, instances, "VIP_999");
|
||||||
assertEquals(1, unknownTenantInstances.size());
|
assertEquals(1, unknownInstances.size());
|
||||||
assertFalse(unknownTenantInstances.get(0).getMetadata().containsKey("tenant-group"));
|
assertFalse(unknownInstances.get(0).getMetadata().containsKey("tenant-id"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testMixedMode() {
|
void testMixedMode() {
|
||||||
// 混合模式:VIP 客户有专属实例,普通客户使用共享实例
|
// 混合模式:VIP 客户有专属实例,普通客户使用共享实例
|
||||||
List<ServiceInstance> instances = Arrays.asList(
|
List<ServiceInstance> instances = Arrays.asList(
|
||||||
// VIP 租户 001 的专属实例
|
// VIP_001 的专属实例
|
||||||
createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-group", "TENANT_VIP_001")),
|
createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001")),
|
||||||
createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-group", "TENANT_VIP_001")),
|
createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-id", "VIP_001")),
|
||||||
// VIP 租户 002 的专属实例
|
// VIP_002 的专属实例
|
||||||
createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-group", "TENANT_VIP_002")),
|
createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-id", "VIP_002")),
|
||||||
// 共享实例(普通租户使用)
|
// 共享实例(普通租户使用)
|
||||||
createInstance("fund-sys", "192.168.1.10", 8110, Collections.emptyMap()),
|
createInstance("fund-sys", "192.168.1.10", 8110, Collections.emptyMap()),
|
||||||
createInstance("fund-sys", "192.168.1.11", 8111, Collections.emptyMap())
|
createInstance("fund-sys", "192.168.1.11", 8111, Collections.emptyMap())
|
||||||
);
|
);
|
||||||
|
|
||||||
// VIP 租户 001 应该路由到专属实例
|
// VIP_001 应该路由到专属实例
|
||||||
List<ServiceInstance> vip1Instances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_VIP_001");
|
List<ServiceInstance> vip1Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_001");
|
||||||
assertEquals(2, vip1Instances.size());
|
assertEquals(2, vip1Instances.size());
|
||||||
|
|
||||||
// VIP 租户 002 应该路由到专属实例
|
// VIP_002 应该路由到专属实例
|
||||||
List<ServiceInstance> vip2Instances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_VIP_002");
|
List<ServiceInstance> vip2Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_002");
|
||||||
assertEquals(1, vip2Instances.size());
|
assertEquals(1, vip2Instances.size());
|
||||||
|
|
||||||
// 普通租户应该路由到共享实例
|
// 普通租户应该路由到共享实例
|
||||||
List<ServiceInstance> normalInstances = invokeFilterByTenantGroup(loadBalancer, instances, "TENANT_NORMAL");
|
List<ServiceInstance> normalInstances = invokeFilterByTenantId(loadBalancer, instances, "NORMAL_001");
|
||||||
assertEquals(2, normalInstances.size());
|
assertEquals(2, normalInstances.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNullTenantId() {
|
||||||
|
// 测试空租户 ID(应该回退到共享实例)
|
||||||
|
List<ServiceInstance> instances = Arrays.asList(
|
||||||
|
createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001")),
|
||||||
|
createInstance("fund-sys", "192.168.1.2", 8101, Collections.emptyMap())
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ServiceInstance> result = invokeFilterByTenantId(loadBalancer, instances, null);
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
assertFalse(result.get(0).getMetadata().containsKey("tenant-id"));
|
||||||
|
}
|
||||||
|
|
||||||
// 辅助方法:创建服务实例
|
// 辅助方法:创建服务实例
|
||||||
private ServiceInstance createInstance(String serviceId, String host, int port, Map<String, String> metadata) {
|
private ServiceInstance createInstance(String serviceId, String host, int port, Map<String, String> metadata) {
|
||||||
return new DefaultServiceInstance(
|
return new DefaultServiceInstance(
|
||||||
@ -104,24 +98,13 @@ class TenantAwareLoadBalancerTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助方法:调用私有方法 buildTenantGroup
|
// 辅助方法:调用私有方法 filterByTenantId
|
||||||
private String invokeBuildTenantGroup(TenantAwareLoadBalancer lb, String tenantId) {
|
|
||||||
try {
|
|
||||||
var method = TenantAwareLoadBalancer.class.getDeclaredMethod("buildTenantGroup", String.class);
|
|
||||||
method.setAccessible(true);
|
|
||||||
return (String) method.invoke(lb, tenantId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助方法:调用私有方法 filterByTenantGroup
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private List<ServiceInstance> invokeFilterByTenantGroup(TenantAwareLoadBalancer lb, List<ServiceInstance> instances, String tenantGroup) {
|
private List<ServiceInstance> invokeFilterByTenantId(TenantAwareLoadBalancer lb, List<ServiceInstance> instances, String tenantId) {
|
||||||
try {
|
try {
|
||||||
var method = TenantAwareLoadBalancer.class.getDeclaredMethod("filterByTenantGroup", List.class, String.class);
|
var method = TenantAwareLoadBalancer.class.getDeclaredMethod("filterByTenantId", List.class, String.class);
|
||||||
method.setAccessible(true);
|
method.setAccessible(true);
|
||||||
return (List<ServiceInstance>) method.invoke(lb, instances, tenantGroup);
|
return (List<ServiceInstance>) method.invoke(lb, instances, tenantId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
|||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
Test set: com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest
|
Test set: com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.310 s -- in com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest
|
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.206 s -- in com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest
|
||||||
|
|||||||
Binary file not shown.
@ -14,9 +14,9 @@ spring:
|
|||||||
username: nacos
|
username: nacos
|
||||||
password: nacos
|
password: nacos
|
||||||
# 租户路由元数据
|
# 租户路由元数据
|
||||||
# tenant-group: 空值=共享实例,有值=VIP专属实例
|
# tenant-id: 空值=共享实例,有值=VIP专属实例
|
||||||
metadata:
|
metadata:
|
||||||
tenant-group: ${TENANT_GROUP:}
|
tenant-id: ${TENANT_ID:}
|
||||||
|
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
|||||||
@ -14,9 +14,9 @@ spring:
|
|||||||
username: nacos
|
username: nacos
|
||||||
password: nacos
|
password: nacos
|
||||||
# 租户路由元数据
|
# 租户路由元数据
|
||||||
# tenant-group: 空值=共享实例,有值=VIP专属实例
|
# tenant-id: 空值=共享实例,有值=VIP专属实例
|
||||||
metadata:
|
metadata:
|
||||||
tenant-group: ${TENANT_GROUP:}
|
tenant-id: ${TENANT_ID:}
|
||||||
|
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
|||||||
@ -1138,3 +1138,39 @@ java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBea
|
|||||||
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||||
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||||
at java.base/java.lang.Thread.run(Thread.java:1583)
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
2026-02-19 21:26:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
|
||||||
|
2026-02-19 21:26:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
||||||
|
2026-02-19 21:26:06.989 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
|
||||||
|
java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
|
||||||
|
at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
|
||||||
|
at io.micrometer.observation.Observation.observe(Observation.java:499)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
|
||||||
|
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
|
||||||
|
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
|
||||||
|
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
|
||||||
|
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
2026-02-19 21:31:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
|
||||||
|
2026-02-19 21:31:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
||||||
|
2026-02-19 21:31:06.988 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
|
||||||
|
java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
|
||||||
|
at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
|
||||||
|
at io.micrometer.observation.Observation.observe(Observation.java:499)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
|
||||||
|
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
|
||||||
|
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
|
||||||
|
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
|
||||||
|
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|||||||
@ -1120,3 +1120,21 @@ java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBea
|
|||||||
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||||
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||||
at java.base/java.lang.Thread.run(Thread.java:1583)
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
2026-02-19 21:29:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
|
||||||
|
2026-02-19 21:29:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
||||||
|
2026-02-19 21:29:07.541 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
|
||||||
|
java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
|
||||||
|
at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
|
||||||
|
at io.micrometer.observation.Observation.observe(Observation.java:499)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
|
||||||
|
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
|
||||||
|
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
|
||||||
|
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
|
||||||
|
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|||||||
@ -1518,3 +1518,51 @@ java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBea
|
|||||||
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||||
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||||
at java.base/java.lang.Thread.run(Thread.java:1583)
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
2026-02-19 21:26:06.989 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
|
||||||
|
java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
|
||||||
|
at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
|
||||||
|
at io.micrometer.observation.Observation.observe(Observation.java:499)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
|
||||||
|
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
|
||||||
|
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
|
||||||
|
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
|
||||||
|
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
2026-02-19 21:29:07.541 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
|
||||||
|
java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
|
||||||
|
at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
|
||||||
|
at io.micrometer.observation.Observation.observe(Observation.java:499)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
|
||||||
|
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
|
||||||
|
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
|
||||||
|
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
|
||||||
|
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
2026-02-19 21:31:06.988 [scheduling-1] [] ERROR o.s.s.support.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
|
||||||
|
java.lang.NullPointerException: Cannot invoke "com.zaxxer.hikari.HikariPoolMXBean.getActiveConnections()" because the return value of "com.zaxxer.hikari.HikariDataSource.getHikariPoolMXBean()" is null
|
||||||
|
at com.fundplatform.sys.config.HikariMonitorConfig.monitorHikariPool(HikariMonitorConfig.java:38)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
|
||||||
|
at io.micrometer.observation.Observation.observe(Observation.java:499)
|
||||||
|
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)
|
||||||
|
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
|
||||||
|
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
|
||||||
|
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
|
||||||
|
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
|
||||||
|
at java.base/java.lang.Thread.run(Thread.java:1583)
|
||||||
|
|||||||
@ -334,3 +334,9 @@
|
|||||||
2026-02-19 21:21:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
2026-02-19 21:21:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
||||||
2026-02-19 21:24:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
|
2026-02-19 21:24:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
|
||||||
2026-02-19 21:24:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
2026-02-19 21:24:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
||||||
|
2026-02-19 21:26:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
|
||||||
|
2026-02-19 21:26:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
||||||
|
2026-02-19 21:29:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
|
||||||
|
2026-02-19 21:29:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
||||||
|
2026-02-19 21:31:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 ===
|
||||||
|
2026-02-19 21:31:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user