fix(内存): 两处无界内存风险加固——brand-check 线程池改有界队列+CallerRuns(大批量检查不再无界堆积 OOM);OSS readObjectBytes 加 20MB 读取上限防大对象误用 OOM
This commit is contained in:
+8
-2
@@ -44,8 +44,14 @@ public class BrandCheckClient {
|
|||||||
this.externalCallMetrics = externalCallMetrics;
|
this.externalCallMetrics = externalCallMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ExecutorService checkExecutor = Executors.newFixedThreadPool(
|
// 有界队列 + CallerRuns:大批量(几千品牌)检查时队列洪峰不无界堆内存,
|
||||||
BRAND_CHECK_CONCURRENCY, namedThreadFactory("brand-check"));
|
// 队列打满时提交线程自己执行一个检查任务,上游天然限速。
|
||||||
|
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
|
@PreDestroy
|
||||||
void shutdownCheckExecutor() {
|
void shutdownCheckExecutor() {
|
||||||
|
|||||||
+22
-1
@@ -41,6 +41,8 @@ public class OssStorageService {
|
|||||||
private static final String DIGITAL_HUMAN_PREFIX = "digital-human/versions/";
|
private static final String DIGITAL_HUMAN_PREFIX = "digital-human/versions/";
|
||||||
private static final String SOFTWARE_VERSION_PREFIX = "nanri-image/versions/";
|
private static final String SOFTWARE_VERSION_PREFIX = "nanri-image/versions/";
|
||||||
private static final String LEGACY_MINIO_ENDPOINT = "http://47.110.241.161:9000";
|
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 OssProperties ossProperties;
|
||||||
private final MinioClient minioClient;
|
private final MinioClient minioClient;
|
||||||
@@ -248,13 +250,32 @@ public class OssStorageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte[] readObjectBytes(String bucket, String objectKey) {
|
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 normalizedBucket = requireStorageName(bucket, "bucket");
|
||||||
String normalizedObjectKey = requireStorageName(objectKey, "objectKey");
|
String normalizedObjectKey = requireStorageName(objectKey, "objectKey");
|
||||||
try (var stream = buildClient().getObject(GetObjectArgs.builder()
|
try (var stream = buildClient().getObject(GetObjectArgs.builder()
|
||||||
.bucket(normalizedBucket)
|
.bucket(normalizedBucket)
|
||||||
.object(normalizedObjectKey)
|
.object(normalizedObjectKey)
|
||||||
.build())) {
|
.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) {
|
} catch (Exception ex) {
|
||||||
throw storageFailure("read", normalizedBucket + "/" + normalizedObjectKey, ex);
|
throw storageFailure("read", normalizedBucket + "/" + normalizedObjectKey, ex);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user