SOFA 源码分析 — 自定义线程池原理

前言
在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的。多个服务可以共用一个独立的线程池。
API使用方式如下:
UserThreadPool threadPool = new UserThreadPool();
threadPool.setCorePoolSize(10);
threadPool.setMaximumPoolSize(100);
threadPool.setKeepAliveTime(200);
threadPool.setPrestartAllCoreThreads(false);
threadPool.setAllowCoreThreadTimeOut(false);
threadPool.setQueueSize(200);
UserThreadPoolManager.registerUserThread("com.alipay.sofa.rpc.quickstart.HelloService", threadPool);
如上为 HelloService 服务设置了一个自定义线程池。
在 SOFABoot 中如下使用:
<bean id="customExcutor" class="com.alipay.sofa.rpc.server.UserThreadPool" init-method="init">
<property name="corePoolSize" value="10" />
<property name="maximumPoolSize" value="10" />
<property name="queueSize" value="0" />
</bean>
<bean id="helloService" class="com.alipay.sofa.rpc.quickstart.HelloService"/>
<sofa:service ref="helloService" interface="XXXService">
<sofa:binding.bolt>
<sofa:global-attrs thread-pool-ref="customExcutor"/>
</sofa:binding.bolt>
</sofa:service>
那么实现原理是什么呢?
一起来看看。
源码分析
关键代码:
UserThreadPoolManager.
registerUserThread("com.alipay.sofa.rpc.quickstart.HelloService", threadPool);
UserThreadPoolManager 是一个用户自定义线程池管理器。里面包含一个 Map, key 是接口名称,value 是线程池(一个 UserThreadPool对象)。
看看这个 UserThreadPool。
很简单的一个类,封装了 JDK 的线程池。并初始化了一些线程池参数,比如:
- corePoolSize = 10
- maximumPoolSize = 100
- keepAliveTime = 300000(线程回收时间(毫秒))
- queueSize = 0
- threadPoolName = "SofaUserProcessor" 线程名字
- boolean allowCoreThreadTimeOut 是否关闭核心线程池
- boolean prestartAllCoreThreads 是否初始化核心线程池
- volatile ThreadPoolExecutor executor
初始化的时候,默认参数不变,核心线程数 10,最大 100,默认不关闭核心线程池,默认不初始化线程池。默认是 SynchronousQueue 队列,此队列性能最高,也可以设置成阻塞队列,或者优先级队列。当然,这些都是可以改的。
这个线程池什么时候回起作用呢?
先说结论:当 Netty 读取数据(channelRead 方法)后,通过层层调用,会调用 RpcRequestProcessor 类的 process 方法。该方法会拿到上下文的 UserProcessor 对象(bolt 的话,实现类是 BoltServerProcessor),UserProcessor 有一个内部接口 ExecutorSelector,线程池选择器,该选择器定义了一个 select 方法,返回的是线程池,如果用户自定义了线程池,就会返回自定义线程池(方式:UserThreadPoolManager.getUserThread(service)),如果没有,返回系统线程池。
来看看具体代码。
RpcHandler 我们很熟悉了,就是 Netty 的 handler ,ChannelRead 方法中,会调用 RpcCommandHandler 的 handleCommand 方法,该方法会提交到线程池执行。任务内容是执行 process 方法。
通过调用,最后会执行 RpcRequestProcessor 的 process 方法。调用栈如下:

105 行会有如下判断:
// to check whether get executor using executor selector
if (null == userProcessor.getExecutorSelector()) {
executor = userProcessor.getExecutor();
} else {
// in case haven't deserialized in io thread
// it need to deserialize clazz and header before using executor dispath strategy
if (!deserializeRequestCommand(ctx, cmd, RpcDeserializeLevel.DESERIALIZE_HEADER)) {
return;
}
//try get executor with strategy
executor = userProcessor.getExecutorSelector().select(cmd.getRequestClass(),
cmd.getRequestHeader());
}
尝试获取线线程池选择器,如果是 null, 使用系统线程池,如果不是 null,调用选择器的 select 方法得到线程池,随后,使用这个线程执行任务。
// Till now, if executor still null, then try default
if (executor == null) {
executor = (this.getExecutor() == null ? defaultExecutor : this.getExecutor());
}
// use the final executor dispatch process task
executor.execute(new ProcessTask(ctx, cmd));
那么这个 select 方法是如何实现的呢?目前仅有一个实现,BoltServerProcessor 的内部类 UserThreadPoolSelector。该方法逻辑如下:
从 Header 中获取服务名称,根据服务名称调用 UserThreadPoolManager.getUserThread(service) ,如果返回值不是 null ,说明用户设置自定义线程池了,就返回该线程池。如果是 null,返回系统线程池。
而 BoltServerProcessor 的 getExecutorSelector 判断规则如下:
@Override
public ExecutorSelector getExecutorSelector() {
return UserThreadPoolManager.hasUserThread() ? executorSelector : null;
}
public static boolean hasUserThread() {
return userThreadMap != null && userThreadMap.size() > 0;
}
public BoltServerProcessor(BoltServer boltServer) {
this.boltServer = boltServer;
this.executorSelector = new UserThreadPoolSelector(); // 支持自定义业务线程池
}
可以看到,BoltServerProcessor 默认就会创建一个内部类对象,只要 UserThreadPoolManager 里面的 Map 不是空,就会尝试调用 select 方法,如果通过服务名称找到缓存中的自定义线程池,就直接返回了。非常完美。
需要注意一点,系统线程池只有一个,默认核心线程池大小 20 ,最大 200。貌似这也是 tomcat 的默认配置,因此,并发很高的时候,可能就需要用户使用自定义线程池了,能够显著提高并发量。
总结
好了,关于自定线程池的原理探究的差不多了,这个功能挺有用的,当系统并发很高的时候,或者某个服务很慢,不能让该服务影响其他服务,就可以使用自定线程池,将这些慢服务和其他服务隔离开。
原理则是通过 UserThreadPoolManager 与 Server 进行交互,当 Server 执行任务的时候,会从当前的上下文中,找到与调用服务对应的线程池,如果有的话,就返回 UserThreadPoolManager 管理的线程池,如果没有,返回框架线程池。
具体判断的代码在 Bolt 模块 com.alipay.remoting.rpc.protocol.RpcRequestProcessor 的 process 方法中。
bye!!!
SOFA 源码分析 — 自定义线程池原理的更多相关文章
- 深入源码分析Java线程池的实现原理
程序的运行,其本质上,是对系统资源(CPU.内存.磁盘.网络等等)的使用.如何高效的使用这些资源是我们编程优化演进的一个方向.今天说的线程池就是一种对CPU利用的优化手段. 通过学习线程池原理,明白所 ...
- 源码分析—ThreadPoolExecutor线程池三大问题及改进方案
前言 在一次聚会中,我和一个腾讯大佬聊起了池化技术,提及到java的线程池实现问题,我说这个我懂啊,然后巴拉巴拉说了一大堆,然后腾讯大佬问我说,那你知道线程池有什么缺陷吗?我顿时哑口无言,甘拜下风,所 ...
- 从JDK源码角度看线程池原理
"池"技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实 ...
- SOFA 源码分析— 自定义路由寻址
前言 SOFA-RPC 中对服务地址的选择也抽象为了一条处理链,由每一个 Router 进行处理.同 Filter 一样, SOFA-RPC 对 Router 提供了同样的扩展能力. 那么就看看 SO ...
- SOFA 源码分析 —— 服务引用过程
前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引 ...
- netty源码分析 - Recycler 对象池的设计
目录 一.为什么需要对象池 二.使用姿势 2.1 同线程创建回收对象 2.2 异线程创建回收对象 三.数据结构 3.1 物理数据结构图 3.2 逻辑数据结构图(重要) 四.源码分析 4.2.同线程获取 ...
- jQuery 2.0.3 源码分析Sizzle引擎解析原理
jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理 声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + ...
- Memcached源码分析之线程模型
作者:Calix 一)模型分析 memcached到底是如何处理我们的网络连接的? memcached通过epoll(使用libevent,下面具体再讲)实现异步的服务器,但仍然使用多线程,主要有两种 ...
- SOFA 源码分析 — 调用方式
前言 SOFARPC 提供了多种调用方式满足不同的场景. 例如,同步阻塞调用:异步 future 调用,Callback 回调调用,Oneway 调用. 每种调用模式都有对应的场景.类似于单进程中的调 ...
随机推荐
- Android用AlarmManager实现后台任务-android学习之旅(63)
因为Timer不能唤醒cpu,所以会在省电的原因下失效,所以需要唤醒cpu在后台稳定化的执行任务,AlarmManager能够唤醒cpu 这个例子讲解了如何通过Service来在后他每一个小时执行.特 ...
- django练习——博客系统优化
一直准备使用Django搭建一个个人网站,最近终于开始动手,上周已经完成了基本博客功能的搭建(http://blog.csdn.net/hcx25909/article/details/2460133 ...
- Eclipse快捷键指南
Eclipse快捷键指南 Eclipse快捷键,熟悉快捷键可以帮助开发事半功倍,节省更多的时间来用于做有意义的事情.Ctrl+1 快速修复(最经典的快捷键,就不用多说了)Ctrl+D: 删除当前行Ct ...
- protobuf代码生成
windows : 1,两个文件:proto.exe, protobuf-java-2.4.1.jar 2,建立一个工程TestPb,在下面建立一个proto文件件,用来存放[.proto]文件 3, ...
- 认证模式之Spnego模式
Spnego模式是一种由微软提出的使用GSS-API接口的认证模式,它扩展了Kerberos协议,在了解Spnego协议之前必须先了解Kerberos协议,Kerberos协议主要解决身份认证及通信密 ...
- matlab学习日志之并行运算
原文地址:matlab并行计算,大家共同学习吧,涉及到大规模数据量处理的时候还是效果很好的 今天搞了一下matlab的并行计算,效果好的出乎我的意料. 本来CPU就是双核,不过以前一直注重算法,没注意 ...
- Android重命名包名
工程写的差不多了才发现原来用的包名还是自己尝试性的进行写代码的时候用到的.但apk的发布,google map api的申请等等方面都需要用到一个比较规范的包名.这就涉及到修改包名的问题. 包名一开始 ...
- UVa - 116 - Unidirectional TSP
Background Problems that require minimum paths through some domain appear in many different areas of ...
- AngularJS进阶(一)深入理解ANGULARUI路由_UI-ROUTER
深入理解ANGULARUI路由_UI-ROUTER 最近在用 ionic写个webapp 看到几个demo中路由有好几种,搞的有点晕,查下资料研究下,做个笔记,其中大部分为摘抄别人的,做个说明免得被人 ...
- "《算法导论》之‘图’":最小生成树(无向图)
本文主要参考自<算法>. 加权图是一种为每条边关联一个权值或是成本的图模型.这种图能够自然地表示许多应用.在一幅航空图中,边表示航线,权值则可以表示距离或是费用.在一幅电路图中,边表示导线 ...