feat: Docker Compose配置多租户混合模式负载均衡

## 主要改动

### docker-compose.yml
- fund-sys 服务改为混合模式部署:
  - fund-sys-shared: 共享实例(8100端口),供所有租户使用
  - fund-sys-vip001: VIP_001专属实例(8101端口)
  - 预留VIP_002模板(注释状态)
- 添加TENANT_ID、TENANT_GROUP环境变量

### NacosMetadataConfig
- 支持从环境变量读取租户元数据(优先级最高)
- 动态注册租户标签到Nacos
- 区分共享实例和VIP专属实例

### Prometheus配置
- 监控共享实例和VIP实例
- 添加tenant_mode、tenant_group标签

## 混合模式说明
- 共享实例(TENANT_GROUP为空): 所有普通租户请求路由到此类实例
- VIP实例(TENANT_GROUP有值): VIP租户请求路由到专属实例
This commit is contained in:
zhangjf 2026-02-19 20:04:21 +08:00
parent 109ae29474
commit 5843cc050e
3 changed files with 170 additions and 32 deletions

View File

@ -1,5 +1,6 @@
# 资金服务平台 - Docker Compose 编排配置 # 资金服务平台 - Docker Compose 编排配置
# 版本: 1.0 # 版本: 1.1
# 支持多租户混合模式VIP专属实例 + 普通租户共享实例
version: '3.8' version: '3.8'
@ -158,14 +159,16 @@ services:
networks: networks:
- fund-network - fund-network
# 系统服务 # ==================== 系统服务(多租户混合模式) ====================
# 系统服务 - 共享实例(供所有租户使用)
fund-sys: fund-sys:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
args: args:
MODULE: fund-sys MODULE: fund-sys
container_name: fund-sys container_name: fund-sys-shared
restart: unless-stopped restart: unless-stopped
environment: environment:
SERVER_PORT: 8100 SERVER_PORT: 8100
@ -181,6 +184,9 @@ services:
REDIS_HOST: redis REDIS_HOST: redis
REDIS_PORT: 6379 REDIS_PORT: 6379
JAVA_OPTS: -Xms256m -Xmx512m JAVA_OPTS: -Xms256m -Xmx512m
# 租户元数据 - 共享实例(无特定租户组,所有租户可用)
TENANT_ID: "1"
TENANT_GROUP: ""
ports: ports:
- "8100:8100" - "8100:8100"
depends_on: depends_on:
@ -199,6 +205,94 @@ services:
networks: networks:
- fund-network - fund-network
# 系统服务 - VIP_001 专属实例
fund-sys-vip001:
build:
context: .
dockerfile: Dockerfile
args:
MODULE: fund-sys
container_name: fund-sys-vip001
restart: unless-stopped
environment:
SERVER_PORT: 8101
SPRING_PROFILES_ACTIVE: docker
NACOS_SERVER_ADDR: nacos:8848
NACOS_USERNAME: nacos
NACOS_PASSWORD: nacos
MYSQL_HOST: mysql
MYSQL_PORT: 3306
MYSQL_DB: fund_platform
MYSQL_USER: root
MYSQL_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}
REDIS_HOST: redis
REDIS_PORT: 6379
JAVA_OPTS: -Xms256m -Xmx512m
# 租户元数据 - VIP_001 专属实例
TENANT_ID: "1001"
TENANT_GROUP: "TENANT_VIP_001"
ports:
- "8101:8101"
depends_on:
nacos:
condition: service_healthy
mysql:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8101/actuator/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 120s
networks:
- fund-network
# 系统服务 - VIP_002 专属实例(可选,按需启用)
# fund-sys-vip002:
# build:
# context: .
# dockerfile: Dockerfile
# args:
# MODULE: fund-sys
# container_name: fund-sys-vip002
# restart: unless-stopped
# environment:
# SERVER_PORT: 8102
# SPRING_PROFILES_ACTIVE: docker
# NACOS_SERVER_ADDR: nacos:8848
# NACOS_USERNAME: nacos
# NACOS_PASSWORD: nacos
# MYSQL_HOST: mysql
# MYSQL_PORT: 3306
# MYSQL_DB: fund_platform
# MYSQL_USER: root
# MYSQL_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}
# REDIS_HOST: redis
# REDIS_PORT: 6379
# JAVA_OPTS: -Xms256m -Xmx512m
# # 租户元数据 - VIP_002 专属实例
# TENANT_ID: "1002"
# TENANT_GROUP: "TENANT_VIP_002"
# ports:
# - "8102:8102"
# depends_on:
# nacos:
# condition: service_healthy
# mysql:
# condition: service_healthy
# redis:
# condition: service_healthy
# healthcheck:
# test: ["CMD", "curl", "-f", "http://localhost:8102/actuator/health"]
# interval: 30s
# timeout: 10s
# retries: 5
# start_period: 120s
# networks:
# - fund-network
# 客户服务 # 客户服务
fund-cust: fund-cust:
build: build:

View File

@ -54,18 +54,25 @@ scrape_configs:
target_label: instance target_label: instance
replacement: 'fund-gateway' replacement: 'fund-gateway'
# 系统服务 # 系统服务 - 多租户混合模式监控
- job_name: 'fund-sys' - job_name: 'fund-sys'
metrics_path: '/actuator/prometheus' metrics_path: '/actuator/prometheus'
static_configs: static_configs:
# 共享实例
- targets: ['fund-sys:8100'] - targets: ['fund-sys:8100']
labels: labels:
application: 'fund-sys' application: 'fund-sys'
service: 'business' service: 'business'
relabel_configs: tenant_mode: 'shared'
- source_labels: [__address__] instance: 'fund-sys-shared'
target_label: instance # VIP_001 专属实例
replacement: 'fund-sys' - targets: ['fund-sys-vip001:8101']
labels:
application: 'fund-sys'
service: 'business'
tenant_mode: 'vip'
tenant_group: 'TENANT_VIP_001'
instance: 'fund-sys-vip001'
# 客户服务 # 客户服务
- job_name: 'fund-cust' - job_name: 'fund-cust'

View File

@ -15,9 +15,15 @@ import java.util.Map;
* Nacos 服务注册元数据配置 * Nacos 服务注册元数据配置
* *
* <p>服务启动时自动注册租户标签到 Nacos支持租户感知的负载均衡</p> * <p>服务启动时自动注册租户标签到 Nacos支持租户感知的负载均衡</p>
*
* <p>支持两种配置方式</p>
* <ul>
* <li>环境变量TENANT_ID, TENANT_GROUPDocker 环境推荐</li>
* <li>配置文件spring.cloud.nacos.discovery.metadata.tenant-id/tenant-group</li>
* </ul>
*/ */
@Configuration @Configuration
@ConditionalOnProperty(name = "tenant.routing.enabled", havingValue = "true") @ConditionalOnProperty(name = "tenant.routing.enabled", havingValue = "true", matchIfMissing = true)
public class NacosMetadataConfig { public class NacosMetadataConfig {
private static final Logger logger = LoggerFactory.getLogger(NacosMetadataConfig.class); private static final Logger logger = LoggerFactory.getLogger(NacosMetadataConfig.class);
@ -25,14 +31,27 @@ public class NacosMetadataConfig {
@Value("${spring.application.name:unknown}") @Value("${spring.application.name:unknown}")
private String applicationName; private String applicationName;
@Value("${spring.cloud.nacos.discovery.metadata.tenant-id:}") /**
* 租户 ID优先级环境变量 > 配置文件 > 默认值
*/
@Value("${TENANT_ID:${spring.cloud.nacos.discovery.metadata.tenant-id:1}}")
private String tenantId; private String tenantId;
@Value("${spring.cloud.nacos.discovery.metadata.tenant-group:}") /**
* 租户组优先级环境变量 > 配置文件 > 自动生成
* 为空表示共享实例供所有租户使用
*/
@Value("${TENANT_GROUP:${spring.cloud.nacos.discovery.metadata.tenant-group:}}")
private String tenantGroup; private String tenantGroup;
@Value("${tenant.routing.default-tenant-id:1}") /**
private String defaultTenantId; * Nacos Registration Bean用于动态添加元数据
*/
private final Registration registration;
public NacosMetadataConfig(Registration registration) {
this.registration = registration;
}
/** /**
* 初始化 Nacos 元数据 * 初始化 Nacos 元数据
@ -40,29 +59,47 @@ public class NacosMetadataConfig {
@PostConstruct @PostConstruct
public void init() { public void init() {
logger.info("[Nacos Metadata] 应用名:{}", applicationName); logger.info("[Nacos Metadata] 应用名:{}", applicationName);
// 如果未配置租户 ID使用默认值
if (tenantId == null || tenantId.isEmpty()) {
tenantId = defaultTenantId;
logger.info("[Nacos Metadata] 未配置租户 ID使用默认值{}", tenantId);
}
// 如果未配置租户组自动生成
if (tenantGroup == null || tenantGroup.isEmpty()) {
tenantGroup = buildTenantGroup(tenantId);
logger.info("[Nacos Metadata] 自动生成租户组:{}", tenantGroup);
}
logger.info("[Nacos Metadata] 租户 ID: {}, 租户组:{}", tenantId, tenantGroup); logger.info("[Nacos Metadata] 租户 ID: {}, 租户组:{}", tenantId, tenantGroup);
// 动态添加租户元数据到服务注册信息
if (registration != null && registration.getMetadata() != null) {
Map<String, String> metadata = registration.getMetadata();
// 添加租户 ID
if (tenantId != null && !tenantId.isEmpty()) {
metadata.put("tenant-id", tenantId);
}
// 添加租户组VIP 专属实例才有值共享实例为空
if (tenantGroup != null && !tenantGroup.isEmpty()) {
metadata.put("tenant-group", tenantGroup);
logger.info("[Nacos Metadata] 注册为 VIP 专属实例,租户组:{}", tenantGroup);
} else {
logger.info("[Nacos Metadata] 注册为共享实例,供所有租户使用");
}
logger.info("[Nacos Metadata] 服务元数据:{}", metadata);
}
} }
/** /**
* 构建租户组名称 * 获取当前租户 ID
*/ */
private String buildTenantGroup(String tenantId) { public String getTenantId() {
if (tenantId == null || tenantId.isEmpty()) { return tenantId;
return "DEFAULT"; }
}
return "TENANT_" + tenantId.toUpperCase(); /**
* 获取当前租户组
*/
public String getTenantGroup() {
return tenantGroup;
}
/**
* 判断是否为 VIP 专属实例
*/
public boolean isVipInstance() {
return tenantGroup != null && !tenantGroup.isEmpty();
} }
} }