尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

美团二面拷打:如何设计一个动态线程池?

美团二面拷打:如何设计一个动态线程池? 什么是动态线程池为什么需要动态线程池动态线程池是一种能够在应用程序运行过程中无需重启服务即可实时调整其核心配置参数如核心线程数、最大线程数等的线程池机制。通常情况下动态线程池不仅支持参数的动态变更还内置了监控和告警功能例如在发生线程池任务堆积时通知相应的开发人员。传统线程池存在的痛点参数不可动态修改传统线程池的参数如核心线程数、最大线程数和任务队列大小在创建时被固定。随着业务负载的变化这种固定配置难以适应需要调整时必须重启服务过程繁琐且耗时。监控不足缺乏有效的运行时监控和告警机制难以及时发现并响应问题例如无法明确得知是否任务积压过多、活跃线程数是否快达到最大线程数、是否开始执行拒绝策略。问题难定位当线程池运行出现问题例如发生线程池拒绝、线程死锁时无法及时捕捉线程堆栈信息从而难以快速定位问题原因。动态线程池是如何解决这些痛点的实时调整参数动态线程池能够根据业务负载的变化实时调整线程池参数如核心线程数和最大线程数从而提高资源利用率和系统吞吐量。内置监控和告警功能动态线程池通常集成了监控和告警功能支持实时检测线程池的运行状态以及阻塞队列容量、线程池活跃度、拒绝策略执行等指标的告警。这使得开发人员能够充分了解线程池的运行情况并及时预防和应对线程池运行过程中出现的异常情况。线程池运行堆栈动态线程池通常支持实时和历史线程栈获取可以大大增强问题定位和性能调优的能力。如何动态修改线程池的参数美团技术团队在《Java 线程池实现原理及其在美团业务中的实践》[1]这篇文章中介绍到对线程池参数实现可自定义配置的思路和方法。美团技术团队的思路是主要对线程池的核心参数实现自定义可配置。这三个核心参数是corePoolSize:核心线程数线程数定义了最小可以同时运行的线程数量。maximumPoolSize:当队列中存放的任务达到队列容量的时候当前可以同时运行的线程数量变为最大线程数。workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数如果达到的话新任务就会被存放在队列中。为什么是这三个参数我在Java 线程池详解[2] 这篇文章中就说过这三个参数是ThreadPoolExecutor最重要的参数它们基本决定了线程池对于任务的处理策略。如何支持参数动态配置且看ThreadPoolExecutor提供的下面这些方法。格外需要注意的是corePoolSize 程序运行期间的时候我们调用setCorePoolSize()这个方法的话线程池会首先判断当前工作线程数是否大于corePoolSize如果大于的话就会回收工作线程。另外你也看到了上面并没有动态指定队列长度的方法美团的方式是自定义了一个叫做ResizableCapacityLinkedBlockIngQueue的队列主要就是把LinkedBlockingQueue的 capacity 字段的 final 关键字修饰给去掉了让它变为可变的。知道了如何修改之后那线程池参数应该如何配置呢我们可以借助配置中心例如 Nacos、Apollo、Zookeeper。你可以将线程池参数配置在配置中心中然后在应用启动时从配置中心获取这些参数。并且你需要监听线程池参数配置当参数有变更时实现线程池的参数的实时修改。Nacos 存储线程池配置不借助中间件可以吗当然是可以的如果不想多引入一个中间件的话也是可以不依赖配置中心的。我们自己去监听线程池配置文件是否被修改即可可以利用 Hutool 的WatchMonitor基于WatchService解决了修改文件会触发两次事件的问题或者 Apache Commons IO 的FileAlterationListenerAdaptor基于观察者模式实现文件修改监听。如何获取线程池的一些指标数据要获取线程池的指标数据可以使用以下方法getCorePoolSize()获取核心线程数。getMaximumPoolSize()获取最大线程数。getPoolSize()获取线程池中的工作线程数包括核心线程和非核心线程。getQueue()获取线程池中的阻塞队列可以从队列中获取任务的数量来了解队列积压情况。getActiveCount()获取活跃线程数也就是正在执行任务的线程。getLargestPoolSize()获取线程池曾经到过的最大工作线程数。getTaskCount()获取历史已完成以及正在执行的总的任务数量。除了这些线程池指标相关的方法之外还可以使用ThreadPoolExecutor的钩子方法进行扩展beforeExecute(Thread t, Runnable r)在执行每个任务之前调用可以在此处记录任务开始执行时间。afterExecute(Runnable r, Throwable t)在每个任务执行后调用不论任务是否成功完成可以用来记录任务执行结束时间。terminated()当线程池进入 TERMINATED 状态时调用可以在此时进行资源清理、统计汇总等操作。如何监控线程池我们上面已经知道了如何获取线程池的一些指标数据通过这些指标数据我们完全可以自己去写一个线程池监控功能。下面是一个简单的 Demo。printThreadPoolStatus()会每隔一秒打印出线程池的线程数、活跃线程数、完成的任务数、以及队列中的任务数。/** * 打印线程池的状态 * * param threadPool 线程池对象 */ public static void printThreadPoolStatus(ThreadPoolExecutor threadPool) { ScheduledExecutorService scheduledExecutorService new ScheduledThreadPoolExecutor(1, createThreadFactory(print-images/thread-pool-status, false)); scheduledExecutorService.scheduleAtFixedRate(() - { log.info(); log.info(ThreadPool Size: [{}], threadPool.getPoolSize()); log.info(Active Threads: {}, threadPool.getActiveCount()); log.info(Number of Tasks : {}, threadPool.getCompletedTaskCount()); log.info(Number of Tasks in Queue: {}, threadPool.getQueue().size()); log.info(); }, 0, 1, TimeUnit.SECONDS); }不过这样实现的话过于简陋。如果想要实现一个支持可视化和告警的线程池监控会比较麻烦需要自己去写可视化界面和告警功能。Spring Boot 提供了 Actuator 模块来监控应用程序的运行状态包括线程池的使用情况。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency不过我们一般不会直接使用 SpringBoot Actuator 内置的实现而是通过定义自定义一个Endpoint类手动暴露线程池相关指标信息这样可以更加灵活和可控。// 简化版只暴露了一些核心信息为了演示方便 Component Endpoint(id threadpool) public class ThreadPoolEndpoint { Autowired private ThreadPoolManager threadPoolManager; ReadOperation public MapString, Object threadPoolsMetric() { MapString, Object metricMap new HashMap(); MapString, MapString, Object threadPools new HashMap(); threadPoolManager.getThreadPoolExecutorMap().forEach((name, executor) - { MonitorThreadPool tpe (MonitorThreadPool) executor; MapString, Object poolInfo new HashMap(); poolInfo.put(coreSize, tpe.getCorePoolSize()); poolInfo.put(maxSize, tpe.getMaximumPoolSize()); poolInfo.put(activeCount, tpe.getActiveCount()); poolInfo.put(taskCount, tpe.getTaskCount()); threadPools.put(name, poolInfo); }); metricMap.put(threadPools, threadPools); return metricMap; }暴露接口之后我们就可以在/actuator/threadpool端点获取线程池的相关信息。management.endpoints.web.exposure.include*线程池信息如下{ threadPools: { pool1: { coreSize: 10, maxSize: 20, activeCount: 5, taskCount: 100 }, pool2: { coreSize: 5, maxSize: 10, activeCount: 2, taskCount: 50 } } }线程池信息有了可视化监控和告警如何实现呢?我们可以利用 Prometheus Grafana这是最常见的方案。Prometheus 是一个开源的监控和告警系统它可以从应用的 Endpoint 拉取指标数据通过 Grafana 可以可视化展示这些指标数据。Grafana 还提供了告警功能支持发送邮件、短信等通知。Dynamic TP 一款开源动态线程池框架集成 Prometheus Grafana 实现线程池监控的效果图如下Dynamic TP 线程池监控除了 Prometheus Grafana还可以集成其他监控系统来实现可视化监控和告警例如 HertzBeat[3]推荐、Cubic[4]。Apache HertzBeat赫兹跳动是一个拥有强大自定义监控能力高性能集群兼容 Prometheus无需 Agent 的开源实时监控告警系统动态线程池开源实现现成的动态线程池开源实现Hippo4j[5]:异步线程池框架支持线程池动态变更监控告警无需修改代码轻松引入。支持多种使用模式轻松引入致力于提高系统运行保障能力。Dynamic TP[6]:轻量级动态线程池内置监控告警功能集成三方中间件线程池管理基于主流配置中心已支持 Nacos、ApolloZookeeper、Consul、Etcd可通过 SPI 自定义实现。
返回列表