task-66: 限制 RustFS 并发读写与重试的总资源预算
- 新增 aiimage.transient-storage.max-total-concurrent-operations 配置 (默认 0 不启用):跨 upload/read/delete 的总并发预算 - executeWithRetry 入口获取一个总许可,整次操作(含全部重试)全程占用, 重试不额外消耗,超预算立即拒绝并计入 operation=total rejected 指标 - 失败/完成后 finally 释放总许可,无残留锁;负值/0 视为未启用保持原行为
This commit is contained in:
@@ -23,6 +23,11 @@ public class TransientStorageProperties {
|
||||
private int maxConcurrentUploads = 16;
|
||||
private int maxConcurrentReads = 32;
|
||||
private int maxConcurrentDeletes = 8;
|
||||
/**
|
||||
* 跨操作类型的总并发预算(含重试期间):一次操作全程占用一个总许可,
|
||||
* 防止多任务叠加时读写与重试之和突破对后端的总压力上限。0/负值表示不启用。
|
||||
*/
|
||||
private long maxTotalConcurrentOperations = 0;
|
||||
private long acquirePermitTimeoutMillis = 2000;
|
||||
private long baseRetryDelayMillis = 500;
|
||||
private long maxRetryDelayMillis = 5000;
|
||||
|
||||
+54
-33
@@ -35,6 +35,7 @@ public class RustfsObjectStorageService {
|
||||
private static final String OP_READ = "read";
|
||||
private static final String OP_DELETE = "delete";
|
||||
private static final String OP_STAT = "stat";
|
||||
private static final String OP_TOTAL = "total";
|
||||
|
||||
private final TransientStorageProperties properties;
|
||||
private final ObjectProvider<MeterRegistry> meterRegistryProvider;
|
||||
@@ -43,6 +44,7 @@ public class RustfsObjectStorageService {
|
||||
private final Semaphore uploadSemaphore;
|
||||
private final Semaphore readSemaphore;
|
||||
private final Semaphore deleteSemaphore;
|
||||
private final Semaphore totalSemaphore;
|
||||
private final AtomicInteger windowFailureCount = new AtomicInteger();
|
||||
private volatile long failureWindowStartedAtMillis;
|
||||
private volatile long circuitOpenUntilMillis;
|
||||
@@ -66,6 +68,13 @@ public class RustfsObjectStorageService {
|
||||
this.uploadSemaphore = new Semaphore(Math.max(1, properties.getMaxConcurrentUploads()));
|
||||
this.readSemaphore = new Semaphore(Math.max(1, properties.getMaxConcurrentReads()));
|
||||
this.deleteSemaphore = new Semaphore(Math.max(1, properties.getMaxConcurrentDeletes()));
|
||||
// 总预算为 0/负值时不启用(Semaphore(0) 的 tryAcquire 永远失败,须用启用开关区分)
|
||||
long totalBudget = Math.max(0L, properties.getMaxTotalConcurrentOperations());
|
||||
this.totalSemaphore = new Semaphore((int) Math.min(totalBudget, Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
private boolean isTotalBudgetEnabled() {
|
||||
return properties.getMaxTotalConcurrentOperations() > 0L;
|
||||
}
|
||||
|
||||
public boolean isConfigured() {
|
||||
@@ -185,45 +194,57 @@ public class RustfsObjectStorageService {
|
||||
CheckedSupplier<T> supplier) {
|
||||
long startedAt = System.nanoTime();
|
||||
Exception last = null;
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
checkDeadline(operation, objectKey, deadlineNanos);
|
||||
rejectIfCircuitOpen(operation, objectKey);
|
||||
acquirePermit(operation, objectKey, semaphore, deadlineNanos);
|
||||
long delayMillis = 0L;
|
||||
try {
|
||||
// 总资源预算:一次操作(含全部重试)全程占用一个总许可,重试不额外消耗。
|
||||
boolean totalAcquired = false;
|
||||
if (isTotalBudgetEnabled()) {
|
||||
acquirePermit(OP_TOTAL, objectKey, totalSemaphore, deadlineNanos);
|
||||
totalAcquired = true;
|
||||
}
|
||||
try {
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
checkDeadline(operation, objectKey, deadlineNanos);
|
||||
rejectIfCircuitOpen(operation, objectKey);
|
||||
acquirePermit(operation, objectKey, semaphore, deadlineNanos);
|
||||
long delayMillis = 0L;
|
||||
try {
|
||||
checkDeadline(operation, objectKey, deadlineNanos);
|
||||
T result = supplier.get();
|
||||
checkDeadline(operation, objectKey, deadlineNanos);
|
||||
if (!OP_UPLOAD.equals(operation)) {
|
||||
resetFailureWindow(operation);
|
||||
}
|
||||
recordOperation(operation, "success", elapsedNanos(startedAt));
|
||||
log.debug("[rustfs] operation success operation={} objectKey={} attempt={}/{} durationMs={} bytes={}",
|
||||
operation, objectKey, attempt, maxRetries, elapsedMillis(startedAt), bytes);
|
||||
return result;
|
||||
} catch (Exception ex) {
|
||||
last = ex;
|
||||
recordOperation(operation, attempt < maxRetries ? "retry" : "failure", elapsedNanos(startedAt));
|
||||
recordFailure(operation, objectKey, ex);
|
||||
if (attempt < maxRetries) {
|
||||
delayMillis = retryDelayMillis(attempt);
|
||||
log.warn("[rustfs] operation failed, retrying operation={} objectKey={} attempt={}/{} delayMs={} err={}",
|
||||
operation, objectKey, attempt, maxRetries, delayMillis, ex.getMessage());
|
||||
try {
|
||||
checkDeadline(operation, objectKey, deadlineNanos);
|
||||
T result = supplier.get();
|
||||
checkDeadline(operation, objectKey, deadlineNanos);
|
||||
if (!OP_UPLOAD.equals(operation)) {
|
||||
resetFailureWindow(operation);
|
||||
}
|
||||
recordOperation(operation, "success", elapsedNanos(startedAt));
|
||||
log.debug("[rustfs] operation success operation={} objectKey={} attempt={}/{} durationMs={} bytes={}",
|
||||
operation, objectKey, attempt, maxRetries, elapsedMillis(startedAt), bytes);
|
||||
return result;
|
||||
} catch (Exception ex) {
|
||||
last = ex;
|
||||
recordOperation(operation, attempt < maxRetries ? "retry" : "failure", elapsedNanos(startedAt));
|
||||
recordFailure(operation, objectKey, ex);
|
||||
if (attempt < maxRetries) {
|
||||
delayMillis = retryDelayMillis(attempt);
|
||||
log.warn("[rustfs] operation failed, retrying operation={} objectKey={} attempt={}/{} delayMs={} err={}",
|
||||
operation, objectKey, attempt, maxRetries, delayMillis, ex.getMessage());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
semaphore.release();
|
||||
}
|
||||
if (last instanceof OperationTimeoutException timeoutException) {
|
||||
throw timeoutException;
|
||||
}
|
||||
if (attempt < maxRetries) {
|
||||
sleepQuietly(delayMillis, "retrying rustfs " + operation,
|
||||
operation, objectKey, deadlineNanos);
|
||||
}
|
||||
} finally {
|
||||
semaphore.release();
|
||||
}
|
||||
if (last instanceof OperationTimeoutException timeoutException) {
|
||||
throw timeoutException;
|
||||
}
|
||||
if (attempt < maxRetries) {
|
||||
sleepQuietly(delayMillis, "retrying rustfs " + operation,
|
||||
operation, objectKey, deadlineNanos);
|
||||
throw new IllegalStateException("failed to " + operation + " payload in transient storage", last);
|
||||
} finally {
|
||||
if (totalAcquired) {
|
||||
totalSemaphore.release();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("failed to " + operation + " payload in transient storage", last);
|
||||
}
|
||||
|
||||
private void acquirePermit(String operation, String objectKey, Semaphore semaphore, long deadlineNanos) {
|
||||
|
||||
Reference in New Issue
Block a user