消费端线程模型,提供者端线程模型
Dubbo 消费端线程池模型用法
消费端线程模型
对 2.7.5 版本之前的 Dubbo 应用,尤其是一些消费端应用,当面临需要消费大量服务且并发数比较大的大流量场景时(典型如网关类场景),经常会出现消费端线程数分配过多的问题,具体问题讨论可参见 Need a limited Threadpool in consumer side #2013
改进后的消费端线程池模型,通过复用业务端被阻塞的线程,很好的解决了这个问题。
老的线程池模型

我们重点关注 Consumer 部分:
- 业务线程发出请求,拿到一个 Future 实例。
- 业务线程紧接着调用 future.get 阻塞等待业务结果返回。
- 当业务数据返回后,交由独立的 Consumer 端线程池进行反序列化等处理,并调用 future.set 将反序列化后的业务结果置回。
- 业务线程拿到结果直接返回
当前线程池模型

- 业务线程发出请求,拿到一个 Future 实例。
- 在调用 future.get() 之前,先调用 ThreadlessExecutor.wait(),wait 会使业务线程在一个阻塞队列上等待,直到队列中被加入元素。
- 当业务数据返回后,生成一个 Runnable Task 并放入 ThreadlessExecutor 队列
- 业务线程将 Task 取出并在本线程中执行:反序列化业务数据并 set 到 Future。
- 业务线程拿到结果直接返回
这样,相比于老的线程池模型,由业务线程自己负责监测并解析返回结果,免去了额外的消费端线程池开销。
提供端线程模型
Dubbo协议的和Triple协议目前的线程模型还并没有对齐,下面分开介绍Triple协议和Dubbo协议的线程模型。
Dubbo协议
介绍Dubbo协议的Provider端线程模型之前,先介绍Dubbo对channel上的操作抽象成了五种行为:
- 建立连接:connected,主要是的职责是在channel记录read、write的时间,以及处理建立连接后的回调逻辑,比如dubbo支持在断开后自定义回调的hook(onconnect),即在该操作中执行。
- 断开连接:disconnected,主要是的职责是在channel移除read、write的时间,以及处理端开连接后的回调逻辑,比如dubbo支持在断开后自定义回调的hook(ondisconnect),即在该操作中执行。
- 发送消息:sent,包括发送请求和发送响应。记录write的时间。
- 接收消息:received,包括接收请求和接收响应。记录read的时间。
- 异常捕获:caught,用于处理在channel上发生的各类异常。
Dubbo框架的线程模型与以上这五种行为息息相关,Dubbo协议Provider线程模型可以分为五类,也就是AllDispatcher、DirectDispatcher、MessageOnlyDispatcher、ExecutionDispatcher、ConnectionOrderedDispatcher。
配置方式
| 线程模型 | 配置值 |
|---|---|
| All Dispatcher | all |
| Direct Dispatcher | direct |
| Execution Dispatcher | execution |
| Message Only Dispatcher | message |
| Connection Ordered Dispatcher | connection |
拿 application.yaml 的配置方式举例:在protocol下配置dispatcher: all,即可把dubbo协议的线程模型调整为All Dispatcher
1. dubbo:
2. application:
3. name: dubbo-springboot-demo-provider
4. protocol:
5. name: dubbo
6. port: -1
7. dispatcher: all
8. registry:
9. id: zk-registry
10. address: zookeeper://127.0.0.1:2181
All Dispatcher
下图是All Dispatcher的线程模型说明图:

- 在IO线程中执行的操作有:
- sent操作在IO线程上执行。
- 序列化响应在IO线程上执行。
- 在Dubbo线程中执行的操作有:
- received、connected、disconnected、caught都是在Dubbo线程上执行的。
- 反序列化请求的行为在Dubbo中做的。
Direct Dispatcher
下图是Direct Dispatcher的线程模型说明图:

- 在IO线程中执行的操作有:
- received、connected、disconnected、caught、sent操作在IO线程上执行。
- 反序列化请求和序列化响应在IO线程上执行。
-
- 并没有在Dubbo线程操作的行为。
Execution Dispatcher
下图是Execution Dispatcher的线程模型说明图:

- 在IO线程中执行的操作有:
- sent、connected、disconnected、caught操作在IO线程上执行。
- 序列化响应在IO线程上执行。
- 在Dubbo线程中执行的操作有:
- received都是在Dubbo线程上执行的。
- 反序列化请求的行为在Dubbo中做的。
Message Only Dispatcher
在Provider端,Message Only Dispatcher和Execution Dispatcher的线程模型是一致的,所以下图和Execution Dispatcher的图一致,区别在Consumer端。见下方Consumer端的线程模型。
下图是Message Only Dispatcher的线程模型说明图:

- 在IO线程中执行的操作有:
- sent、connected、disconnected、caught操作在IO线程上执行。
- 序列化响应在IO线程上执行。
- 在Dubbo线程中执行的操作有:
- received都是在Dubbo线程上执行的。
- 反序列化请求的行为在Dubbo中做的。
Connection Ordered Dispatcher
下图是Connection Ordered Dispatcher的线程模型说明图:

- 在IO线程中执行的操作有:
- sent操作在IO线程上执行。
- 序列化响应在IO线程上执行。
- 在Dubbo线程中执行的操作有:
- received、connected、disconnected、caught都是在Dubbo线程上执行的。但是connected和disconnected两个行为是与其他两个行为通过线程池隔离开的。并且在Dubbo connected thread pool中提供了链接限制、告警灯能力。
- 反序列化请求的行为在Dubbo中做的。
Triple协议
下图为Triple协议 Provider端的线程模型

Triple协议Provider线程模型目前还比较简单,目前序列化和反序列化操作都在Dubbo线程上工作,而IO线程并没有承载这些工作。
线程池隔离
一种新的线程池管理方式,使得提供者应用内各个服务的线程池隔离开来,互相独立,某个服务的线程池资源耗尽不会影响其他正常服务。支持线程池可配置化,由用户手动指定。
使用线程池隔离来确保 Dubbo 用于调用远程方法的线程与微服务用于执行其任务的线程是分开的。可以通过防止线程阻塞或相互竞争来帮助提高系统的性能和稳定性。
目前可以以 API、XML、Annotation 的方式进行配置
配置参数
ApplicationConfig新增String executor-management-mode参数,配置值为default和isolation,默认为default。executor-management-mode = default使用原有 以协议端口为粒度、服务间共享 的线程池管理方式executor-management-mode = isolation使用新增的 以服务三元组为粒度、服务间隔离 的线程池管理方式ServiceConfig新增Executor executor参数,用以服务间隔离的线程池,可以由用户配置化、提供自己想要的线程池,若没有指定,则会根据协议配置(ProtocolConfig)信息构建默认的线程池用以服务隔离。
ServiceConfig新增Executor executor配置参数只有指定executor-management-mode = isolation才生效。
API
1. public void test() {
2. // provider app
3. DubboBootstrap providerBootstrap = DubboBootstrap.newInstance();
5. ServiceConfig serviceConfig1 = new ServiceConfig();
6. serviceConfig1.setInterface(DemoService.class);
7. serviceConfig1.setRef(new DemoServiceImpl());
8. serviceConfig1.setVersion(version1);
9. // set executor1 for serviceConfig1, max threads is 10
10. NamedThreadFactory threadFactory1 = new NamedThreadFactory("DemoService-executor");
11. ExecutorService executor1 = Executors.newFixedThreadPool(10, threadFactory1);
12. serviceConfig1.setExecutor(executor1);
14. ServiceConfig serviceConfig2 = new ServiceConfig();
15. serviceConfig2.setInterface(HelloService.class);
16. serviceConfig2.setRef(new HelloServiceImpl());
17. serviceConfig2.setVersion(version2);
18. // set executor2 for serviceConfig2, max threads is 100
19. NamedThreadFactory threadFactory2 = new NamedThreadFactory("HelloService-executor");
20. ExecutorService executor2 = Executors.newFixedThreadPool(100, threadFactory2);
21. serviceConfig2.setExecutor(executor2);
23. ServiceConfig serviceConfig3 = new ServiceConfig();
24. serviceConfig3.setInterface(HelloService.class);
25. serviceConfig3.setRef(new HelloServiceImpl());
26. serviceConfig3.setVersion(version3);
27. // Because executor is not set for serviceConfig3, the default executor of serviceConfig3 is built using
28. // the threadpool parameter of the protocolConfig ( FixedThreadpool , max threads is 200)
29. serviceConfig3.setExecutor(null);
31. // It takes effect only if [executor-management-mode=isolation] is configured
32. ApplicationConfig applicationConfig = new ApplicationConfig("provider-app");
33. applicationConfig.setExecutorManagementMode("isolation");
35. providerBootstrap
36. .application(applicationConfig)
37. .registry(registryConfig)
38. // export with tri and dubbo protocol
39. .protocol(new ProtocolConfig("tri", 20001))
40. .protocol(new ProtocolConfig("dubbo", 20002))
41. .service(serviceConfig1)
42. .service(serviceConfig2)
43. .service(serviceConfig3);
45. providerBootstrap.start();
46. }
XML
1. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2. xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
3. xmlns="http://www.springframework.org/schema/beans"
4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
5. http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
7. <!-- NOTE: we need config executor-management-mode="isolation" -->
8. <dubbo:application name="demo-provider" executor-management-mode="isolation">
9. </dubbo:application>
11. <dubbo:config-center address="zookeeper://127.0.0.1:2181"/>
12. <dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>
13. <dubbo:registry id="registry1" address="zookeeper://127.0.0.1:2181?registry-type=service"/>
15. <dubbo:protocol name="dubbo" port="-1"/>
16. <dubbo:protocol name="tri" port="-1"/>
18. <!-- expose three service with dubbo and tri protocol-->
19. <bean id="demoServiceV1" class="org.apache.dubbo.config.spring.impl.DemoServiceImpl"/>
20. <bean id="helloServiceV2" class="org.apache.dubbo.config.spring.impl.HelloServiceImpl"/>
21. <bean id="helloServiceV3" class="org.apache.dubbo.config.spring.impl.HelloServiceImpl"/>
23. <!-- customized thread pool -->
24. <bean id="executor-demo-service"
25. class="org.apache.dubbo.config.spring.isolation.spring.support.DemoServiceExecutor"/>
26. <bean id="executor-hello-service"
27. class="org.apache.dubbo.config.spring.isolation.spring.support.HelloServiceExecutor"/>
29. <!-- this service use [executor="executor-demo-service"] as isolated thread pool-->
30. <dubbo:service executor="executor-demo-service"
31. interface="org.apache.dubbo.config.spring.api.DemoService" version="1.0.0" group="Group1"
32. timeout="3000" ref="demoServiceV1" registry="registry1" protocol="dubbo,tri"/>
34. <!-- this service use [executor="executor-hello-service"] as isolated thread pool-->
35. <dubbo:service executor="executor-hello-service"
36. interface="org.apache.dubbo.config.spring.api.HelloService" version="2.0.0" group="Group2"
37. timeout="5000" ref="helloServiceV2" registry="registry1" protocol="dubbo,tri"/>
39. <!-- not set executor for this service, the default executor built using threadpool parameter of the protocolConfig -->
40. <dubbo:service interface="org.apache.dubbo.config.spring.api.HelloService" version="3.0.0" group="Group3"
41. timeout="5000" ref="helloServiceV3" registry="registry1" protocol="dubbo,tri"/>
43. </beans>
Annotation
1. @Configuration
2. @EnableDubbo(scanBasePackages = "org.apache.dubbo.config.spring.isolation.spring.annotation.provider")
3. public class ProviderConfiguration {
4. @Bean
5. public RegistryConfig registryConfig() {
6. RegistryConfig registryConfig = new RegistryConfig();
7. registryConfig.setAddress("zookeeper://127.0.0.1:2181");
8. return registryConfig;
9. }
11. // NOTE: we need config executor-management-mode="isolation"
12. @Bean
13. public ApplicationConfig applicationConfig() {
14. ApplicationConfig applicationConfig = new ApplicationConfig("provider-app");
16. applicationConfig.setExecutorManagementMode("isolation");
17. return applicationConfig;
18. }
20. // expose services with dubbo protocol
21. @Bean
22. public ProtocolConfig dubbo() {
23. ProtocolConfig protocolConfig = new ProtocolConfig("dubbo");
24. return protocolConfig;
25. }
27. // expose services with tri protocol
28. @Bean
29. public ProtocolConfig tri() {
30. ProtocolConfig protocolConfig = new ProtocolConfig("tri");
31. return protocolConfig;
32. }
34. // customized thread pool
35. @Bean("executor-demo-service")
36. public Executor demoServiceExecutor() {
37. return new DemoServiceExecutor();
38. }
40. // customized thread pool
41. @Bean("executor-hello-service")
42. public Executor helloServiceExecutor() {
43. return new HelloServiceExecutor();
44. }
45. }
1. // customized thread pool
2. public class DemoServiceExecutor extends ThreadPoolExecutor {
3. public DemoServiceExecutor() {
4. super(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
5. new NamedThreadFactory("DemoServiceExecutor"));
6. }
7. }
1. // customized thread pool
2. public class HelloServiceExecutor extends ThreadPoolExecutor {
3. public HelloServiceExecutor() {
4. super(100, 100, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
5. new NamedThreadFactory("HelloServiceExecutor"));
6. }
7. }
1. // "executor-hello-service" is beanName
2. @DubboService(executor = "executor-demo-service", version = "1.0.0", group = "Group1")
3. public class DemoServiceImplV1 implements DemoService {
5. @Override
6. public String sayName(String name) {
7. return "server name";
8. }
10. @Override
11. public Box getBox() {
12. return null;
13. }
14. }
1. // not set executor for this service, the default executor built using threadpool parameter of the protocolConfig
2. @DubboService(version = "3.0.0", group = "Group3")
3. public class HelloServiceImplV2 implements HelloService {
4. private static final Logger logger = LoggerFactory.getLogger(HelloServiceImplV2.class);
6. @Override
7. public String sayHello(String name) {
8. return "server hello";
9. }
10. }
1. @DubboService(executor = "executor-hello-service", version = "2.0.0", group = "Group2")
2. public class HelloServiceImplV3 implements HelloService {
3. private static final Logger logger = LoggerFactory.getLogger(HelloServiceImplV3.class);
5. @Override
6. public String sayHello(String name) {
7. return "server hello";
8. }
9. }
线程池状态导出
dubbo 通过 Jstack 自动导出线程堆栈来保留现场,方便排查问题。
默认策略
- 导出路径: user.home标识的用户主目录
- 导出间隔: 最短间隔允许每隔10分钟导出一次
- 导出开关: 默认打开
当业务线程池满时,我们需要知道线程都在等待哪些资源、条件,以找到系统的瓶颈点或异常点。
导出开关控制
1. # dubbo.properties
2. dubbo.application.dump.enable=true
1. <dubbo:application name="demo-provider" dump-enable="false"/>
1. dubbo:
2. application:
3. name: dubbo-springboot-demo-provider
4. dump-enable: false
导出路径
1. # dubbo.properties
2. dubbo.application.dump.directory=/tmp
1. <dubbo:application name="demo-provider" dump-directory="/tmp"/>
1. dubbo:
2. application:
3. name: dubbo-springboot-demo-provider
4. dump-directory: /tmp
最后修改 September 13, 2024: Refactor website structure (#2860) (1a4b998f54b)
