task-80: 补充 JVM 堆、直接内存、临时磁盘和连接池容量配置说明

This commit is contained in:
2026-08-30 22:30:56 +08:00
parent 143ad3f6da
commit c517711169
5 changed files with 302 additions and 1 deletions
@@ -0,0 +1,39 @@
package com.nanri.aiimage.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "aiimage.capacity-plan")
public class CapacityPlanProperties {
/** JVM 堆上限(-Xmx),0 表示交由启动参数决定。 */
private long heapMaxBytes = 6L * 1024 * 1024 * 1024;
/** JVM 直接内存上限(-XX:MaxDirectMemorySize),0 表示交由启动参数决定。 */
private long directMaxBytes = 1L * 1024 * 1024 * 1024;
/** 临时磁盘(./data/tmp)为一次大任务预留的字节上限,0 会触发警告。 */
private long tempDiskReserveBytes = 4L * 1024 * 1024 * 1024;
/** 数据库连接池(Hikari maximum-pool-size)。 */
private int dbPoolMaxSize = 30;
/** 外部 HTTP 客户端(Coze/品牌/紫鸟)连接池容量。 */
private int httpClientPoolMaxSize = 32;
/** RustFS/MinIO OkHttp 连接池容量。 */
private int rustfsPoolMaxSize = 56;
/** 三个连接池之和的上限:超过视为配置异常,启动时警告。 */
private int poolTotalMax = 150;
/** 堆上限占机器物理内存的最大比例(超过触发警告)。 */
private double heapRatioWarn = 0.70;
/** 直接内存上限占机器物理内存的最大比例(超过触发警告)。 */
private double directRatioWarn = 0.15;
/** 临时磁盘预留占机器物理内存的最大比例(超过触发警告)。 */
private double tempDiskRatioWarn = 0.50;
}
@@ -0,0 +1,102 @@
package com.nanri.aiimage.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* Task 80:JVM 堆、直接内存、临时磁盘和连接池容量配置说明。把所有容量边界
* 收敛为一个可校验、可记录的 CapacityPlan:启动时打印摘要,配置越限时输出
* 明确警告(不吞配置、不改默认行为),方便按机器内存调整 -Xmx/-XX:
* MaxDirectMemorySize 与三个连接池。
*/
@Slf4j
@Component
public class CapacityPlanService implements CommandLineRunner {
private final CapacityPlanProperties properties;
private final long machineMaxMemoryBytes;
/** 供测试注入机器内存字节数;生产构造时取 Runtime.maxMemory()。 */
public CapacityPlanService(CapacityPlanProperties properties) {
this(properties, maxMemoryBytes());
}
CapacityPlanService(CapacityPlanProperties properties, long machineMaxMemoryBytes) {
this.properties = properties;
this.machineMaxMemoryBytes = machineMaxMemoryBytes;
}
private static long maxMemoryBytes() {
try {
return Runtime.getRuntime().maxMemory();
} catch (Exception ex) {
return 0L;
}
}
public CapacityPlan buildPlan() {
if (properties.getDbPoolMaxSize() < 0 || properties.getHttpClientPoolMaxSize() < 0
|| properties.getRustfsPoolMaxSize() < 0 || properties.getPoolTotalMax() < 0) {
throw new IllegalArgumentException("连接池容量不能为负值: db=" + properties.getDbPoolMaxSize()
+ " http=" + properties.getHttpClientPoolMaxSize()
+ " rustfs=" + properties.getRustfsPoolMaxSize());
}
List<String> warnings = new ArrayList<>();
if (machineMaxMemoryBytes > 0) {
warnRatio(warnings, "", properties.getHeapMaxBytes(), properties.getHeapRatioWarn());
warnRatio(warnings, "直接内存", properties.getDirectMaxBytes(), properties.getDirectRatioWarn());
warnRatio(warnings, "临时磁盘预留", properties.getTempDiskReserveBytes(), properties.getTempDiskRatioWarn());
} else {
warnings.add("无法取得机器内存,容量比例校验跳过");
}
if (properties.getTempDiskReserveBytes() <= 0) {
warnings.add("临时磁盘预留为 0,大任务可能无界写盘");
}
int poolTotal = properties.getDbPoolMaxSize()
+ properties.getHttpClientPoolMaxSize()
+ properties.getRustfsPoolMaxSize();
if (poolTotal > properties.getPoolTotalMax()) {
warnings.add("连接池总和 " + poolTotal + " 超过上限 " + properties.getPoolTotalMax());
}
return new CapacityPlan(properties.getHeapMaxBytes(), properties.getDirectMaxBytes(),
properties.getTempDiskReserveBytes(), properties.getDbPoolMaxSize(),
properties.getHttpClientPoolMaxSize(), properties.getRustfsPoolMaxSize(),
poolTotal, machineMaxMemoryBytes, List.copyOf(warnings));
}
private void warnRatio(List<String> warnings, String label, long bytes, double ratioWarn) {
if (bytes > 0 && (double) bytes > machineMaxMemoryBytes * ratioWarn) {
warnings.add(label + "上限 " + bytes + " 超过机器内存 " + machineMaxMemoryBytes
+ "" + (int) (ratioWarn * 100) + "%");
}
}
@Override
public void run(String... args) {
CapacityPlan plan = buildPlan();
log.info("[capacity] JVM 堆上限={} 直接内存={} 临时磁盘预留={} 连接池 db={} http={} rustfs={} 总和={} 机器内存={}",
plan.heapMaxBytes(), plan.directMaxBytes(), plan.tempDiskReserveBytes(),
plan.dbPoolMaxSize(), plan.httpClientPoolMaxSize(), plan.rustfsPoolMaxSize(),
plan.poolTotal(), plan.machineMaxMemoryBytes());
for (String warning : plan.warnings()) {
log.warn("[capacity] {}", warning);
}
}
public record CapacityPlan(
long heapMaxBytes,
long directMaxBytes,
long tempDiskReserveBytes,
int dbPoolMaxSize,
int httpClientPoolMaxSize,
int rustfsPoolMaxSize,
int poolTotal,
long machineMaxMemoryBytes,
List<String> warnings
) {
}
}
@@ -4,6 +4,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties({OssProperties.class, TransientStorageProperties.class, StorageProperties.class, BrandProgressProperties.class, DeleteBrandProgressProperties.class, BrandCheckProperties.class, ZiniaoProperties.class, ModuleCleanupProperties.class, TaskPressureProperties.class, TaskImageCacheCleanupProperties.class, AppearancePatentProperties.class, SimilarAsinProperties.class, ImageVideoProperties.class, InstanceRoutingProperties.class})
@EnableConfigurationProperties({OssProperties.class, TransientStorageProperties.class, StorageProperties.class, BrandProgressProperties.class, DeleteBrandProgressProperties.class, BrandCheckProperties.class, ZiniaoProperties.class, ModuleCleanupProperties.class, TaskPressureProperties.class, TaskImageCacheCleanupProperties.class, AppearancePatentProperties.class, SimilarAsinProperties.class, ImageVideoProperties.class, InstanceRoutingProperties.class, CapacityPlanProperties.class})
public class PropertiesConfig {
}
@@ -151,6 +151,17 @@ aiimage:
transient-payload-retention-hours: ${AIIMAGE_STORAGE_TRANSIENT_PAYLOAD_RETENTION_HOURS:72}
temp-dir:
retention-hours: ${AIIMAGE_TEMP_DIR_RETENTION_HOURS:24}
capacity-plan:
heap-max-bytes: ${AIIMAGE_CAPACITY_PLAN_HEAP_MAX_BYTES:6442450944}
direct-max-bytes: ${AIIMAGE_CAPACITY_PLAN_DIRECT_MAX_BYTES:1073741824}
temp-disk-reserve-bytes: ${AIIMAGE_CAPACITY_PLAN_TEMP_DISK_RESERVE_BYTES:4294967296}
db-pool-max-size: ${AIIMAGE_DB_POOL_MAX_SIZE:30}
http-client-pool-max-size: ${AIIMAGE_CAPACITY_PLAN_HTTP_CLIENT_POOL_MAX_SIZE:32}
rustfs-pool-max-size: ${AIIMAGE_CAPACITY_PLAN_RUSTFS_POOL_MAX_SIZE:56}
pool-total-max: ${AIIMAGE_CAPACITY_PLAN_POOL_TOTAL_MAX:150}
heap-ratio-warn: ${AIIMAGE_CAPACITY_PLAN_HEAP_RATIO_WARN:0.70}
direct-ratio-warn: ${AIIMAGE_CAPACITY_PLAN_DIRECT_RATIO_WARN:0.15}
temp-disk-ratio-warn: ${AIIMAGE_CAPACITY_PLAN_TEMP_DISK_RATIO_WARN:0.50}
brand-progress:
ttl-hours: ${AIIMAGE_BRAND_PROGRESS_TTL_HOURS:24}
failed-ttl-hours: ${AIIMAGE_BRAND_PROGRESS_FAILED_TTL_HOURS:2}