我是3y,一年CRUD
教训用十年的markdown
程序员长年被誉为职业八股文选手
好几天没更新austin
的系列文章啦,次要是始终在写austin
的代码。而这篇文章我想了很久题目,最初定为《优雅,不过时》。文章的内容次要由以下局部组成:
- 利用公布重启了怎么办?内存数据不是失落了吗?
- 什么是优雅停机?如何实现优雅停机?
- 如何优雅地调整线程池的参数?
如果你的我的项目遇到了相似的问题,也能够借鉴下我明天所解说的内容,读完我置信你必定会有些播种。
01、利用公布重启了怎么办
家喻户晓,如果咱们零碎在运行的过程中,内存数据没存储起来那就会导致失落。对于austin
我的项目而言,就会使音讯失落,并且无奈下发到用户上。
这个在我讲述完我是如何设计「发送音讯生产端」以及「读取文件」时,尤其问得比拟多。为了局部没有追更的读者,我再简略讲述下我这边的设计:
在austin-handler
模块,每个渠道的每种音讯类型我都用到了线程池进行隔离而生产:
在austin-cron
模块,我读取文件是把每一条记录放至了单线程池做LazyPending
,目标为了提早生产做批量下发。
敏感的技术人看到内存队列或线程池(线程池也须要指定对应的内存队列)就很失常地想:内存队列可能的size
为1024
,而服务器在重启的时候可能内存队列的数据还没生产完,此时你怎么办?数据就丢了吗?
咱们应用线程池/内存队列在很多场景下都是为了进步吞吐量,有得就必有失。至于重启服务器导致内存数据的失落,就看你评估对本人的业务带来多少的影响了。
针对这种问题,austin
自身就开发好了相干的性能作为「补充」,通过实时计算引擎flink
的能力能够实时在后盾查看音讯下发的状况:
能够在离线
hive找到音讯下发失败的userId
(离线这块暂未实现),输出具体的receiverId
能够查看实时下发时失败的起因
查明起因之后再通过csv
文件上传的做补发。
不过,这是平台提供做补发的能力,从技术上的角度,还有别的思路尽量避免线程池或者内存队列的数据因重启而失落的数据吗?有的,优雅敞开线程池
02、优雅停机
所谓「优雅停机」就是敞开的时候先将本人须要解决的内容解决完了,之后才敞开。如果你间接kill -9
,是没有「优雅」这一说法的,神仙都救不了。
1、在网络层:TCP有四次挥手、TCP KeepAlive
、HTTP KeepAlive
让连贯 优雅地敞开,防止很多报错。
2、在Java里边通过Runtime.getRuntime().addShutdownHook()
注册事件,当虚拟机敞开的前调用该办法的具体逻辑进行善后。
3、在Spring里边执行了ApplicationContext
的close
之后,只有咱们Bean配置了destroy
策略,那Spring在敞开之前也会先执行咱们的已实现好的destroy
办法
4、在Tomcat容器提供了几种敞开的姿态,先暂停申请,抉择期待N秒才齐全敞开容器。
5、在Java线程池提供了shutdown
和shutdownNow
供咱们敞开线程,显然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 有无依赖中间件实现动静线程池,也有默认实现Nacos
和Apollo
的版本,并有着治理后盾,而dynamic-tp 默认实现依赖Nacos
或Apollo
。大佬们的代码都写得很不错,我举荐大家都能够去学学。
我在最后的时候接的是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