This is an automated email from the ASF dual-hosted git repository.
albumenj pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-awesome.git
The following commit(s) were added to refs/heads/master by this push:
new 89b4cb9 Proposal service executor isolation (#92)
89b4cb9 is described below
commit 89b4cb975322f55119bd9f6dbad031262d6cbc79
Author: 灼华 <[email protected]>
AuthorDate: Mon Oct 17 11:18:51 2022 +0800
Proposal service executor isolation (#92)
* Add D14-service-executor-isolation.md
* Add D14-service-executor-isolation.md
* Add desc
* Add and update documentation for isolating thread pools
Co-authored-by: geyu <[email protected]>
---
images/isolation/default_executor_mode.png | Bin 0 -> 222218 bytes
images/isolation/default_executor_mode_ex.png | Bin 0 -> 264242 bytes
images/isolation/isolation_executor_mode.png | Bin 0 -> 241832 bytes
.../D14-1-service-executor-isolation-think.md | 241 +++++++++++++++++++++
.../D14-2-service-executor-isolation-config.md | 216 ++++++++++++++++++
.../D14-3-service-executor-isolation-code-desc.md | 30 +++
6 files changed, 487 insertions(+)
diff --git a/images/isolation/default_executor_mode.png
b/images/isolation/default_executor_mode.png
new file mode 100644
index 0000000..a7b71d0
Binary files /dev/null and b/images/isolation/default_executor_mode.png differ
diff --git a/images/isolation/default_executor_mode_ex.png
b/images/isolation/default_executor_mode_ex.png
new file mode 100644
index 0000000..e39c5ef
Binary files /dev/null and b/images/isolation/default_executor_mode_ex.png
differ
diff --git a/images/isolation/isolation_executor_mode.png
b/images/isolation/isolation_executor_mode.png
new file mode 100644
index 0000000..40a4b14
Binary files /dev/null and b/images/isolation/isolation_executor_mode.png differ
diff --git a/proposals/D14-1-service-executor-isolation-think.md
b/proposals/D14-1-service-executor-isolation-think.md
new file mode 100644
index 0000000..8515fc4
--- /dev/null
+++ b/proposals/D14-1-service-executor-isolation-think.md
@@ -0,0 +1,241 @@
+# 概述
+
+## 线程池
+
+一个提供者应用暴露服务有两个关键步骤
+- 服务信息写入注册中心
+- 开启监听指定协议端口的服务器
+
+步骤2开启监听服务器的时候同时创建线程池,当消费者应用向提供者应用发起rpc请求的时候,会把rpc请求投递给线程池处理。
+
+## 现状
+目前线程池的设计是以**协议端口为粒度**的,假设某个提供者应用以 `triple` 协议暴露了多个服务,此时会以`triple`
协议配置的port为唯一键创建对应的线程池,即**不管该提供者应用暴露了多少个服务,都只会创建并共享这一个线程池**。当消费者应用向提供者应用的不同服务发起rpc调用的时候,在提供者的服务器这一侧,都会把请求投递到这个线程池去处理。
+
+## 问题
+倘若,提供者应用某个服务因为各种因素出现了问题,比如该服务处理请求的响应时间变长,而某个消费者还在不停地、高并发地调用这个服务,就会使得这个**异常服务把整个线程池都占满**了!而提供者应用还有其他正常的服务,但是因为线程池资源被异常服务耗尽导致其他正常的服务不能接受消费者的请求。
+
+## 优化
+因此需要引入一种**新的线程池管理方式**,使得应用内**各个服务的线程池隔离开来,互相独立**,某个服务的线程池资源耗尽不会影响其他正常服务。同时要支持**线程池可配置化**,由用户手动指定。
+
+# 隔离线程池优化思路
+
+## 管理线程池的容器
+
+### 线程池容器
+回到dubbo工程代码中,如下,我们发现提供者线程池是统一收敛在 `DefaultExecuteRepository` 中的核心容器 `data` 管理的
,该 `data` 就存储了所有的线程池。
+为了方便理解,只关注 `data` 的二级KV即可,即 `ConcurrentMap<Integer, ExecutorService>` 这部分。
+
+```java
+public class DefaultExecutorRepository implements ExecutorRepository,
ExtensionAccessorAware {
+
+ private final ConcurrentMap<String, ConcurrentMap<Integer,
ExecutorService>> data = new ConcurrentHashMap<>();
+}
+```
+
+### 线程池的存储
+#### 原有逻辑
+线程池的创建和存储主要是调用了如下方法,`data` 作为一个 `Map` 容器,**针对提供者**,Map 的 二级 **key 为协议的端口**,Map
的 **value 是由协议配置信息构建的线程池**。如下可以看到线程池存入的过程。
+```java
+// DefaultExecutorRepository#createExecutorIfAbsent
+public synchronized ExecutorService createExecutorIfAbsent(URL url) {
+ String executorKey = getExecutorKey(url);
+ Map<Integer, ExecutorService> executors =
data.computeIfAbsent(executorKey, k -> new ConcurrentHashMap<>());
+ // Consumer's executor is sharing globally, key=Integer.MAX_VALUE.
Provider's executor is sharing by protocol.
+ Integer portKey =
CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY)) ? Integer.MAX_VALUE
: url.getPort(); // 注意这里,Map 的 二级 key 为协议的端口
+
+ URL finalUrl = url;
+ ExecutorService executor = executors.computeIfAbsent(portKey, k ->
createExecutor(finalUrl)); // 注意这里,Map 的 value 是由协议配置信息构建的线程池
+ return executor;
+ }
+```
+#### 优化逻辑
+想要做到提供者应用的服务间线程池隔离,我们可以将 **data容器的二级key设置为各个服务的三元组,
value设置为由用户给服务指定的线程池。**(三元组即`interfaceName + version + group`,可以用
`url.serviceKey()` 获取,若用户未指定线程池则可以根据协议配置构建默认线程池)。
+
+### 线程池的获取
+
+#### 原有逻辑
+如下是原有线程池的获取逻辑,以协议端口为二级key来获取先前缓存的线程池。
+```java
+// DefaultExecutorRepository#getExecutor
+public ExecutorService getExecutor(URL url) {
+ Map<Integer, ExecutorService> executors =
data.get(getExecutorKey(url));
+ // Consumer's executor is sharing globally, key=Integer.MAX_VALUE.
Provider's executor is sharing by protocol.
+ Integer portKey =
CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY)) ? Integer.MAX_VALUE
: url.getPort();
+ ExecutorService executor = executors.get(portKey);
+ return executor;
+ }
+
+```
+#### 优化逻辑
+想要做到提供者应用的服务间线程池隔离,我们可以 **以各个服务的三元组为二级key** 从data容器中取出先前缓存的线程池。
+
+### 备注
+为了方便后面描述,`DefaultExecuteRepository#createExecutorIfAbsent` 记作
`createExecutorIfAbsent` ,`DefaultExecutorRepository#getExecutor` 记作
`getExecutor`。
+
+**同时为了后面流程的理解,我们要先把 `createExecutorIfAbsent` 和 `getExecutor`
当做是已经优化后、以服务间隔离的方式创建和获取线程池的。**
+
+
+## 隔离线程池的存储时机
+
+隔离线程池的存储其实就是通过调用上面的 `createExecutorIfAbsent` 方法进行存储的。 只是 `dubbo` 协议和 `triple`
协议在暴露服务的流程中,调用 `createExecutorIfAbsent` 方法的时机略有不同,下面针对这两种协议简单梳理下调用时机。
+
+### dubbo协议
+假设某个提供者应用以 `dubbo` 协议暴露了两个服务,会挨个调用 `ServiceConfig.export`,第一个服务最后会创建监听服务器
`server`,
+并以应用所在机器的 **address 作为key, server 做为 value** 缓存起来,这样做的目的是同一个机器只开启一个监听服务器。
+
+```java
+// DubboProtocol#openServer
+private void openServer(URL url) {
+ checkDestroyed();
+ // find server.
+ String key = url.getAddress(); // 注意这里,address作为serverMap缓存的key
+ ProtocolServer server = serverMap.get(key);
+ if (server == null) {
+ synchronized (this) {
+ server = serverMap.get(key);
+ if (server == null) {
+ serverMap.put(key, createServer(url)); //
注意这里,createServer(url) 会创建并返回监听服务器server,server作为serverMap缓存的value
+ return;
+ }
+ }
+ // server supports reset, use together with override
+ server.reset(url); // 注意这里,当address缓存命中后,不会在创建server,而是调用
reset,后面会解释该方法作用
+ }
+ }
+```
+
+`createServer(url)` 其实就创建了一个 `NettyServer`,也即下面代码片段的
`AbstractServer`,`AbstractServer`内部会调用 `createExecutorIfAbsent` ,
+这就是我们找的时机。 `createExecutorIfAbsent`即像前一小节描述的,会创建 url 对应服务的线程池。
+
+同时这里注意 `AbstractServer` 传入的 `url` 为第一个服务的 `url` ,**且`server` 只存储第一个暴露服务对应的
`url` 引用**,这里有一个印象即可,后面在**隔离线程池的获取**小节会分析这一点。
+```java
+public AbstractServer(URL url, ChannelHandler handler) throws
RemotingException {
+ super(url, handler);
+ // ...
+ executors.add(executorRepository.createExecutorIfAbsent(url)); // 注意这里
+ }
+```
+
+再看暴露第二个服务的流程,(对照前面`DubboProtocol#openServer`代码片段)也会去尝试开启监听服务器 `server`,但是因为
`address` 缓存命中,因此不会再次重复创建 `server`,
+但是会调用 `server.reset(url)` 方法,该方法代码片段如下,也会调用 `createExecutorIfAbsent`,以及将当前服务的
url 参数以若存在则覆盖的方式合并到第一个服务 url 的参数列表上。但要注意 **监听服务器 server
存的还是第一个服务的url,三元组信息保留的还是第一个服务的url的**。
+
+```java
+// DubboProtocol#reset
+public void reset(URL url) {
+ ExecutorService executor =
executorRepository.createExecutorIfAbsent(url); // 注意这里,调用实际
+ executors.add(executor);
+ executorRepository.updateThreadpool(url, executor);
+ super.setUrl(getUrl().addParameters(url.getParameters())); //
注意这里,将当前服务的 url 参数以若存在则覆盖的方式合并到第一个服务 url 的参数列表上
+ }
+```
+
+`createExecutorIfAbsent` 方法的两处调用时机前面已经分析完了。我们可以得到的结论是,dubbo 协议暴露 n 个服务,就会调用 n
次 `createExecutorIfAbsent` 方法,也正好会创建各自服务隔离的线程池。
+
+### triple协议
+再看 `triple` 协议,还是以前面暴露两个服务为例,两个服务在暴露的流程中,都会走到 `TripleProtocol#export` ,并调用
`createExecutorIfAbsent` 来创建线程池。
+
+`TripleProtocol#export` 如下
+```java
+public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
+ URL url = invoker.getUrl();
+ String key = serviceKey(url);
+ final AbstractExporter<T> exporter = new AbstractExporter<T>(invoker) {
+ @Override
+ public void afterUnExport() {
+ // ...
+ }
+ };
+
+ exporterMap.put(key, exporter);
+
+
url.getOrDefaultApplicationModel().getExtensionLoader(ExecutorRepository.class)
+ .getDefaultExtension()
+ .createExecutorIfAbsent(url); // 注意这里
+;
+ return exporter;
+ }
+```
+
+### 结论&优化思路
+可以发现不管是 `dubbo` 协议还是 `triple` 协议,**暴露 n 个服务,就会调用 n 次
`createExecutorIfAbsent(url)` 方法**,而该方法每次传入 `url` 参数包含了对应服务的三元组信息,
+因此只需要简单改造下 `createExecutorIfAbsent` 方法
,**从url提取到服务的三元组信息,以及从url提取到用户配置的线程池**,然后存储到 `data` 容器,就做到了服务间的线程池隔离。
+
+## 隔离线程池的获取时机
+
+前面说了服务暴露在 `dubbo` 和 `triple` 协议下线程池的存储时机(即调用`createExecutorIfAbsent`方法的时机)
。这里再来看一下在这两种协议下,
+当服务提供者收到rpc请求的时候是**怎么获取线程池**来处理请求的,也就是在何时调用 `getExecutor` 方法的。
+
+
+### dubbo协议
+
+看如下代码片段,可以看到调用了 `executorRepository.getExecutor(url)` ,内部会使用 `url.serviceKey`
三元组信息获取对应服务的线程池。
+我们需要特别关注一下这个 `url` ,该 `url` 是 `WrappedChannelHandler` 的属性,其实就是前面提到的开启监听服务器
`server` 里面的 `url` 属性,
+但是像前面的提到的,在暴露两个服务的时候,`server`的 `url` 保留的是一个服务的 `url` , 只是参数是融合/覆盖了多个服务的(前面
`server.reset`方法 )。
+所以有一个很严重的问题,**该 url 的三元组信息是第一个暴露服务的三元组信息**,这样在**收到第二个服务请求的时候实际是拿到的第一个服务的线程池**!
+虽然我们前面通过改造 `createExecutorIfAbsent`方法,在 `data` 容器里面的确存了各自服务隔离的线程池。
+
+不过仔细观察下面`getPreferredExecutorService`方法的参数 `Object msg` ,`msg`
其实就是网络数据包信息,里面是含有各自服务的三元组信息的!
+因此我们当我们想要使用服务间线程池隔离模式的时候,不能直接使用如下的 `url`,而是提取网络包数据的三元组信息 构建新的 `url` 然后调用
`executorRepository.getExecutor(url)` 从 data容器获取对应服务的线程池。
+
+特殊说明,`Object msg` 的三元组是实际放在 `Request#mdata`
这部分未解码的消息体里的,因此必须要解码才能拿到三元组信息,而解码工作原先默认是放在 **各自服务隔离的线程池** 去做的,
+但是我们本身的目的又是去获取隔离线程池,这里就发生了矛盾,所以不得不做出一种让步,当使用服务间线程池隔离模式的时候,则需要在 `netty io`
线程进行解码。
+
+```java
+
+// WrappedChannelHandler#getPreferredExecutorService
+public ExecutorService getPreferredExecutorService(Object msg){
+ if(msg instanceof Response){
+ // ...
+ }else{
+ return getSharedExecutorService();
+ }
+}
+
+// WrappedChannelHandler#getSharedExecutorService
+public ExecutorService getSharedExecutorService() {
+ // note: url.getOrDefaultApplicationModel() may create new application
model
+ ApplicationModel applicationModel = url.getOrDefaultApplicationModel();
+
+ ExecutorRepository executorRepository =
+
applicationModel.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
+
+ // 注意这里,各个不同的服务都会使用第一个服务 url.serviceKey 三元组信息获取第一个服务服务的线程池,所以不能使用该url
+ ExecutorService executor = executorRepository.getExecutor(url);
+
+ if (executor == null) {
+ executor = executorRepository.createExecutorIfAbsent(url);
+ }
+
+ return executor;
+}
+```
+
+### triple协议
+
+看下面 `TripleHttp2Protocol#lookupExecutor` 代码片段,`triple` 在服务器开启的时候就**根据第一个服务的
url 构建executor** 并设置到了
+`TripleHttp2FrameServerHandler#executor`属性,后续各个不同的服务都会使用这个 `executor`
,显然这个也不能用于服务间的线程池隔离。
+
+但是注意下面`TripleHttp2FrameServerHandler#onHeadersRead`方法的参数`Http2HeadersFrame
msg` ,这也是网络数据包,里面也维护了服务的三元组信息,
+我们只需要提取网络包的三元组信息构建新的 `url` 然后调用 `executorRepository.getExecutor(url)` ,就可以从
`data` 容器获取对应服务的线程池。
+而`Http2HeadersFrame msg` 是rpc请求的头部信息,里面就含有了三元组信息,直接就可以拿到。不像 `dubbo` 协议那样,还要提前在
`netty io` 线程中对 `Request#mdata` 解码。
+
+```java
+// TripleHttp2Protocol#lookupExecutor
+private Executor lookupExecutor(URL url) {
+ return url.getOrDefaultApplicationModel()
+ .getExtensionLoader(ExecutorRepository.class)
+ .getDefaultExtension().getExecutor(url);
+ }
+
+// TripleHttp2FrameServerHandler#onHeadersRead
+public void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame msg)
throws Exception {
+ TripleServerStream tripleServerStream = new
TripleServerStream(ctx.channel(),
+ frameworkModel, executor, //
注意这里的executor是第一个暴露服务生成的线程池,想要使用隔离线程池模式,就不能用这个。需要从参数 msg 提取三元组来获取
+ pathResolver, acceptEncoding, filters);
+ ctx.channel().attr(SERVER_STREAM_KEY).set(tripleServerStream);
+ tripleServerStream.transportObserver.onHeader(msg.headers(),
msg.isEndStream());
+ }
+```
+
+### 结论&优化思路
+想要获取各自服务隔离的线程池,`dubbo` 和 `triple` 协议都需要提取网络包数据中服务的三元组信息,构建新的 `url` 然后调用
`executorRepository.getExecutor(url)` 从 `data` 容器获取对应的线程池。
diff --git a/proposals/D14-2-service-executor-isolation-config.md
b/proposals/D14-2-service-executor-isolation-config.md
new file mode 100644
index 0000000..b3cd32d
--- /dev/null
+++ b/proposals/D14-2-service-executor-isolation-config.md
@@ -0,0 +1,216 @@
+# 新增配置参数
+- `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、XML、Annotation 的方式进行配置
+## API
+```java
+ public void test() {
+ // provider app
+ DubboBootstrap providerBootstrap = DubboBootstrap.newInstance();
+
+ ServiceConfig serviceConfig1 = new ServiceConfig();
+ serviceConfig1.setInterface(DemoService.class);
+ serviceConfig1.setRef(new DemoServiceImpl());
+ serviceConfig1.setVersion(version1);
+ // set executor1 for serviceConfig1, max threads is 10
+ NamedThreadFactory threadFactory1 = new
NamedThreadFactory("DemoService-executor");
+ ExecutorService executor1 = Executors.newFixedThreadPool(10,
threadFactory1);
+ serviceConfig1.setExecutor(executor1);
+
+ ServiceConfig serviceConfig2 = new ServiceConfig();
+ serviceConfig2.setInterface(HelloService.class);
+ serviceConfig2.setRef(new HelloServiceImpl());
+ serviceConfig2.setVersion(version2);
+ // set executor2 for serviceConfig2, max threads is 100
+ NamedThreadFactory threadFactory2 = new
NamedThreadFactory("HelloService-executor");
+ ExecutorService executor2 = Executors.newFixedThreadPool(100,
threadFactory2);
+ serviceConfig2.setExecutor(executor2);
+
+ ServiceConfig serviceConfig3 = new ServiceConfig();
+ serviceConfig3.setInterface(HelloService.class);
+ serviceConfig3.setRef(new HelloServiceImpl());
+ serviceConfig3.setVersion(version3);
+ // Because executor is not set for serviceConfig3, the default
executor of serviceConfig3 is built using
+ // the threadpool parameter of the protocolConfig ( FixedThreadpool ,
max threads is 200)
+ serviceConfig3.setExecutor(null);
+
+ // It takes effect only if [executor-management-mode=isolation] is
configured
+ ApplicationConfig applicationConfig = new
ApplicationConfig("provider-app");
+ applicationConfig.setExecutorManagementMode("isolation");
+
+ providerBootstrap
+ .application(applicationConfig)
+ .registry(registryConfig)
+ // export with tri and dubbo protocol
+ .protocol(new ProtocolConfig("tri", 20001))
+ .protocol(new ProtocolConfig("dubbo", 20002))
+ .service(serviceConfig1)
+ .service(serviceConfig2)
+ .service(serviceConfig3);
+
+ providerBootstrap.start();
+ }
+```
+
+## XML
+```xml
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
+ xmlns="http://www.springframework.org/schema/beans"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
+ http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
+
+ <!-- NOTE: we need config executor-management-mode="isolation" -->
+ <dubbo:application name="demo-provider" executor-management-mode="isolation">
+ </dubbo:application>
+
+ <dubbo:config-center address="zookeeper://127.0.0.1:2181"/>
+ <dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>
+ <dubbo:registry id="registry1"
address="zookeeper://127.0.0.1:2181?registry-type=service"/>
+
+ <dubbo:protocol name="dubbo" port="-1"/>
+ <dubbo:protocol name="tri" port="-1"/>
+
+ <!-- expose three service with dubbo and tri protocol-->
+ <bean id="demoServiceV1"
class="org.apache.dubbo.config.spring.impl.DemoServiceImpl"/>
+ <bean id="helloServiceV2"
class="org.apache.dubbo.config.spring.impl.HelloServiceImpl"/>
+ <bean id="helloServiceV3"
class="org.apache.dubbo.config.spring.impl.HelloServiceImpl"/>
+
+ <!-- customized thread pool -->
+ <bean id="executor-demo-service"
+
class="org.apache.dubbo.config.spring.isolation.spring.support.DemoServiceExecutor"/>
+ <bean id="executor-hello-service"
+
class="org.apache.dubbo.config.spring.isolation.spring.support.HelloServiceExecutor"/>
+
+ <!-- this service use [executor="executor-demo-service"] as isolated thread
pool-->
+ <dubbo:service executor="executor-demo-service"
+ interface="org.apache.dubbo.config.spring.api.DemoService"
version="1.0.0" group="Group1"
+ timeout="3000" ref="demoServiceV1" registry="registry1"
protocol="dubbo,tri"/>
+
+ <!-- this service use [executor="executor-hello-service"] as isolated thread
pool-->
+ <dubbo:service executor="executor-hello-service"
+ interface="org.apache.dubbo.config.spring.api.HelloService"
version="2.0.0" group="Group2"
+ timeout="5000" ref="helloServiceV2" registry="registry1"
protocol="dubbo,tri"/>
+
+ <!-- not set executor for this service, the default executor built using
threadpool parameter of the protocolConfig -->
+ <dubbo:service interface="org.apache.dubbo.config.spring.api.HelloService"
version="3.0.0" group="Group3"
+ timeout="5000" ref="helloServiceV3" registry="registry1"
protocol="dubbo,tri"/>
+
+</beans>
+```
+## Annotation
+```java
+@Configuration
+@EnableDubbo(scanBasePackages =
"org.apache.dubbo.config.spring.isolation.spring.annotation.provider")
+public class ProviderConfiguration {
+ @Bean
+ public RegistryConfig registryConfig() {
+ RegistryConfig registryConfig = new RegistryConfig();
+ registryConfig.setAddress("zookeeper://127.0.0.1:2181");
+ return registryConfig;
+ }
+
+ // NOTE: we need config executor-management-mode="isolation"
+ @Bean
+ public ApplicationConfig applicationConfig() {
+ ApplicationConfig applicationConfig = new
ApplicationConfig("provider-app");
+
+ applicationConfig.setExecutorManagementMode("isolation");
+ return applicationConfig;
+ }
+
+ // expose services with dubbo protocol
+ @Bean
+ public ProtocolConfig dubbo() {
+ ProtocolConfig protocolConfig = new ProtocolConfig("dubbo");
+ return protocolConfig;
+ }
+
+ // expose services with tri protocol
+ @Bean
+ public ProtocolConfig tri() {
+ ProtocolConfig protocolConfig = new ProtocolConfig("tri");
+ return protocolConfig;
+ }
+
+ // customized thread pool
+ @Bean("executor-demo-service")
+ public Executor demoServiceExecutor() {
+ return new DemoServiceExecutor();
+ }
+
+ // customized thread pool
+ @Bean("executor-hello-service")
+ public Executor helloServiceExecutor() {
+ return new HelloServiceExecutor();
+ }
+}
+```
+```java
+// customized thread pool
+public class DemoServiceExecutor extends ThreadPoolExecutor {
+ public DemoServiceExecutor() {
+ super(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
+ new NamedThreadFactory("DemoServiceExecutor"));
+ }
+}
+```
+
+```java
+// customized thread pool
+public class HelloServiceExecutor extends ThreadPoolExecutor {
+ public HelloServiceExecutor() {
+ super(100, 100, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
+ new NamedThreadFactory("HelloServiceExecutor"));
+ }
+}
+```
+```java
+// "executor-hello-service" is beanName
+@DubboService(executor = "executor-demo-service", version = "1.0.0", group =
"Group1")
+public class DemoServiceImplV1 implements DemoService {
+
+ @Override
+ public String sayName(String name) {
+ return "server name";
+ }
+
+ @Override
+ public Box getBox() {
+ return null;
+ }
+}
+
+```
+
+```java
+// not set executor for this service, the default executor built using
threadpool parameter of the protocolConfig
+@DubboService(version = "3.0.0", group = "Group3")
+public class HelloServiceImplV2 implements HelloService {
+ private static final Logger logger =
LoggerFactory.getLogger(HelloServiceImplV2.class);
+
+ @Override
+ public String sayHello(String name) {
+ return "server hello";
+ }
+}
+
+```
+
+```java
+@DubboService(executor = "executor-hello-service", version = "2.0.0", group =
"Group2")
+public class HelloServiceImplV3 implements HelloService {
+ private static final Logger logger =
LoggerFactory.getLogger(HelloServiceImplV3.class);
+
+ @Override
+ public String sayHello(String name) {
+ return "server hello";
+ }
+}
+```
diff --git a/proposals/D14-3-service-executor-isolation-code-desc.md
b/proposals/D14-3-service-executor-isolation-code-desc.md
new file mode 100644
index 0000000..ae4255a
--- /dev/null
+++ b/proposals/D14-3-service-executor-isolation-code-desc.md
@@ -0,0 +1,30 @@
+
+## DefaultExecutorRepository 调整说明
+
+`ApplicationConfig` 新增 `String executor-management-mode` 参数 为了方便后面描述,记作 `mode`。
+
+1.原先代码都是获取默认的 `SPI` 实例,即 `DefaultExecutorRepository`,现在调整为根据 `mode` 参数值 获取对应
`SPI` 实例。(详见 `ExecutorRepository.getInstance` 方法)
+
+2.新增 `SPI` 类 `IsolationExecutorRepository`,父类为 `DefaultExecutorRepository`,前后
`SPI name` 分别为 `isolation` 和 `default`。
+
+3.`DefaultExecutorRepository#data` 存储结构调整,原有二级key类型从 `Integer` 调整为
`String`。调整原因是 `IsolationExecutorRepository` 需要以 `url.serviceKey`
作为二级key存储,该类型是 `String`,且主要依靠该值做隔离。
+
+4.方法调整说明
+- `getExecutorSecondKey` 根据 `side = provider or consumer` 调用 `getConsumerKey`
or `getProviderKey` 方法。
+- `setThreadNameIfAbsent` 当某个服务暴露多个协议的时候,是不支持 服务+协议 维度线程池隔离的,所以如果 `SPI` 为
`isolation`,则 `threadName` 不会再有协议端口的信息。
+- `createExecutor` `SPI` 为 `default` 则根据 `protocolConfig`
创建线程池,`SPI` 为 `isolation` 则从 `url.attributes` 获取线程池(见下面"注")。
+- `getConsumerKey` 消费端按照原有逻辑,不参与服务端线程池隔离。
+- `getProviderKey` `SPI` 为 `default` 则获取 `url.port` 做隔离
,`SPI` 为 `isolation` 则获取 `url.serviceKey` 做隔离。
+- `getExecutorSupport` 主要是在服务端收到请求的时候协助获取线程池的,`SPI` 为 `default` 则返回
`DefaultExecutorSupport`,`SPI` 为 `isolation` 则根据 `url.protocol` 返回
`DubboIsolationExecutorSupport` 还是 `TriIsolationExecutorSupport`。
+
+注:当 `ServiceConfig#export` 的时候,因为 `executor` 属性类型是 `Executor` 类型,不是
`String`,无法直接以参数的方式附着在 `url.parameters` 上,所以会将 `executor` 添加到 `url.attributes`
中(详见 `ServiceConfig#processServiceExecutor` 方法),并在
`IsolationExecutorRepository#createExecutor` 方法中获取。
+
+# 隔离线程池的存储和获取
+
+## 隔离线程池的存储
+服务暴露的时候,根据各自服务的 `url.serviceKey` 存储在 `IsolationExecutorRepository#data` 中。
+
+## 隔离线程池的获取
+- 当消费者向提供者发起请求,服务端需要根据 **url.serviceKey/三元组** 取出对应的线程池,所以如何获取这份数据是最为关键的。可以发现
三元组 存储在网络数据包里的,如果是 `dubbo` 协议,则存在 `Request#mdata` 属性中,如果是 `triple` 协议,则可以从
`headers` 获取三元组信息。因此添加 `DubboIsolationExecutorSupport` 和
`TriIsolationExecutorSupport` 这两个类来解析并获取网络数据包的三元组信息,从而取出服务各自的线程池。
+- `dubbo` 协议获取服务端线程池的逻辑在 `WrappedChannelHandler#getSharedExecutorService`
方法,会在这里使用 `DubboIsolationExecutorSupport#getExecutor`,triple协议则在
`TripleHttp2FrameServerHandler#onHeadersRead` 方法,会在这里使用
`TriIsolationExecutorSupport#getExecutor`。
+- `dubbo` 协议不同于 `triple`,`triple` 直接能从头部获取三元组,而 `dubbo` 协议的三元组是放在`
Request#mdata` 这部分未解码的消息体里的,而解码工作原先默认是放在 **提供者/服务/隔离线程池**
去做的,但是我们本身的目的又是去获取隔离线程池,这里就发生了矛盾,所以不得不做出一种让步:当识别到 `mode = isolation`,则直接在
`netty io` 线程进行解码。(详见 `DubboCodec#isDecodeDataInIoThread`)
\ No newline at end of file