Dubbo 2.7.6在线程模型上的优化(下)

简介: Dubbo 2.7.6在线程模型上的优化(下)

场景复现


上面说了这么多2.7.5版本之前的线程模型的问题,我们怎么复现一次呢?


我这里条件有限,场景复现起来比较麻烦,但是我在issues#890中发现了一个很好的终结,我搬过来即可:


根据他接下来的描述做出思维导图如下:


上面说的是corethreads大于0的场景。但是根据现有的线程模型,即使核心池数(corethreads)为0,当消费者应用依赖的服务提供者处理很慢时且请求并发量比较大时,也会出现消费者线程数很多问题。大家可以对比着看一下。


新旧线程模型对比


在之前的介绍中大家已经知道了,这次升级主要是增强客户端线程模型,所以关于2.7.5版本之前和之后的线程池模型我们主要关心Consumer部分。


老的线程模型


老的线程池模型如下,注意线条颜色:


1、业务线程发出请求,拿到一个 Future 实例。


2、业务线程紧接着调用 future.get 阻塞等待业务结果返回。 3、当业务数据返回后,交由独立的 Consumer 端线程池进行反序列化等处理,并调用 future.set 将反序列化后的业务结果置回。 4、业务线程拿到结果直接返回。


新的线程模型


新的线程池模型如下,注意线条颜色:


1、业务线程发出请求,拿到一个 Future 实例。 2、在调用 future.get() 之前,先调用 ThreadlessExecutor.wait(),wait 会使业务线程在一个阻塞队列上等待,直到队列中被加入元素。 3、当业务数据返回后,生成一个 Runnable Task 并放ThreadlessExecutor 队列。 4、业务线程将 Task 取出并在本线程中执行反序列化业务数据并 set 到 Future。 5、业务线程拿到结果直接返回。


可以看到,相比于老的线程池模型,新的线程模型由业务线程自己负责监测并解析返回结果,免去了额外的消费端线程池开销。


代码对比


接下来我们对比一下2.7.4.1版本和2.7.5版本的代码,来说明上面的变化。


需要注意的是,由于涉及到的变化代码非常的多,我这里仅仅起到一个导读的作用,如果读者想要详细了解相关变化,还需要自己仔细阅读源码


首先两个版本的第一步是一样的:业务线程发出请求,拿到一个Future实例。


但是实现代码却有所差异,在2.7.4.1版本中,如下代码所示:


上图圈起来的request方法最终会走到这个地方,可以看到确实是返回了一个Future实例:


而newFuture方法源码如下,请记住这个方法,后面会进行对比:


同时通过源码可以看到在获取到Future实例后,紧接着调用了subscribeTo方法,实现方法如下:


用了Java 8的CompletableFuture,实现异步编程。


但是在2.7.5版本中,如下代码所示:


在request方法中多了个executor参数,而该参数就是的实现类就是ThreadlessExecutor。


接下来,和之前的版本一样,会通过newFuture方法去获取一个DefaultFuture对象:


通过和2.7.4.1版本的newFuture方法对比你会发现这个地方就大不一样了。虽然都是获取Future,但是Future里面的内容不一样了。


直接上个代码对比图,一目了然:


第二步:业务线程紧接着调用 future.get 阻塞等待业务结果返回。


由于Dubbo默认是同步调用,而同步和异步调用的区别我在第一篇文章《Dubbo 2.7新特性之异步化改造》中就进行了详细解析:


我们找到异步转同步的地方,先看2.7.4.1版本的如下代码所示:


而这里的asyncResult.get()对应的源码是,CompletableFuture.get():


而在2.7.5版本中对应的地方发生了变化:


变化就在这个asyncResult.get方法上。

在2.7.5版本中,该方法的实现源码是:


先说标号为②的地方,和2.7.4.1版本是一样的,都是调用的CompletableFuture.get()。但是多了标号为①的代码逻辑。而这段代码就是之前新的线程模型里面体现的地方,下面红框框起来的部分:


在调用 future.get() 之前(即调用标号为②的代码之前),先调用 ThreadlessExecutor.wait()(即标号为①处的逻辑),wait 会使业务线程在一个阻塞队列上等待,直到队列中被加入元素。


接下来再对比两个地方:


第一个地方:之前提到的WrappedChannelHandler,可以看到2.7.5版本其构造函数的改造非常大:


第二个地方:之前提到的Dispatcher,是需要再写一篇文章才能说的清楚的,我这仅仅是做一个抛砖引玉,提一下:


AllChannelHandler是默认的策略,证明代码如下:


首先还是看标号为②的地方,看起来变化很大,其实就是对代码进行了一个抽离,封装。sendFeedback方法如下,和2.7.4.1版本中标号为②的地方的代码是一样的:



所以我们重点对比一下两个标号为①的地方,它们获取executor的方法变了:


2.7.4.1版本的方法是getExecutorService()
2.7.5版本的方法是getPreferredExecutorService()


代码如下,大家品一品两个版本之前的差异:



主要翻译一下getPreferredExecutorService方法上的注释:

Currently, this method is mainly customized to facilitate the thread model on consumer side.
1. Use ThreadlessExecutor, aka., delegate callback directly to the thread initiating the call.   
2. Use shared executor to execute the callback.


目前,使用这种方法主要是为了客户端的线程模型而定制的。


1.使用ThreadlessExceutor,aka.,将回调直接委托给发起调用的线程。 2.使用shared executor执行回调。


小声说一句:这里这个aka怎么翻译,我实在是不知道了。难道是嘻哈里面的AKA?大家好,我是宝石GEM,aka(又名) 你的老舅。又画彩虹又画龙的。


好了,导读就到这里了。能看到这个地方的人我相信已经不多了。还是之前那句话由于涉及到的变化代码非常的多,我这里仅仅起到一个导读的作用,如果读者想要详细了解相关变化,还需要自己仔细阅读源码。希望你能自己搭个Demo跑一跑,对比一下两个版本的差异。



Dubbo版本介绍


趁着这次的版本升级,也趁机介绍一下Dubbo目前的主要版本吧。


据刘军大佬的分享:Dubbo 社区目前主力维护的有 2.6.x 和 2.7.x 两大版本,其中:


2.6.x 主要以 bugfix 和少量 enhancements 为主,因此能完全保证稳定性。


2.7.x 作为社区的主要开发版本,得到持续更新并增加了大量新 feature 和优化,同时也带来了一些稳定性挑战。


为方便 Dubbo 用户升级,社区在以下表格对 Dubbo 的各个版本进行了总结,包括主要功能、稳定性和兼容性等,从多个方面评估每个版本,以期能帮助用户完成升级评估:


可以看到社区对于最新的2.7.5版本的升级建议是:不建议大规模生产使用。

同时你去看Dubbo最新的issue,有很多都是对于2.7.5版本的"吐槽"。


但是我倒是觉得2.7.5是Dubbo发展进程中浓墨重彩的一笔,该版本打响了对于 Dubbo向整个微服务云原生体系靠齐的第一枪。对于多语言的支持方向的探索。实现了对 HTTP/2 协议的支持,同时增加了与 Protobuf 的结合。


开源项目,共同维护。我们当然知道Dubbo不是一个完美的框架,但是我们也知道,它的背后有一群知道它不完美,但是仍然不言乏力、不言放弃的工程师,他们在努力改造它,让它趋于完美。我们作为使用者,我们少一点"吐槽",多一点鼓励。只有这样我们才能骄傲的说,我们为开源世界贡献了一点点的力量,我们相信它的明天会更好。

向开源致敬,向开源工程师致敬。


总之,牛逼。


最后说一句


才疏学浅,难免会有纰漏,如果你发现了错误的地方,还请你留言给我指出来,我对其加以修改。


感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。

目录
相关文章
|
5月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
146 1
|
16天前
|
并行计算 算法 安全
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
68 3
|
5月前
|
编解码 网络协议 API
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
|
1月前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
3月前
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
3月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
28 1
|
3月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
76 4
|
4月前
|
消息中间件 存储 NoSQL
剖析 Redis List 消息队列的三种消费线程模型
Redis 列表(List)是一种简单的字符串列表,它的底层实现是一个双向链表。 生产环境,很多公司都将 Redis 列表应用于轻量级消息队列 。这篇文章,我们聊聊如何使用 List 命令实现消息队列的功能以及剖析消费者线程模型 。
112 20
剖析 Redis List 消息队列的三种消费线程模型
|
3月前
|
NoSQL Redis 数据库
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快
本文解释了Redis为什么采用单线程模型,以及为什么Redis单线程模型的效率和速度依然可以非常高,主要原因包括Redis操作主要访问内存、核心操作简单、单线程避免了线程竞争开销,以及使用了IO多路复用机制epoll。
66 0
Redis单线程模型 redis 为什么是单线程?为什么 redis 单线程效率还能那么高,速度还能特别快
|
3月前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。