fix(内存): 两处无界内存风险加固——brand-check 线程池改有界队列+CallerRuns(大批量检查不再无界堆积 OOM);OSS readObjectBytes 加 20MB 读取上限防大对象误用 OOM

This commit is contained in:
2026-09-11 16:09:50 +08:00
parent c2915036d6
commit cd055f8ccd
2 changed files with 30 additions and 3 deletions
@@ -44,8 +44,14 @@ public class BrandCheckClient {
this.externalCallMetrics = externalCallMetrics;
}
private final ExecutorService checkExecutor = Executors.newFixedThreadPool(
BRAND_CHECK_CONCURRENCY, namedThreadFactory("brand-check"));
// 有界队列 + CallerRuns:大批量(几千品牌)检查时队列洪峰不无界堆内存,
// 队列打满时提交线程自己执行一个检查任务,上游天然限速。
private final ExecutorService checkExecutor = new java.util.concurrent.ThreadPoolExecutor(
BRAND_CHECK_CONCURRENCY, BRAND_CHECK_CONCURRENCY,
60L, java.util.concurrent.TimeUnit.SECONDS,
new java.util.concurrent.LinkedBlockingQueue<>(1000),
namedThreadFactory("brand-check"),
new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
@PreDestroy
void shutdownCheckExecutor() {
@@ -41,6 +41,8 @@ public class OssStorageService {
private static final String DIGITAL_HUMAN_PREFIX = "digital-human/versions/";
private static final String SOFTWARE_VERSION_PREFIX = "nanri-image/versions/";
private static final String LEGACY_MINIO_ENDPOINT = "http://47.110.241.161:9000";
/** readObjectBytes 兜底读取上限(20MB):防止大对象一次性读入内存导致 OOM。 */
private static final long DEFAULT_READ_MAX_BYTES = 20L * 1024 * 1024;
private final OssProperties ossProperties;
private final MinioClient minioClient;
@@ -248,13 +250,32 @@ public class OssStorageService {
}
public byte[] readObjectBytes(String bucket, String objectKey) {
// 防御性上限:防止被误用到大对象时一次性读入内存导致 OOM(当前仅模板下载场景,模板为 xlsx KB 级)
return readObjectBytes(bucket, objectKey, DEFAULT_READ_MAX_BYTES);
}
public byte[] readObjectBytes(String bucket, String objectKey, long maxBytes) {
String normalizedBucket = requireStorageName(bucket, "bucket");
String normalizedObjectKey = requireStorageName(objectKey, "objectKey");
try (var stream = buildClient().getObject(GetObjectArgs.builder()
.bucket(normalizedBucket)
.object(normalizedObjectKey)
.build())) {
return stream.readAllBytes();
byte[] buffer = new byte[1024 * 1024];
ByteArrayOutputStream out = new ByteArrayOutputStream();
long total = 0;
int read;
while ((read = stream.read(buffer)) != -1) {
total += read;
if (total > maxBytes) {
throw new IllegalArgumentException("对象过大,超过读取上限 " + maxBytes + " 字节: "
+ normalizedBucket + "/" + normalizedObjectKey);
}
out.write(buffer, 0, read);
}
return out.toByteArray();
} catch (IllegalArgumentException ex) {
throw ex;
} catch (Exception ex) {
throw storageFailure("read", normalizedBucket + "/" + normalizedObjectKey, ex);
}