终篇 支持JDK19虚拟线程的web框架,之五:兴风作浪的ThreadLocal( 二 )

  • 如果您的应用对内存有较严要求,quarkus官方建议您继续坚持(stick)使用反应式框架(这话中透露出浓浓的无可奈何 , 别催了,搞不定...)
    • 接下来官方就要甩锅了,有趣的是,这次接锅的并非JDK,而是大名鼎鼎的...Netty
    Netty的问题
    • 为什么是Netty接锅呢?
    • 首先 , Netty使用了Reactor线程模型,而Netty Reactor的核心是Event Loop,下图来自《Netty in Action》,是处理web请求的内部架构图,

    终篇 支持JDK19虚拟线程的web框架,之五:兴风作浪的ThreadLocal

    文章插图
    • 那么 , 应该有多少个EventLoop线程呢?下图是Netty源码 , 默认值是CPU核数的2倍,看得出这是个很保守的数字

    终篇 支持JDK19虚拟线程的web框架,之五:兴风作浪的ThreadLocal

    文章插图
    • 从上面的架构图和代码可以看出,Netty的反应式框架的核心是使用少量线程来分发web请求,这样的结果仅使用了少量线程资源就能高效处理事件
    • 也正式因为有了线程数不多这个前提 , 在对JSON做序列化处理时,Netty放心的使用了ThreadLocal,毕竟线程少 , 一个4核的CPU也才8个ThreadLocal,毫无压力
    • 而且,为了更加高效,Netty还对ThreadLoacal进行过改造,也就是他们自研的FastThreadLocal
    • 然后,时间一天天过去 , 终于等来了JDK19发布,
    • quarkus的反应式web服务模块底层就是Netty , 为了用上虚拟线程,他们动手了...咱们脑补一下吧,铺天盖地的虚拟线程线程 , 铺天盖地的FastThreadLocal对象,炸了吧您...Are U OK ?
    • 快乐之后,咱们还是要正视这个问题,表面上看是个坑,实际上是两种设计思路的冲突:
    1. 虚拟线程的特性类似golang的协程,很适合直接拿来处理高并发web请求,为每个请求分配一个虚拟线程,逻辑清晰直白 , 资源消耗又不高,典型的简单高效
    2. Netty的反应式模型,核心思路就是用少量线程高效分发大量请求 , 本身就很高效,而且就算优化,线程数也不是瓶颈
    3. 所以,quarkus拎着虚拟线程冲到Netty的地盘一阵操作猛如虎 , 一看结果...唉,扯远了,来看quarkus官方的解释吧

    终篇 支持JDK19虚拟线程的web框架,之五:兴风作浪的ThreadLocal

    文章插图
    • 上图红框中那句话很有价值,咱们都能从中领悟到一些东西,我的收获是:当线程数不是系统瓶颈的时候,就别冲动,强行上虚拟线程没用
    quarkus强行挽尊
    • 既然虚拟线程不适合反应式模型,个人认为:那就不妨大大方方的承认Netty的Reactor是优秀的,放弃将虚拟线程加入进来,这样不是挺好么?
    • 然而quarkus接下来的操作还是把我吓到了:既然虚拟线程不适合反应式模型?那就想办法强行让它适合,下图就是quarkus的做法:在构建阶段,找到创建ThreadLocal的那段代码,修改它的字节码,以此来解决前面的内存问题

    终篇 支持JDK19虚拟线程的web框架,之五:兴风作浪的ThreadLocal

    文章插图
    • 然后我就翻到了上图提到的那段代码

    终篇 支持JDK19虚拟线程的web框架,之五:兴风作浪的ThreadLocal

    文章插图
    • 好奇心驱使,我点开上图那个NettyCurrentAdaptor去看了下源码,当时就一阵头晕眼花,ASM风格的代码您能撑多久?试试下图

    终篇 支持JDK19虚拟线程的web框架,之五:兴风作浪的ThreadLocal

    文章插图
    • 按照官方的说法,经过他们的优化有百分之八十的提升,终于快要达到之前反应式框架的水平了
    • 呃 , 搞得这么辛苦,也只是快要追上而已,那行,咱不用了行吗?
    • 另外,上面说的优化手段也不是默认开启的 , 还要做以下几步操作
    1. maven的pom.xml添加以下依赖
    <dependency><groupId>io.quarkus</groupId><artifactId>quarkus-netty-loom-adaptor</artifactId></dependency>
    1. 编译构建的时候,增加参数-Dnet.bytebuddy.experimental
    2. 启动的时候,增加参数--add-opens java.base/java.lang=ALL-UNNAMED
    • 上述操作算 , quarkus的手段,我这个草根只能仰望,能开拓自己的见识:原来还可以这样解决问题
    • 但我自己是绝对不敢模仿的,开玩笑,在编辑阶段注入代码,难度太大,并且后面如何维护和交接?
    小结