為什么程序中突然多了 200 個(gè) Dubbo-thread 線程的說(shuō)明
背景
在某次查看程序線程堆棧信息時(shí),偶然發(fā)現(xiàn)有 200 個(gè) Dubbo-thread 線程,而且大部分都處于 WAITING 狀態(tài),如下所示:
"Dubbo-thread-200" #160932 daemon prio=5 os_prio=0 tid=0x00007f5af9b54800 nid=0x79a6 waiting on condition [0x00007f5a9acd5000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000c78f1240> (a java.util.concurrent.SynchronousQueue$TransferStack) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:458) at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:362) at java.util.concurrent.SynchronousQueue.take(SynchronousQueue.java:924) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Locked ownable synchronizers: - None
為什么會(huì)有這么多 Dubbo-thread 線程呢?這些線程有什么作用呢?帶著疑問(wèn)就去研究了下源碼。
源碼分析
Dubbo (2.7.5 版本)的線程池 ThreadPool 有四種具體的實(shí)現(xiàn)類(lèi)型:
fixed=org.apache.dubbo.common.threadpool.support.fixed.FixedThreadPool cached=org.apache.dubbo.common.threadpool.support.cached.CachedThreadPool limited=org.apache.dubbo.common.threadpool.support.limited.LimitedThreadPool eager=org.apache.dubbo.common.threadpool.support.eager.EagerThreadPool
程序通過(guò)調(diào)用具體實(shí)現(xiàn)類(lèi)的 getExecutor(URL url) 方法來(lái)創(chuàng)建線程池。而調(diào)用該方法的只有 DefaultExecutorRepository 類(lèi)的 createExecutor 方法,該方法會(huì)根據(jù) url 上的參數(shù) threadpool=cached 來(lái)決定創(chuàng)建那種類(lèi)型的線程池。createExecutor 是一個(gè)私有方法,調(diào)用它的有下面兩個(gè)方法:
/** * Get called when the server or client instance initiating. * * @param url * @return */ public synchronized ExecutorService createExecutorIfAbsent(URL url) { String componentKey = EXECUTOR_SERVICE_COMPONENT_KEY; if (CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) { componentKey = CONSUMER_SIDE; } Map<Integer, ExecutorService> executors = data.computeIfAbsent(componentKey, k -> new ConcurrentHashMap<>()); Integer portKey = url.getPort(); ExecutorService executor = executors.computeIfAbsent(portKey, k -> createExecutor(url)); // If executor has been shut down, create a new one if (executor.isShutdown() || executor.isTerminated()) { executors.remove(portKey); executor = createExecutor(url); executors.put(portKey, executor); } return executor; } public ExecutorService getExecutor(URL url) { String componentKey = EXECUTOR_SERVICE_COMPONENT_KEY; if (CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) { componentKey = CONSUMER_SIDE; } Map<Integer, ExecutorService> executors = data.get(componentKey); /** * It's guaranteed that this method is called after {@link #createExecutorIfAbsent(URL)}, so data should already * have Executor instances generated and stored. */ if (executors == null) { logger.warn("No available executors, this is not expected, framework should call createExecutorIfAbsent first " + "before coming to here."); return null; } Integer portKey = url.getPort(); ExecutorService executor = executors.get(portKey); if (executor != null) { if (executor.isShutdown() || executor.isTerminated()) { executors.remove(portKey); executor = createExecutor(url); executors.put(portKey, executor); } } return executor; }
對(duì)于上面第一個(gè)方法,備注已經(jīng)說(shuō)明在服務(wù)提供者或者服務(wù)消費(fèi)者初始化的時(shí)候會(huì)調(diào)用,通過(guò)debug 可以得出:服務(wù)提供者初始化會(huì)創(chuàng)建線程名為 DubboServerHandler-10.12.16.67:20880-thread 的線程池,服務(wù)消費(fèi)者會(huì)創(chuàng)建線程名為 DubboClientHandler-10.12.16.67:20880-thread 的線程池。
這里需要說(shuō)明下,Dubbo 創(chuàng)建的線程池會(huì)存儲(chǔ)在 Map 中共享使用:
private ConcurrentMap<String, ConcurrentMap<Integer, ExecutorService>> data = new ConcurrentHashMap<>();
外面的 key 表示服務(wù)提供方還是消費(fèi)方,里面的 key 表示服務(wù)暴露的端口號(hào),也就是說(shuō)消費(fèi)方對(duì)于相同端口號(hào)的服務(wù)只會(huì)創(chuàng)建一個(gè)線程池,共享同一個(gè)線程池進(jìn)行服務(wù)請(qǐng)求和消息接收后一系列處理。
顯然和 Dubbo-thread 名不一樣,那就很有可能是通過(guò)調(diào)用第二個(gè)方法創(chuàng)建的線程池。第二個(gè)方法的調(diào)用往上追溯就比較分散了,找不到什么有用的信息。
再看方法具體內(nèi)容,當(dāng)已經(jīng)創(chuàng)建的線程池關(guān)閉或終止時(shí)會(huì)重新創(chuàng)建新的線程池。然后就推測(cè)什么情況下線程池會(huì)被關(guān)閉或終止,在服務(wù)重啟后輸出堆棧信息并沒(méi)有 Dubbo-thread 線程,然后就猜測(cè)消費(fèi)方和提供方連接斷開(kāi)會(huì)不會(huì)觸發(fā)線程池關(guān)閉,于是重啟了服務(wù)提供方,果然重現(xiàn)了Dubbo-thread 線程。
然后在 Dubbo 的具體線程池創(chuàng)建方法中添加日志,輸出調(diào)用棧信息(通過(guò)產(chǎn)生一個(gè)異常輸出調(diào)用信息)。
如下圖:
在這里插入圖片描述可以看到當(dāng) channel 失效時(shí)會(huì)調(diào)用 disconnected 方法,最終會(huì)調(diào)用 DefaultExecutorRepository 類(lèi)的 getExecutor 創(chuàng)建線程池,當(dāng)服務(wù)提供者重啟時(shí),消費(fèi)方相應(yīng)的線程池會(huì)被shutdown。
重現(xiàn)創(chuàng)建線程池所用的 URL 是 WrappedChannelHandler 類(lèi)的 URL,該值是在服務(wù)啟動(dòng)初始化時(shí)設(shè)置的,該值的設(shè)置要早于 AbstractClient 客戶(hù)端 Executor 初始化。
因此由于 channel 斷開(kāi)而重新創(chuàng)建的線程池所用的 URL 和客戶(hù)端初始創(chuàng)建線程池用的 URL 可能是不同的,特別是在沒(méi)有配置 consumer 的線程池類(lèi)型時(shí),初始創(chuàng)建的 Cached 類(lèi)型線程池,線程名稱(chēng)是 DubboClientHandler…。
而重新創(chuàng)建所用 URL 是沒(méi)有經(jīng)過(guò)下面方法設(shè)置的,因此就會(huì)創(chuàng)建默認(rèn)類(lèi)型為 fixed 的線程池,線程數(shù)為默認(rèn) 200,線程名為 Dubbo…。
private void initExecutor(URL url) { url = ExecutorUtil.setThreadName(url, CLIENT_THREAD_POOL_NAME); url = url.addParameterIfAbsent(THREADPOOL_KEY, DEFAULT_CLIENT_THREADPOOL); executor = executorRepository.createExecutorIfAbsent(url); }
總結(jié)
那么,就可以知道 Dubbo-thread 線程池的創(chuàng)建是由于服務(wù)消費(fèi)方和提供方之間連接斷開(kāi)而創(chuàng)建的線程池,代替程序啟動(dòng)初始化時(shí)創(chuàng)建的 DubboClientHandler 線程池。主要做一些 channel 斷開(kāi)后續(xù)一些處理,還有接收服務(wù)端消息后的反序列化等操作,具體的可以看類(lèi) ThreadlessExecutor(同步調(diào)用處理類(lèi)) 、ChannelEventRunnable(channel 不同狀態(tài)處理,包括:連接、接收到消息、斷開(kāi)鏈接等)。
還有一個(gè)要注意到點(diǎn)是,如果沒(méi)有配置consumer.threadpool 類(lèi)型、therads 等信息,那么斷開(kāi)連接后再創(chuàng)建的線程池將會(huì)是 fixed 類(lèi)型的線程池,線程數(shù)為默認(rèn) 200。
以上這篇為什么程序中突然多了 200 個(gè) Dubbo-thread 線程的說(shuō)明就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- 淺析Java SPI 與 dubbo SPI
- Java和Dubbo的SPI機(jī)制原理解析
- SpringBoot+Dubbo+Zookeeper實(shí)現(xiàn)簡(jiǎn)單分布式開(kāi)發(fā)的應(yīng)用詳解
- 使用docker部署dubbo項(xiàng)目的方法步驟
- SpringBoot中dubbo+zookeeper實(shí)現(xiàn)分布式開(kāi)發(fā)的應(yīng)用詳解
- springboot整合dubbo設(shè)置全局唯一ID進(jìn)行日志追蹤的示例代碼
- python 如何調(diào)用 dubbo 接口
- 詳解SPI在Dubbo中的應(yīng)用
相關(guān)文章
Java判斷中英文符號(hào)、標(biāo)點(diǎn)的實(shí)現(xiàn)
本篇文章主要介紹了Java判斷中英文符號(hào)、標(biāo)點(diǎn)的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10SpringBoot中的錯(cuò)誤處理機(jī)制源碼解析
這篇文章主要介紹了SpringBoot中的錯(cuò)誤處理機(jī)制源碼解析,springboot根據(jù)訪問(wèn)者的request中的Accept屬性來(lái)判斷要返回什么樣的數(shù)據(jù),SpringBoot存在一個(gè)錯(cuò)誤處理機(jī)制,會(huì)根據(jù)不同請(qǐng)求返回不同的結(jié)果,需要的朋友可以參考下2023-12-12Redis有效時(shí)間設(shè)置以及時(shí)間過(guò)期處理操作
這篇文章主要介紹了Redis有效時(shí)間設(shè)置以及時(shí)間過(guò)期處理操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11IntelliJ Idea常用11款插件(提高開(kāi)發(fā)效率)
這篇文章主要介紹了IntelliJ Idea常用11款插件(提高開(kāi)發(fā)效率),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07IDEA無(wú)法識(shí)別相關(guān)module模塊問(wèn)題的解決過(guò)程
這篇文章主要給大家介紹了關(guān)于IDEA無(wú)法識(shí)別相關(guān)module模塊問(wèn)題的解決過(guò)程,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用IDEA具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07Spring Data JPA中的動(dòng)態(tài)查詢(xún)實(shí)例
本篇文章主要介紹了詳解Spring Data JPA中的動(dòng)態(tài)查詢(xún)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04Eclipse配置python開(kāi)發(fā)環(huán)境過(guò)程圖解
這篇文章主要介紹了Eclipse配置python開(kāi)發(fā)環(huán)境過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03Spring中@Autowired @Resource @Inject三個(gè)注解有什么區(qū)別
在我們使用Spring框架進(jìn)行日常開(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)使用@Autowired, @Resource, @Inject注解來(lái)進(jìn)行依賴(lài)注入,下面來(lái)介紹一下這三個(gè)注解有什么區(qū)別2023-03-03Java?如何通過(guò)注解實(shí)現(xiàn)接口輸出時(shí)數(shù)據(jù)脫敏
這篇文章主要介紹了Java?如何通過(guò)注解實(shí)現(xiàn)接口輸出時(shí)數(shù)據(jù)脫敏,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12