diff --git a/docker-compose.yml b/docker-compose.yml index 794354e..3880a0b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -185,7 +185,10 @@ services: REDIS_PORT: 6379 JAVA_OPTS: -Xms256m -Xmx512m # 租户 ID - 共享实例(空值,所有租户可用) - TENANT_ID: "" + # TENANT_ID: "" + + # 租户 ID - 多租户专属实例(逗号分隔,服务多个租户) + TENANT_ID: "VIP_001,VIP_002,VIP_003" ports: - "8100:8100" depends_on: @@ -227,8 +230,8 @@ services: REDIS_HOST: redis REDIS_PORT: 6379 JAVA_OPTS: -Xms256m -Xmx512m - # 租户 ID - VIP_001 专属实例 - TENANT_ID: "VIP_001" + # 租户 ID - 单租户专属实例 + TENANT_ID: "VIP_004" ports: - "8101:8101" depends_on: diff --git a/fund-common/src/main/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.java b/fund-common/src/main/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.java index 9f530a3..646aabb 100644 --- a/fund-common/src/main/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.java +++ b/fund-common/src/main/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.java @@ -14,6 +14,8 @@ import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBal import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import reactor.core.publisher.Mono; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; @@ -31,14 +33,15 @@ import java.util.stream.Collectors; * *

路由规则:

*
- * 1. 查找 metadata.tenant-id == 请求.tenantId 的实例 → VIP 专属实例
+ * 1. 查找 metadata.tenant-id 包含请求 tenantId 的实例 → 专属实例
  * 2. 找不到 → 回退到共享实例(metadata.tenant-id 为空)
  * 
* *

实例配置:

*
- * 共享实例: metadata.tenant-id = "" (空)
- * VIP 实例: metadata.tenant-id = "VIP_001"
+ * 共享实例:     metadata.tenant-id = "" (空)
+ * 单租户专属:   metadata.tenant-id = "VIP_001"
+ * 多租户专属:   metadata.tenant-id = "VIP_001,VIP_002,VIP_003"
  * 
*/ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalancer { @@ -130,9 +133,11 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc * *

路由策略:

*
    - *
  1. 优先选择租户专属实例(metadata.tenant-id 匹配)
  2. + *
  3. 优先选择租户专属实例(metadata.tenant-id 包含请求的 tenantId)
  4. *
  5. 回退到共享实例(metadata.tenant-id 为空或不存在)
  6. *
+ * + *

tenant-id 支持逗号分隔的多个租户 ID

*/ List filterByTenantId(List instances, String tenantId) { if (instances == null || instances.isEmpty()) { @@ -151,14 +156,13 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc if (tenantId != null && !tenantId.isEmpty()) { List tenantInstances = instances.stream() .filter(inst -> { - Map metadata = inst.getMetadata(); - if (metadata == null) return false; - String instanceTenantId = metadata.get("tenant-id"); - boolean match = tenantId.equals(instanceTenantId); - if (match) { - logger.debug("[TenantLB] 匹配租户专属实例:{}:{}", inst.getHost(), inst.getPort()); + List allowedTenants = parseTenantIds(inst); + if (allowedTenants.contains(tenantId)) { + logger.debug("[TenantLB] 匹配租户专属实例:{}:{} (允许租户: {})", + inst.getHost(), inst.getPort(), allowedTenants); + return true; } - return match; + return false; }) .collect(Collectors.toList()); @@ -169,21 +173,18 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc } // 检查是否启用回退到共享实例 - boolean fallbackEnabled = true; - if (routingProperties != null) { - fallbackEnabled = routingProperties.isFallbackToShared(); - } + boolean fallbackEnabled = routingProperties == null || routingProperties.isFallbackToShared(); if (!fallbackEnabled) { logger.warn("[TenantLB] 服务 {} 未启用共享实例回退,返回空列表", serviceId); - return List.of(); + return Collections.emptyList(); } // 回退到共享实例(metadata.tenant-id 为空或不存在) List sharedInstances = instances.stream() .filter(inst -> { - Map metadata = inst.getMetadata(); - return metadata == null || metadata.get("tenant-id") == null || metadata.get("tenant-id").isEmpty(); + List allowedTenants = parseTenantIds(inst); + return allowedTenants.isEmpty(); // 空列表表示共享实例 }) .collect(Collectors.toList()); @@ -191,6 +192,30 @@ public class TenantAwareLoadBalancer implements ReactorServiceInstanceLoadBalanc return sharedInstances; } + /** + * 解析实例元数据中的租户 ID 列表 + * + * @param inst 服务实例 + * @return 租户 ID 列表,空列表表示共享实例 + */ + private List parseTenantIds(ServiceInstance inst) { + Map metadata = inst.getMetadata(); + if (metadata == null) { + return Collections.emptyList(); + } + + String tenantIdStr = metadata.get("tenant-id"); + if (tenantIdStr == null || tenantIdStr.trim().isEmpty()) { + return Collections.emptyList(); + } + + // 支持逗号分隔的多个租户 ID + return Arrays.stream(tenantIdStr.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + /** * 从实例列表中选择一个(随机) */ diff --git a/fund-common/src/test/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.java b/fund-common/src/test/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.java index 90cacc7..ab4f87d 100644 --- a/fund-common/src/test/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.java +++ b/fund-common/src/test/java/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.java @@ -22,70 +22,140 @@ class TenantAwareLoadBalancerTest { } @Test - void testFilterByTenantId() { - // 创建测试实例 + void testSingleTenantInstance() { + // 单租户专属实例 List 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, Map.of("tenant-id", "VIP_001")), - 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.2", 8101, Map.of("tenant-id", "VIP_002")), + createInstance("fund-sys", "192.168.1.3", 8102, Collections.emptyMap()) // 共享实例 ); - // 测试 VIP_001 过滤 + // VIP_001 匹配专属实例 List vip1Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_001"); - assertEquals(2, vip1Instances.size()); - assertTrue(vip1Instances.stream().allMatch(i -> "VIP_001".equals(i.getMetadata().get("tenant-id")))); + assertEquals(1, vip1Instances.size()); + assertEquals("VIP_001", vip1Instances.get(0).getMetadata().get("tenant-id")); - // 测试 VIP_002 过滤 + // VIP_002 匹配专属实例 List vip2Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_002"); assertEquals(1, vip2Instances.size()); - // 测试未知租户(回退到共享实例) + // 未知租户回退到共享实例 List unknownInstances = invokeFilterByTenantId(loadBalancer, instances, "VIP_999"); assertEquals(1, unknownInstances.size()); assertFalse(unknownInstances.get(0).getMetadata().containsKey("tenant-id")); } @Test - void testMixedMode() { - // 混合模式:VIP 客户有专属实例,普通客户使用共享实例 + void testMultiTenantInstance() { + // 多租户专属实例(一个实例服务多个租户) List instances = Arrays.asList( - // 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-id", "VIP_001")), - // 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.11", 8111, Collections.emptyMap()) + // 实例1:服务 VIP_001, VIP_002, VIP_003 + createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001,VIP_002,VIP_003")), + // 实例2:服务 VIP_004, VIP_005 + createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-id", "VIP_004,VIP_005")), + // 实例3:共享实例 + createInstance("fund-sys", "192.168.1.10", 8110, Collections.emptyMap()) ); - // VIP_001 应该路由到专属实例 + // VIP_001 匹配实例1 List vip1Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_001"); - assertEquals(2, vip1Instances.size()); + assertEquals(1, vip1Instances.size()); + assertEquals("8100", String.valueOf(vip1Instances.get(0).getPort())); - // VIP_002 应该路由到专属实例 + // VIP_003 也匹配实例1 + List vip3Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_003"); + assertEquals(1, vip3Instances.size()); + assertEquals("8100", String.valueOf(vip3Instances.get(0).getPort())); + + // VIP_004 匹配实例2 + List vip4Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_004"); + assertEquals(1, vip4Instances.size()); + assertEquals("8101", String.valueOf(vip4Instances.get(0).getPort())); + + // VIP_005 匹配实例2 + List vip5Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_005"); + assertEquals(1, vip5Instances.size()); + assertEquals("8101", String.valueOf(vip5Instances.get(0).getPort())); + + // 未知租户回退到共享实例 + List unknownInstances = invokeFilterByTenantId(loadBalancer, instances, "VIP_999"); + assertEquals(1, unknownInstances.size()); + assertEquals("8110", String.valueOf(unknownInstances.get(0).getPort())); + } + + @Test + void testMixedMode() { + // 混合模式:单租户实例 + 多租户实例 + 共享实例 + List instances = Arrays.asList( + // 单租户专属实例 + createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001")), + // 多租户专属实例(VIP_002, VIP_003) + createInstance("fund-sys", "192.168.1.2", 8101, Map.of("tenant-id", "VIP_002,VIP_003")), + // 共享实例 + createInstance("fund-sys", "192.168.1.10", 8110, Collections.emptyMap()) + ); + + // VIP_001 走单租户实例 + List vip1Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_001"); + assertEquals(1, vip1Instances.size()); + assertEquals(8100, vip1Instances.get(0).getPort()); + + // VIP_002 走多租户实例 List vip2Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_002"); assertEquals(1, vip2Instances.size()); + assertEquals(8101, vip2Instances.get(0).getPort()); - // 普通租户应该路由到共享实例 + // VIP_003 也走多租户实例 + List vip3Instances = invokeFilterByTenantId(loadBalancer, instances, "VIP_003"); + assertEquals(1, vip3Instances.size()); + assertEquals(8101, vip3Instances.get(0).getPort()); + + // 普通租户走共享实例 List normalInstances = invokeFilterByTenantId(loadBalancer, instances, "NORMAL_001"); - assertEquals(2, normalInstances.size()); + assertEquals(1, normalInstances.size()); + assertEquals(8110, normalInstances.get(0).getPort()); + } + + @Test + void testSharedInstanceFallback() { + // 测试回退到共享实例 + List 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()), + createInstance("fund-sys", "192.168.1.3", 8102, Map.of("tenant-id", "")) // 空字符串也是共享 + ); + + // 未知租户应该找到 2 个共享实例 + List sharedInstances = invokeFilterByTenantId(loadBalancer, instances, "UNKNOWN"); + assertEquals(2, sharedInstances.size()); } @Test void testNullTenantId() { - // 测试空租户 ID(应该回退到共享实例) List 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()) ); + // null 租户 ID 应该回退到共享实例 List result = invokeFilterByTenantId(loadBalancer, instances, null); assertEquals(1, result.size()); assertFalse(result.get(0).getMetadata().containsKey("tenant-id")); } + @Test + void testTenantIdWithSpaces() { + // 测试带空格的租户 ID 配置 + List instances = Arrays.asList( + createInstance("fund-sys", "192.168.1.1", 8100, Map.of("tenant-id", "VIP_001, VIP_002 , VIP_003")) + ); + + // 应该能正确匹配(去除空格) + assertEquals(1, invokeFilterByTenantId(loadBalancer, instances, "VIP_001").size()); + assertEquals(1, invokeFilterByTenantId(loadBalancer, instances, "VIP_002").size()); + assertEquals(1, invokeFilterByTenantId(loadBalancer, instances, "VIP_003").size()); + } + // 辅助方法:创建服务实例 private ServiceInstance createInstance(String serviceId, String host, int port, Map metadata) { return new DefaultServiceInstance( diff --git a/fund-common/target/classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.class b/fund-common/target/classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.class index 09a93da..52fe28d 100644 Binary files a/fund-common/target/classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.class and b/fund-common/target/classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancer.class differ diff --git a/fund-common/target/surefire-reports/TEST-com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.xml b/fund-common/target/surefire-reports/TEST-com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.xml index d2d15a3..40bef56 100644 --- a/fund-common/target/surefire-reports/TEST-com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.xml +++ b/fund-common/target/surefire-reports/TEST-com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.xml @@ -1,5 +1,5 @@ - + @@ -13,7 +13,7 @@ - + @@ -29,7 +29,7 @@ - + @@ -56,20 +56,39 @@ - - + - - + - - + + + + + + + + + + \ No newline at end of file diff --git a/fund-common/target/surefire-reports/com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.txt b/fund-common/target/surefire-reports/com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.txt index 0d24cd0..5c9c301 100644 --- a/fund-common/target/surefire-reports/com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.txt +++ b/fund-common/target/surefire-reports/com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest.txt @@ -1,4 +1,4 @@ ------------------------------------------------------------------------------- Test set: 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 +Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.505 s -- in com.fundplatform.common.loadbalancer.TenantAwareLoadBalancerTest diff --git a/fund-common/target/test-classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.class b/fund-common/target/test-classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.class index ef1af4d..6493fa4 100644 Binary files a/fund-common/target/test-classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.class and b/fund-common/target/test-classes/com/fundplatform/common/loadbalancer/TenantAwareLoadBalancerTest.class differ diff --git a/logs/fund-sys-shared-8100.log b/logs/fund-sys-shared-8100.log index 87cc114..bbe95d6 100644 --- a/logs/fund-sys-shared-8100.log +++ b/logs/fund-sys-shared-8100.log @@ -1174,3 +1174,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$Worker.run(ThreadPoolExecutor.java:642) at java.base/java.lang.Thread.run(Thread.java:1583) +2026-02-19 21:36:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 === +2026-02-19 21:36:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool +2026-02-19 21:36: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) diff --git a/logs/fund-sys-tenant-vip001-8101.log b/logs/fund-sys-tenant-vip001-8101.log index 1bd03c0..395045c 100644 --- a/logs/fund-sys-tenant-vip001-8101.log +++ b/logs/fund-sys-tenant-vip001-8101.log @@ -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$Worker.run(ThreadPoolExecutor.java:642) at java.base/java.lang.Thread.run(Thread.java:1583) +2026-02-19 21:34:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 === +2026-02-19 21:34:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool +2026-02-19 21:34: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:39:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 === +2026-02-19 21:39:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool +2026-02-19 21:39: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) diff --git a/logs/fund-sys/error.log b/logs/fund-sys/error.log index 53e79df..1cad01a 100644 --- a/logs/fund-sys/error.log +++ b/logs/fund-sys/error.log @@ -1566,3 +1566,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$Worker.run(ThreadPoolExecutor.java:642) at java.base/java.lang.Thread.run(Thread.java:1583) +2026-02-19 21:34: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:36: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:39: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) diff --git a/logs/fund-sys/info.log b/logs/fund-sys/info.log index 1698e7a..13c83f5 100644 --- a/logs/fund-sys/info.log +++ b/logs/fund-sys/info.log @@ -340,3 +340,9 @@ 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 +2026-02-19 21:34:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 === +2026-02-19 21:34:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool +2026-02-19 21:36:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 === +2026-02-19 21:36:06.988 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool +2026-02-19 21:39:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - === HikariCP 连接池状态 === +2026-02-19 21:39:07.541 [scheduling-1] [] INFO com.fundplatform.sys.config.HikariMonitorConfig - 连接池名称: FundSysHikariPool