当前位置: 首页 > 科技观察

深入剖析动态线程池的九大场景

时间:2023-03-21 12:43:46 科技观察

线程池是一种基于池化思想进行线程管理的工具。使用线程池可以减少创建和销毁线程的开销,避免线程过多导致系统资源耗尽。在高并发、大批量的任务处理场景下,线程池的使用必不可少。如果你真正在项目中使用线程池,相信你可能会遇到以下痛点:线程池随便定义,线程资源过多,导致服务器负载过高。线程池参数不容易评估。随着业务并发量的增加,业务面临失败的风险。线程池任务执行时间超过平均执行周期,开发者无法感知。线程池任务堆积,触发拒绝策略,影响现有业务的正常运行。当业务出现超时、断路等问题时,因为没有监控,无法判断是否是线程池导致的。本机线程池不支持运行时变量的传递。比如MDC上下文遇到线程池就会GG。项目关闭时无法优雅退出,大量正在运行的线程池任务被丢弃。线程池运行的时候,任务执行停止了,怀疑死锁或者执行了耗时操作,但是没有办法启动。基于以上痛点,小马开始了hippo4j的开发,致力于打造标准线程池的动态变化和监控的中间件框架。GitHub:https://github.com/opengoofy/hippo4jGitee:https://gitee.com/agentart/hippo4jHippo4j增强了JDKThreadPoolExecutor的线程池,扩展了三方框架的底层线程池,提供业务系统提升线上运营保障能力。Tips:hippo4j不仅限于JavaThreadPoolExecutor的增强,Dubbo、RabbitMQ、RocketMQ、Hystrix、Tomcat、Jetty、Undertow等Framework线程池也有监控动态变化。1、线程池定义随意,导致服务器负载高。在系统开发过程中,由于涉及多人协作,难免信息不通。在同一个系统中,对于线程池,任意定义线程池是很常见的。开发者张三想记录用户操作日志,定义了user-log-record-thread-pool;开发者李四想记录会员操作日志,定义了member-log-record-thread-pool;开发者王五要记录权限操作日志,定义了power-log-record-thread-pool;...随着系统的不断发展,定义了越来越多相同或不同语义的线程池,间接导致严重消耗服务器资源。而如果系统中使用了hippo4j,可以在控制台查看当前应用已有的线程池,是否有相同语义和业务复用性的线程池实例,避免线程资源的过度浪费。2.线程池参数不好评估。业务中使用了线程池。十个程序员就有九个可能在嘀咕。这个线程池的配置应该如何选择呢?我觉得纠结点主要有两点,无非就是设定太多或者太少。如果预设的线程数或阻塞队列数少,当业务量增大时,会出现两种情况,不管哪一种是业务无法接受的。估计200ms执行完的任务,可能2s就执行完了,因为任务都在排队了。任务满后执行拒收政策,影响正常业务。如果线程池的参数设置过大,无疑会造成资源的浪费,同时也会造成两种情况。线程资源也占用服务器资源,开多了对服务器会有一定的压力。如果创建的线程过多,则在使用其他线程池执行时,服务器CPU上下文切换也会成为问题。大家都知道,如果要修改正在运行的应用程序线程池的参数,需要停止在线应用程序,调整成功后再释放。这个过程非常繁琐。如果能在运行过程中动态调整线程池的参数就好了。基于这些痛点,美团技术团队引入了动态线程池的概念,催生了多个动态线程池框架,hippo4j就是其中之一。如果应用部署在集群中,hippo4j可以选择修改线程池的一个实例,或者修改集群的所有实例,在运行时生效,不需要重启服务。再比如,在压力测试时使用hippo4j动态调整线程池参数,对于开发测试来说也是一个不错的选择。3、线程池运行时告警策略从线程池运行时监控的角度来看,hippo4j内置了4种告警策略,线程池活跃度、阻塞队列容量、拒绝策略触发、任务运行超时告警。线程池活跃度:假设阈值设置为80%,线程池最大线程数为10,当线程数达到8时,会触发告警。阻塞队列容量:假设阈值设置为80%,阻塞队列容量为100,当容量达到80时,会触发告警。触发拒绝策略:当线程池任务触发拒绝策略时,会发起拒绝策略告警。任务运行超时:假设单个任务的超时时间为1000ms,如果任务执行超过这个时间就会报警。hippo4j支持钉钉、企业微信、飞书软件通知,线程池任务运行超时告警示例:4、线程池运行状态是黑匣子线程池黑匣子服务运行过程中为开发者提供的一套完整的集合。开发者无法获知线程池的参数变化,比如阻塞队列数或已完成任务数等核心参数,不利于排查。hippo4j支持实时查看线程池的运行状态,并在核心参数的基础上扩展了负载、内存、拒绝次数等关键指标。每次查询都会返回线程池当前的运行信息。5、线程池监控hippo4j提供了两种线程池运行时数据监控方式,即:(1)内置数据池数据采集+监控,不依赖任何中间件,由hippo4j内部集成运行。(2)使用第三方中间件Prometheus+Grafana或者ElasticSearch+Grafana进行采集监控。6、集成SpringThreadPoolTask??ExecutorSpringThreadPoolTask??Executor扩展了原生线程池的一些功能。我觉得比较实用的还有两个,hippo4j也支持。当服务停止时,通知线程池处理剩余任务,等待指定时间后强行停止。将线程上下文传递给线程池执行上下文。第一个是实际使用中非常核心的一个功能,减少了线程池丢弃任务的可能性。这里我们重点说说。我们平时在停止应用的时候,有没有这样的考虑,线程池中的任务真的都执行完了吗?也许已经完成了,也许还没有。基于以上考虑,Spring注册了线程池销毁方法。当应用程序关闭时,如果发现线程池中有任务没有执行完,则需要等待一段指定的时间。如果在规定的时间内完成了任务的执行,那大家都开心;如果还有未完成的任务,它将被丢弃。为什么任务会被丢弃而不是等待?因为如果长时间执行线程池任务,会影响整个应用的停止,做了折衷。7、三方框架中间件线程池适配hippo4j的目标是兼容所有框架的线程池,并提供监控和动态修改能力。目前支持的三方框架线程池列表:ApacheDubboAlibabaDubboRabbitMQApacheRocketMQSpringCloudStreamRocketMQSpringCloudHystrixTomcatJettyUndertow支持上述框架线程池的动态变化参数和监控功能,例如:hippo4j将支持更多的三方框架线程池在以后有好的想法可以和我交流。8、查看线程池运行栈,查看线程池正在运行,但是任务停止了,怀疑是死锁或者执行了耗时操作。大部分程序员会选择使用命令或者arthas来查看线程池运行的线程栈,看看Workers卡在了哪个方法。基于以上痛点,hippo4j推出了实时查看线程池运行的功能堆。9、动态线程池对性能有影响吗?这可能是很多开发者关心的问题,所以这里统一回复一下。hippo4j只是增强了线程池的部分核心功能,并没有修改任务执行的源码流程,可以保证绝对的安全。其次,hippo4j的上述功能均扩展到线程池中执行任务的主进程之外,不会影响线程池的正常执行性能。Hippo4j为用户提供了两种操作模式,分别是轻量级的配置中心接入和功能更强大的服务器接入。以下是各自的优点和缺点。1、hippo4jconfig依赖配置中心完成线程池的动态变化。支持的配置中心包括:Nacos、Apollo、Zookeeper、Etcd,未来会接入Consul。另外,hippo4j已经支持用户自定义配置中心的实现。如果使用公司自研或其他配置中心,也可以用最少的工作量引入。使用hippo4jconfig方式的优缺点:优点:轻量级引入,hippo4j集成可以根据项目中已有的配置中心进行,无需引入其他服务,可以使用动态线程池参数、runtime等核心监控、告警功能。不足:缺少可视化控制台页面,上面介绍的很多功能无法使用。2、hippo4j服务端需要部署hippo4jJar包,涵盖上述所有功能。因为配置中心和注册中心是服务端内部实现的(参考nacos和eureka实现),不依赖任何第三方中间件。优点:功能齐全,享受更多服务和便利。如果应用启动一个集群,可以指定其中一个实例的线程池修改,而config是整个集群的变化。不足:与hippo4jconfig相比,需要额外部署一个jar包,增加了部署的工作量。如果你最初使用的是hippo4jconfig,想切换到服务端,两者替换时不需要修改业务代码。使用建议:根据公司情况选择。如果基本功能可以使用,就选择hippo4jconfig来使用;如果你想要更多的功能,你可以选择hippo4jserver。开源项目的发展离不开用户和贡献者的支持。小马整理了hippo4j最近的发展情况:GitHub和Gitee获得了3.3k+stars和830+fork。被Gitee评为GVP(最有价值开源项目)。70个项目贡献者为hippo4j做出了贡献,在此特别致谢。18家注册公司在生产环境中使用了hippo4j。通过Murphy安全扫描,不存在代码安全漏洞隐患。最后总结一下,笔者每天下班后和周六周日都在做开源项目,为爱适当发电。如果大家觉得有用,请在以下平台点赞支持,非常感谢~GitHub:https://github.com/opengoofy/hippo4jGitee:https://gitee.com/agentart/hippo4j