Skip to main content

15小节:开发线程池阈值触发告警规则

作者:程序员马丁

在线博客:https://nageoffer.com

note

热门项目实战社群,收获国内众多知名公司面试青睐,近千名同学面试成功!助力你在校招或社招上拿个offer。

开发线程池阈值触发告警规则,元数据信息:

©版权所有 - 拿个offer-开源&项目实战星球专属学习项目,依据《中华人民共和国著作权法实施条例》《知识星球产权保护》,严禁未经本项目原作者明确书面授权擅自分享至 GitHub、Gitee 等任何开放平台。违者将面临法律追究。


内容摘要:本文介绍 oneThread 中线程池告警机制的设计与实现,涵盖三大告警维度、定时检查器的源码解析,以及告警机制在实际项目中的常见问题与优化建议,帮助系统快速构建稳定可控的线程池监控体系。

课程目录如下所示:

  • 前言
  • 告警规则
  • 实现告警检查器
  • 常见问题
  • 文末总结

前言

在日常服务运行过程中,线程池作为业务异步处理的重要基础组件,其健康状态直接影响系统的吞吐能力与稳定性。然而,大多数线程池在使用过程中缺乏运行时监控和告警机制,导致问题往往在“任务堆积严重”、“线程打满”、“请求被拒”后才被动发现,极易引发雪崩效应。

为了解决这一痛点,oneThread 在框架层内置了线程池运行状态的告警机制,并通过统一的定时检查器配合线程池注册表,提供了轻量、通用、可配置的线程池健康巡检能力。

告警规则

如果让你来设计线程池告警机制,你会关注哪些维度?是线程池任务堆积太多,还是线程都被打满,又或者是任务被拒绝的次数陡增

在 oneThread 中,我们结合大量项目实践与告警命中率,最终提炼出了三条“高命中”的告警策略,并给出默认的触发阈值与判定逻辑,覆盖了最常见的线程池异常使用场景

告警策略如下所示:

维度触发条件检测含义
活跃度activeCount / maximumPoolSize 连续高于阈值(默认 80%)线程资源已逼近瓶颈,需扩容或对入口流量做限流
队列负载queueSize / queueCapacity 超过阈值排队任务激增,处理能力被入口流量压制,易引发大面积超时
拒绝异常监控到新的 RejectedExecutionException线程池已无法接收新任务,属于阻断场景,应立刻介入

活跃度和队列负载的监控规则较为简单,通过定时任务扫描即可实现。不过需要注意的是,定时任务的执行间隔需合理设置:过短会因监控 API 加锁导致与线程池其他操作竞争锁资源,过长则可能错过重要的告警时机。oneThread 在充分权衡后,默认将扫描间隔设置为 5 秒

因为拒绝策略告警设计到动态代理相关知识,为了文章内容垂直,会放到下一章节讲解。

实现告警检查器

1. 告警定时检查

由于线程池状态相关的检查 API(如 getActiveCount等)会竞争 mainLock,若在高频场景下调用,可能对业务线程产生性能干扰。因此,线程池状态监控通常采用定时任务方式进行,以延迟换取业务稳定性。此类定时检查无需引入额外框架,JDK 提供的 ScheduledExecutorService 已能满足稳定的调度需求。

ThreadPoolAlarmChecker 利用一个单线程的调度器,定期扫描系统中所有已注册线程池的运行状态,并针对启用了告警的线程池执行各类运行指标检测,及时触发相关告警处理。

/**
* 线程池运行状态报警检查器
* <p>
* 作者:马丁
* 加项目群:早加入就是优势!500人内部项目群,分享的知识总有你需要的 <a href="https://t.zsxq.com/cw7b9" />
* 开发时间:2025-05-04
*/
@Slf4j
@RequiredArgsConstructor
public class ThreadPoolAlarmChecker {

// ......

private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(
1,
ThreadFactoryBuilder.builder()
.namePrefix("scheduler_thread-pool_alarm_checker")
.build()
);

/**
* 启动定时检查任务
*/
public void start() {
// 每10秒检查一次,初始延迟0秒
scheduler.scheduleWithFixedDelay(this::checkAlarm, 0, 5, TimeUnit.SECONDS);
}

/**
* 停止报警检查
*/
public void stop() {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
}
}

/**
* 报警检查核心逻辑
*/
private void checkAlarm() {
Collection<ThreadPoolExecutorHolder> holders = OneThreadRegistry.getAllHolders();
for (ThreadPoolExecutorHolder holder : holders) {
if (holder.getExecutorProperties().getAlarm().getEnable()) {
checkQueueUsage(holder);
checkActiveRate(holder);
// ......
}
}
}
// ......
}

2. 活跃度告警

checkActiveRate 会监控线程池中活跃线程数的使用比例,当活跃度高于配置阈值时,触发“Activity”类型的告警,帮助开发者及时发现线程池可能存在“线程资源耗尽”或“处理能力过载”的风险。

/**
* 检查线程活跃度(活跃线程数 / 最大线程数)
*/
private void checkActiveRate(ThreadPoolExecutorHolder holder) {
ThreadPoolExecutor executor = holder.getExecutor();
ThreadPoolExecutorProperties properties = holder.getExecutorProperties();

int activeCount = executor.getActiveCount(); // API 有锁,避免高频率调用
int maximumPoolSize = executor.getMaximumPoolSize();

if (maximumPoolSize == 0) {
return;
}

int activeRate = (int) Math.round((activeCount * 100.0) / maximumPoolSize);
int threshold = properties.getAlarm().getActiveThreshold();

if (activeRate >= threshold) {
sendAlarmMessage("Activity", holder);
}
}

代码执行流程如下所示:

  1. ThreadPoolExecutorHolder 中获取实际的线程池实例(ThreadPoolExecutor)和对应的配置属性;
  2. 获取当前线程池中“正在执行任务”的线程数。这是一个有同步锁的调用,频繁获取会有性能开销,所以建议定时调度而非高频轮询。
  3. 获取线程池的最大线程数;防止除以 0 的情况,这里直接提前 return 掉。
  4. 计算活跃线程的使用率(百分比),如当前活跃线程为 8,最大线程数为 10,则 activeRate = 80
  5. 获取配置中设定的“活跃度告警阈值”(比如 80 或 90)。
  6. 如果活跃线程使用率超过(或等于)设定阈值,就调用 sendAlarmMessage(...) 方法,触发 线程活跃度过高 的报警。

线程池获取活跃线程方法源代码如下所示:

解锁付费内容,👉 戳