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

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

读不下去不要紧,我写的真的很辛苦的,帮忙拉到最后点个赞吧。


本文目录


第一节:官方发布


本小节主要是通过官方发布的一篇名为《Dubbo 发布里程碑版本,性能提升30%》的文章作为引子,引出本文所要分享的内容:客户端线程模型优化。


第二节:官网上的介绍


在介绍优化后的消费端线程模型之前,先简单的介绍一下Dubbo的线程模型是什么。同时发现官方文档对于该部分的介绍十分简略,所以结合代码对其进行补充说明。


第三节:2.7.5版本之前的线程模型的问题



通过一个issue串联本小节,道出并分析一些消费端应用,当面临需要消费大量服务且并发数比较大的大流量场景时(典型如网关类场景),经常会出现消费端线程数分配过多的问题。


第四节:thredless是什么


通过第三节引出了新版本的解决方案,thredless。并对其进行一个简单的介绍。


第五节:场景复现


由于条件有限,场景复现起来比较麻烦,但是我在issues#890中发现了一个很好的终结,所以我搬过来了。


第六节:新旧线程模型对比


本小节通过对比新老线程模型的调用流程,并对比2.7.4.1版本和2.7.5版本关键的代码,起到一个导读的作用。


第七节:Dubbo版本介绍。


趁着这次的版本升级,也趁机介绍一下Dubbo目前的两个主要版本:2.6.X和2.7.X。


官方发布


2020年1月9日,阿里巴巴中间件发布名为《Dubbo 发布里程碑版本,性能提升30%》的文章:


文章中说这是Dubbo的一个里程碑式的版本。


在阅读了相关内容后,我发现这确实是一个里程碑式的跨域,对于Dubbo坎坷的一生来说,这是展现其强大的生命力和积极探索精神的一个版本。


强大的生命力体现在新版本发布后众多的或赞扬、或吐槽的社区反馈。


探索精神体现在Dubbo在多语言和协议穿透性上的探索。


在文章中列举了9大改造点,本文仅介绍2.7.5版本中的一个改造点:优化后的消费端线程模型。


本文大部分源码为2.7.5版本,同时也会有2.7.4.1版本的源码作为对比。


官网上的介绍


在介绍优化后的消费端线程模型之前,先简单的介绍一下Dubbo的线程模型是什么。

直接看官方文档中的描述,Dubbo官方文档是一份非常不错的入门学习的文档,很多知识点都写的非常详细。


可惜,在线程模型这块,差强人意,寥寥数语,图不达意:


官方的配图中,完全没有体现出线程"池"的概念,也没有体现出同步转异步的调用链路。仅仅是一个远程调用请求的发送与接收过程,至于响应的发送与接收过程,这张图中也没有表现出来。


所以我结合官方文档和2.7.5版本的源码进行一个简要的介绍,在阅读源码的过程中你会发现:


在客户端,除了用户线程外,还会有一个线程名称为DubboClientHandler-ip:port的线程池,其默认实现是cache线程池。


上图的第93行代码的含义是,当客户端没有指定threadpool时,采用cached实现方式。

上图中的setThreadName方法,就是设置线程名称:

org.apache.dubbo.common.utils.ExecutorUtil#setThreadName


可以清楚的看到,线程名称如果没有指定时,默认是DubboClientHandler-ip:port。

在服务端,除了有boss线程、worker线程(io线程),还有一个线程名称为DubboServerHandler-ip:port的线程池,其默认实现是fixed线程池。


启用线程池的dubbo.xml配置如下:

<dubbo:protocol name="dubbo" threadpool="xxx"/>

上面的xxx可以是fixed、cached、limited、eager,其中fixed是默认实现。当然由于是SPI,所以也可以自行扩展:


所以,基于最新2.7.5版本,官方文档下面红框框起来的这个地方,描述的有误导性:


从SPI接口看来,fixed确实是缺省值。


但是由于客户端在初始化线程池之前,加了一行代码(之前说的93行),所以客户端的默认实现是cached,服务端的默认实现是fixed。


我也看了之前的版本,至少在2.6.0时(更早之前的版本没有查看),客户端的线程池的默认实现就是cached。


关于Dispatcher部分的描述是没有问题的:


Dispatcher部分是线程模型中一个比较重要的点,后面会提到。

这里配一个稍微详细一点的2.7.5版本之前的线程模型,供大家参考:


图片来源:https://github.com/apache/dubbo/issues/890

2.7.5之前的线程模型的问题


那么改进之前的线程模型到底存在什么样的问题呢?


在《Dubbo 发布里程碑版本,性能提升30%》一文中,是这样描述的:


对 2.7.5 版本之前的 Dubbo 应用,尤其是一些消费端应用,当面临需要消费大量服务且并发数比较大的大流量场景时(典型如网关类场景),经常会出现消费端线程数分配过多的问题。


同时文章给出了一个issue的链接:


https://github.com/apache/dubbo/issues/2013

这一小节,我就顺着这个issue#2013给大家捋一下Dubbo 2.7.5版本之前的线程模型存在的问题,准确的说,是客户端线程模型存在的问题:


首先,Jaskey说到,分析了issue#1932,他说在某些情况下,会创建非常多的线程,因此进程会出现OOM的问题。


在分析了这个问题之后,他发现客户端使用了一个缓存线程池(就是我们前面说的客户端线程实现方式是cached),它并没有限制线程大小,这是根本原因。


接下来,我们去issue#1932看看是怎么说的:

https://github.com/apache/dubbo/issues/1932


可以看到issue#1932也是Jaskey提出的,他主要传达了一个意思:为什么我设置了actives=20,但是在客户端却有超过10000个线程名称为DubboClientHandler的线程的状态为blocked?这是不是一个Bug呢?


仅就这个issue,我先回答一下这个:不是Bug!

我们先看看actives=20的含义是什么:


按照官网上的解释:actives=20的含义是每个服务消费者每个方法最大并发调用数为20。


也就是说,服务端提供一个方法,客户端调用该方法,同时最多允许20个请求调用,但是客户端的线程模型是cached,接受到请求后,可以把请求都缓存到线程池中去。所以在大量的比较耗时的请求的场景下,客户端的线程数远远超过20。


这个actives配置在《一文讲透Dubbo负载均衡之最小活跃数算法》这篇文章中也有说明。它的生效需要配合ActiveLimitFilter过滤器,actives的默认值为0,表示不限制。当actives>0时,ActiveLimitFilter自动生效。由于不是本文重点,就不在这里详细说明了,有兴趣的可以阅读之前的文章。


顺着issue#2013捋下去,我们可以看到issue#1896提到的这个问题:


问题1我已经在前面解释了,他这里的猜测前半句对,后半句错。不再多说。


这里主要看问题2(可以点开大图看看):服务提供者多了,消费端维护的线程池就多了。导致虽然服务提供者的能力大了,但是消费端有了巨大的线程消耗。他和下面issue#4467的哥们表达的是同一个意思:想要的是一个共享的线程池。

我们接着往下捋,可以发现issue#4467和issue#5490


对于issue#4467,CodingSinger说:为什么Dubbo对每一个链接都创建一个线程池?


从Dubbo 2.7.4.1的源码我们也可以看到确实是在WarppedChannelHandler构造函数里面确实是为每一个连接都创建了一个线程池:


issue#4467想要表达的是什么意思呢?

就是这个地方为什么要做链接级别的线程隔离,一个客户端,就算有多个连接都应该用共享线程池呀?


我个人也觉得这个地方不应该做线程隔离。线程隔离的使用场景应该是针对一些特别重要的方法或者特别慢的方法或者功能差异较大的方法。很显然,Dubbo的客户端就算一个方法有多个连接(配置了connections参数),也是一视同仁,不太符合线程隔离的使用场景。


然后chickenij大佬在2019年7月24日回复了这个issue:


现有的设计就是:provider端默认共用一个线程池。consumer端是每个链接共享一个线程池。

同时他也说了:对于consumer线程池,当前正在尝试优化中。


言外之意是他也觉得现有的consumer端的线程模型也是有优化空间的。


这里插一句:chickenlj是谁呢?


刘军,GitHub账号Chickenlj,Apache Dubbo PMC,项目核心维护者,见证了Dubbo从重启开源到Apache毕业的整个流程。现任职阿里云云原生应用平台团队,参与服务框架、微服务相关工作,目前主要在推动Dubbo开源的云原生化。


他这篇文章的作者呀,他的话还是很有分量的。

之前也在Dubbo开发者日成都站听到过他的分享:


如果对他演讲的内容有兴趣的朋友可以在公众号的后台回复:1026。领取讲师PPT和录播地址。

好了,我们接着往下看之前提到的issue#5490,刘军大佬在2019年12月16日就说了,在2.7.5版本时会引入threadless executor机制,用于优化、增强客户端线程模型。


threadless是什么?


根据类上的说明我们可以知道:


这个Executor和其他正常Executor之间最重要的区别是这个Executor不管理任何线程。

通过execute(Runnable)方法提交给这个执行器的任务不会被调度到特定线程,而其他的Executor就把Runnable交给线程去执行了。


这些任务存储在阻塞队列中,只有当thead调用waitAndDrain()方法时才会真正执行。简单来说就是,执行task的thead与调用waitAndDrain()方法的thead完全相同。


其中说到的waitAndDrain()方法如下:



execute(Runnable)方法如下:


同时我们还可以看到,里面还维护了一个名称叫做sharedExecutor的线程池。见名知意,我们就知道了,这里应该是要做线程池共享了。

目录
打赏
0
0
0
0
6
分享
相关文章
Redis的线程模型
Redis采用单线程模型确保操作的原子性,每次只执行一个操作,避免并发冲突。它通过MULTI/EXEC事务机制、Lua脚本和复合指令(如MSET、GETSET等)保证多个操作要么全成功,要么全失败,确保数据一致性。Redis事务在EXEC前失败则不执行任何操作,EXEC后失败不影响其他操作。Pipeline虽高效但不具备原子性,适合非热点时段的数据调整。Redis 7引入Function功能,支持函数复用,简化复杂业务逻辑。总结来说,Redis的单线程模型简单高效,适用于高并发场景,但仍需合理选择指令执行方式以发挥其性能优势。
31 6
MySQL底层概述—3.InnoDB线程模型
InnoDB存储引擎采用多线程模型,包含多个后台线程以处理不同任务。主要线程包括:IO Thread负责读写数据页和日志;Purge Thread回收已提交事务的undo日志;Page Cleaner Thread刷新脏页并清理redo日志;Master Thread调度其他线程,定时刷新脏页、回收undo日志、写入redo日志和合并写缓冲。各线程协同工作,确保数据一致性和高效性能。
MySQL底层概述—3.InnoDB线程模型
|
3月前
|
多线程优化For循环:实战指南
本文介绍如何使用多线程优化For循环,提高程序处理大量数据或耗时操作的效率。通过并行任务处理,充分利用多核处理器性能,显著缩短执行时间。文中详细解释了多线程基础概念,如线程、进程、线程池等,并提供了Python代码示例,包括单线程、多线程和多进程实现方式。最后,还总结了使用多线程或多进程时需要注意的事项,如线程数量、任务拆分、共享资源访问及异常处理等。
55 7
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
190 1
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
Netty运行原理问题之Netty的主次Reactor多线程模型工作的问题如何解决
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
178 3
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
剖析 Redis List 消息队列的三种消费线程模型
Redis 列表(List)是一种简单的字符串列表,它的底层实现是一个双向链表。 生产环境,很多公司都将 Redis 列表应用于轻量级消息队列 。这篇文章,我们聊聊如何使用 List 命令实现消息队列的功能以及剖析消费者线程模型 。
152 20
剖析 Redis List 消息队列的三种消费线程模型
|
6月前
|
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
56 1