乐趣区

关于java:如何实现线上优雅停机和调整线程池参数

我是 3y,一年 CRUD 教训用十年的 markdown 程序员👨🏻‍💻长年被誉为职业八股文选手

好几天没更新 austin 的系列文章啦,次要是始终在写 austin 的代码。而这篇文章我想了很久题目,最初定为《优雅,不过时》。文章的内容次要由以下局部组成:

  • 利用公布重启了怎么办?内存数据不是失落了吗?
  • 什么是优雅停机?如何实现优雅停机?
  • 如何优雅地调整线程池的参数?

如果你的我的项目遇到了相似的问题,也能够借鉴下我明天所解说的内容,读完我置信你必定会有些播种。

01、利用公布重启了怎么办

家喻户晓,如果咱们零碎在运行的过程中,内存数据没存储起来那就会导致失落。对于 austin 我的项目而言,就会使音讯失落,并且无奈下发到用户上。

这个在我讲述完我是如何设计「发送音讯生产端」以及「读取文件」时,尤其问得比拟多。为了局部没有追更的读者,我再简略讲述下我这边的设计:

austin-handler 模块,每个渠道的每种音讯类型我都用到了线程池进行隔离而生产:

austin-cron 模块,我读取文件是把每一条记录放至了单线程池做 LazyPending,目标为了提早生产做 批量 下发。

敏感的技术人看到内存队列或线程池(线程池也须要指定对应的内存队列)就很失常地想:内存队列可能的 size1024,而服务器在重启的时候可能内存队列的数据还没生产完,此时你怎么办?数据就丢了吗?

咱们应用线程池 / 内存队列在很多场景下都是为了进步吞吐量,有得就必有失。至于重启服务器导致内存数据的失落,就看你评估对本人的业务带来多少的影响了。

针对这种问题,austin自身就开发好了相干的性能作为「补充 」,通过实时计算引擎flink 的能力能够 实时 在后盾查看音讯下发的状况:

能够在 离线hive 找到音讯下发失败的userId(离线这块暂未实现),输出具体的receiverId 能够查看实时下发时失败的起因

查明起因之后再通过 csv 文件上传的做补发。

不过,这是平台提供做 补发 的能力,从技术上的角度,还有别的思路尽量避免线程池或者内存队列的数据因重启而失落的数据吗?有的,优雅敞开线程池

02、优雅停机

所谓「优雅停机」就是敞开的时候 先将本人须要解决的内容解决完了,之后才敞开。如果你间接kill -9,是没有「优雅」这一说法的,神仙都救不了。

1、在 网络层:TCP 有四次挥手、TCP KeepAliveHTTP KeepAlive 让连贯 优雅地敞开,防止很多报错。

2、在 Java 里边通过 Runtime.getRuntime().addShutdownHook() 注册事件,当虚拟机敞开的前调用该办法的具体逻辑进行 善后

3、在 Spring 里边执行了 ApplicationContextclose 之后,只有咱们 Bean 配置了 destroy 策略,那 Spring 在敞开之前也会先执行咱们的已实现好的 destroy 办法

4、在 Tomcat 容器提供了几种敞开的姿态,先暂停申请,抉择期待 N 秒才齐全敞开容器。

5、在 Java 线程池 提供了 shutdownshutdownNow供咱们敞开线程,显然 shutdown 是优雅敞开线程池的办法。

咱们的 austin 我的项目是基于 SpringBoot 环境结构的,所以咱们能够 重度依赖SpringBoot 进行优雅停机。

1、咱们设置应用服务器的停机模式为graceful

server.shutdown=graceful

2、在 austin 曾经引入 动静线程池 而非应用 Spring 治理下的ThreadPoolTaskExecutor,所以咱们能够把本人创立进去的线程池在 Spring 敞开的时候,进行优雅shutdown(想要敞开其余的资源时,也能够相似干这种操作)

注:如果是应用 Spring 封装过的线程池 ThreadPoolTaskExecutor,默认就会优雅敞开,因为它是实现了DisposableBean 接口的

03、如何优雅地调整线程池的参数?

austin在整个我的项目里边,还是有挺多中央是用到了线程池,特地重要的是从 MQ 里生产所创立的线程池。

有小伙伴过后给过倡议:有没有打算引入动静线程池,不必公布就调整线程池的参数从而 长期 进步生产能力。顺便在这给大家举荐美团的线程池文章:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html,如果没读过这篇文章的,倡议都去读下,挺不错的。

美团这篇文章讲述了动静线程池的思路,但应该是未官网开源,所以有很多小伙伴基于文章的思路造了好用的轮子。比方 Hippo4J 和 dynamic-tp 都是比拟优良的轮子了。

这两个仓库我都看了下源码,Hippo4J 有 无依赖中间件 实现动静线程池,也有默认实现 NacosApollo的版本,并有着 治理后盾 ,而 dynamic-tp 默认实现依赖NacosApollo。大佬们的代码都写得很不错,我举荐大家都能够去学学。

我在最后的时候接的是 dynamic-tp 的代码,因为我自身 austin 就接入了Apollo,也感觉临时不太须要治理后盾。起初 Hippo4J 作者找我聊了下,心愿我能接入 Hippo4J。

我依照我目前的应用场景对着代码看了一把,我是须要通过在 创立线程池后再动静调参 的场景。于是跟 Hippo4J 作者反馈了下,他果决说早晨或今天就给我实现(:恐怖如斯,太肝了

不过,周三我反馈完,周四早晨我差不多就将 dynamic-tp 快接入完了。我目前当初打算先跑着(毕竟切换 API 其实也是须要工夫老本的),后续看有没有遇到痛点或者空的时候再迁徙到 Hippo4J 再体验体验

也不为别的,就看中龙台大佬比我还肝(本人提出的场景,开源作者能很快地反馈并实现,太强了,丝毫不放心有大坑要我本人搞)

04、总结

对于 austin 而言,失常的重启公布咱们通过 优雅停机 来尽可能减少零碎的解决数据时的失落。如果音讯是真的十分重要而且须要做补发,在 austin 中也能够通过 上传文件 的形式再做补发,且能看到实时推送的数据链路统计和某个用户下发音讯失败的起因。

我置信,这曾经能笼罩线上绝大多数的场景了。

或者后续也能够针对某些场景在生产端做 exactly once + 幂等 来解决kill -9 的困境,但要晓得的是:想要保证数据不失落、不反复发送给用户,肯定会带来性能的损耗,这是须要做均衡的。

在我的项目很少应用线程池之前,始终可能认为线程池的相干面试题就是八股文。但当你我的项目零碎真的遇到线程池优雅敞开的问题、线程池参数动静调整的问题,你就会发现之前看的内容其实是很有意义的。

阿,原来能够设置参数让外围线程数也会回收的(之前始终都没有留神过呢)

阿,原来都大多数框架都有提供对应的扩大接口给咱们监听敞开,默认的实现都有优雅停机的机制咯,之前始终都不晓得呢。

….

austin还在继续优化和更新中,欢送大佬们给点意见和想法一起探讨,对该我的项目感兴趣的同学也能够到我的 GitHub 上逛逛,或者有可能这个季度的 KPI 就有了咯。

动静线程池的仓库地址:

  • Hippo4J:https://github.com/acmenlt/dynamic-threadpool
  • DynamicTp:https://gitee.com/yanhom/dynamic-tp

都看到这里了,点个赞一点都不过分吧?我是 3y,下期见。

关注我的微信公众号【Java3y】除了技术我还会聊点日常,有些话只能轻轻说~ 【对线面试官 + 从零编写 Java 我的项目】继续高强度更新中!求 star!!原创不易!!求三连!!

austin 我的项目源码 Gitee 链接:gitee.com/austin

austin 我的项目源码 GitHub 链接:github.com/austin

退出移动版