task-75: 虚拟线程任务等待队列上限与拒绝/延迟指标

This commit is contained in:
2026-08-30 20:39:31 +08:00
parent ba163aa4bd
commit 6a4a115260
4 changed files with 340 additions and 3 deletions
@@ -0,0 +1,114 @@
package com.nanri.aiimage.config;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.core.task.TaskExecutor;
import org.springframework.core.task.TaskRejectedException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Task 75:虚拟线程任务排队闸门。Coze 执行池的信号量只限制"正在执行"的
* 并发度,提交侧仍会在虚拟线程里无限排队。此闸门在提交时统计"已受理未启动"
* 的等待数,达到上限立即拒绝并记录指标,防止等待队列无界堆积:
* <ul>
* <li>等待数:提交时 +1,任务执行(或执行器拒绝)时 -1;上限钳制到 [1, +∞)</li>
* <li>指标:等待耗时、执行耗时、拒绝次数(queue-full / delegate-rejected / invalid-input);</li>
* <li>执行器不可用(provider 为空)时拒绝新提交,不产生死等任务。</li>
* </ul>
*/
@Slf4j
public class CozeTaskQueueGate implements TaskExecutor {
private final TaskExecutor delegate;
private final int maxWaiting;
private final ObjectProvider<MeterRegistry> meterRegistryProvider;
private final AtomicInteger waiting = new AtomicInteger();
public CozeTaskQueueGate(TaskExecutor delegate, int maxWaiting,
ObjectProvider<MeterRegistry> meterRegistryProvider) {
this.delegate = delegate;
this.maxWaiting = Math.max(1, maxWaiting);
this.meterRegistryProvider = meterRegistryProvider;
}
public int waiting() {
return waiting.get();
}
@Override
public void execute(Runnable command) {
if (command == null) {
recordRejected("invalid-input");
throw new IllegalArgumentException("coze 任务不能为 null");
}
if (waiting.get() >= maxWaiting) {
recordRejected("queue-full");
log.warn("[coze-task][gate] waiting queue full, reject submit waiting={} limit={}",
waiting.get(), maxWaiting);
throw new TaskRejectedException("coze 等待队列已满,limit=" + maxWaiting
+ ", waiting=" + waiting.get());
}
waiting.incrementAndGet();
long queuedAt = System.nanoTime();
try {
delegate.execute(() -> {
long waitNanos = System.nanoTime() - queuedAt;
try {
run(command);
} finally {
waiting.decrementAndGet();
recordQueueWait(waitNanos);
}
});
} catch (RuntimeException ex) {
waiting.decrementAndGet();
recordQueueWait(System.nanoTime() - queuedAt);
recordRejected("delegate-rejected");
log.warn("[coze-task][gate] delegate rejected submit waiting={} limit={} msg={}",
waiting.get(), maxWaiting, ex.getMessage(), ex);
throw ex;
}
}
private void run(Runnable command) {
long startedAt = System.nanoTime();
try {
command.run();
} finally {
recordExecution(System.nanoTime() - startedAt);
}
}
private void recordRejected(String reason) {
MeterRegistry registry = meterRegistry();
if (registry != null) {
registry.counter("aiimage.coze-task.submit.rejected.total", "reason", reason).increment();
}
}
private void recordQueueWait(long waitNanos) {
MeterRegistry registry = meterRegistry();
if (registry != null && waitNanos >= 0L) {
Timer.builder("aiimage.coze-task.queue.wait.duration")
.register(registry)
.record(waitNanos, TimeUnit.NANOSECONDS);
}
}
private void recordExecution(long durationNanos) {
MeterRegistry registry = meterRegistry();
if (registry != null && durationNanos >= 0L) {
Timer.builder("aiimage.coze-task.execution.duration")
.register(registry)
.record(durationNanos, TimeUnit.NANOSECONDS);
}
}
private MeterRegistry meterRegistry() {
return meterRegistryProvider == null ? null : meterRegistryProvider.getIfAvailable();
}
}
@@ -1,5 +1,7 @@
package com.nanri.aiimage.config;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -40,9 +42,11 @@ public class TaskFileJobConfig {
@Bean("cozeTaskExecutor")
public TaskExecutor cozeTaskExecutor(
ExecutorService cozeVirtualThreadExecutor,
@Value("${aiimage.coze-task.max-concurrent:12}") int maxConcurrent) {
@Value("${aiimage.coze-task.max-concurrent:12}") int maxConcurrent,
@Value("${aiimage.coze-task.max-waiting:1000}") int maxWaiting,
ObjectProvider<MeterRegistry> meterRegistryProvider) {
Semaphore semaphore = new Semaphore(Math.max(1, maxConcurrent));
return new ConcurrentTaskExecutor(command -> {
TaskExecutor semaphoreLimited = new ConcurrentTaskExecutor(command -> {
if (command == null) {
throw new IllegalArgumentException("coze 任务不能为 null");
}
@@ -61,5 +65,6 @@ public class TaskFileJobConfig {
}
});
});
return new CozeTaskQueueGate(semaphoreLimited, maxWaiting, meterRegistryProvider);
}
}