【第四弹】详解分布式中间件Dubbo
8. Dubbo的线程池
8.1 Dubbo线程池的使用
DUBBO提供多种线程池策略,选择线程池策略并在配置文件指定threadpool属性,如下:
<dubbo:protocol name="dubbo" threadpool="fixed" threads="100" />
<dubbo:protocol name="dubbo" threadpool="cached" threads="100" />
<dubbo:protocol name="dubbo" threadpool="limited" threads="100" />
<dubbo:protocol name="dubbo" threadpool="eager" threads="100" />
8.2 Dubbo提供的4种线程池策略
这里的线程池ThreadPool也是一个扩展接口SPI,Dubbo提供了该扩展接口的一些实现,具体实现如下:
CachedThreadPool:创建一个自适应线程池,当线程处于空闲1分钟时候,线程会被回收,当有新请求到来时候会创建新线程
public class CachedThreadPool implements ThreadPool {
public Executor getExecutor(URL url) {
//线程名,默认为Dubbo
String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
//核心线程数,默认为0
int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
//可使用的最大线程数,默认为Integer.MAX_VALUE
int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE);
//队列数,默认为0
int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
//线程存活时间,默认60s
int alive = url.getParameter(ALIVE_KEY, DEFAULT_ALIVE);
//根据queue决定是SynchronousQueue还是LinkedBlockingQueue,默认queue=0,所以是SynchronousQueue
return new ThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue<Runnable>() :
(queues < 0 ? new LinkedBlockingQueue<Runnable>()
: new LinkedBlockingQueue<Runnable>(queues)),
//NamedInternalThreadFactory主要用于修改线程名,方便我们排查问题。
// AbortPolicyWithReport对拒绝的任务打印日志,也是方便排查问题。
new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
LimitedThreadPool:创建一个线程池,这个线程池中线程个数随着需要量动态增加,但是数量不超过配置的阈值的个数,另外空闲线程不会被回收,会一直存在
public class LimitedThreadPool implements ThreadPool {
public Executor getExecutor(URL url) {
//线程名,默认为Dubbo
String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
//核心线程数,默认为0
int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
//可使用的最大线程数,默认为200
int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
//队列数,默认为0
int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
//线程存活时间,默认Long.MAX_VALUE,说明线程数只会增加不会减少
return new ThreadPoolExecutor(cores, threads, Long.MAX_VALUE, TimeUnit.MILLISECONDS,
//根据queue决定是SynchronousQueue还是LinkedBlockingQueue,默认queue=0,所以是SynchronousQueue
queues == 0 ? new SynchronousQueue<Runnable>() :
(queues < 0 ? new LinkedBlockingQueue<Runnable>()
: new LinkedBlockingQueue<Runnable>(queues)),
//NamedInternalThreadFactory主要用于修改线程名,方便我们排查问题。
// AbortPolicyWithReport对拒绝的任务打印日志,也是方便排查问题。
new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
FixedThreadPool:创建一个复用固定个数线程的线程池
public class FixedThreadPool implements ThreadPool {
public Executor getExecutor(URL url) {
//线程名,默认为Dubbo
String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
//可使用的最大线程数,默认为200
int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
//队列数,默认为0
int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
//线程存活时间,默认0s
return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
//根据queue决定是SynchronousQueue还是LinkedBlockingQueue,默认queue=0,所以是SynchronousQueue
queues == 0 ? new SynchronousQueue<Runnable>() :
(queues < 0 ? new LinkedBlockingQueue<Runnable>()
: new LinkedBlockingQueue<Runnable>(queues)),
//NamedInternalThreadFactory主要用于修改线程名,方便我们排查问题。
// AbortPolicyWithReport对拒绝的任务打印日志,也是方便排查问题。
new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
EagerThreadPool:创建一个线程池,这个线程池当所有核心线程都处于忙碌状态时候,创建新的线程来执行新任务,而不是把任务放入线程池阻塞队列
public class EagerThreadPool implements ThreadPool {
public Executor getExecutor(URL url) {
//线程名,默认为Dubbo
String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
//核心线程数,默认为0
int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
//可使用的最大线程数,默认为Integer.MAX_VALUE
int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE);
//队列数,默认为0
int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
//线程存活时间,默认60s
int alive = url.getParameter(ALIVE_KEY, DEFAULT_ALIVE);
// init queue and executor
//自定义实现TaskQueue,默认长度为1,使用时要自己配置下;它会在提交任务时判断是否
// currentPoolSize<submittedtaskcount<maxpoolsize,
// 然后通过它的offer方法返回false导致增加工作线程
TaskQueue<Runnable> taskQueue = new TaskQueue<Runnable>(queues <= 0 ? 1 : queues);
//EagerThreadPoolExecutor继承ThreadPoolExecutor,对当前线程池提交的任务数submittedTaskCount进行记录
EagerThreadPoolExecutor executor = new EagerThreadPoolExecutor(cores,
threads,
alive,
TimeUnit.MILLISECONDS,
taskQueue,
new NamedInternalThreadFactory(name, true),
new AbortPolicyWithReport(name, url));
taskQueue.setExecutor(executor);
return executor;
}
}
8.3 Dubbo 线程池打满异常分析及解决
问题介绍:
Dubbo默认的线程模型:
Dubbo服务端每次接收到一个Dubbo请求,便交给一个线程池处理,该线程池默认有200个线程,如果200个线程都不处于空闲状态,则客户端会报出如下异常:
Caused by<span class="token operator" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">: java<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.util<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.concurrent<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.ExecutionException<span class="token operator" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">: org<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.apache<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.dubbo<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.remoting<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.RemotingException<span class="token operator" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">: Server <span class="token function" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">side<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">(<span class="token number" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">192.168<span class="token number" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.1<span class="token number" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.101<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">,<span class="token number" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">20880<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">) threadpool is exhausted <span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.<span class="token punctuation" style="font-size: inherit;color: inherit;line-height: inherit;overflow-wrap: inherit !important;word-break: inherit !important;">.
</span class="token punctuation"></span class="token punctuation"></span class="token punctuation"></span class="token punctuation"></span class="token number"></span class="token punctuation"></span class="token number"></span class="token number"></span class="token number"></span class="token punctuation"></span class="token function"></span class="token operator"></span class="token punctuation"></span class="token punctuation"></span class="token punctuation"></span class="token punctuation"></span class="token operator"></span class="token punctuation"></span class="token punctuation"></span class="token punctuation"></span class="token operator">
服务端会打印 WARN 级别的日志:
[DUBBO] Thread pool is EXHAUSTED!
问题原因分析:
(1)客户端/服务端超时时间设置不合理,导致请求无限等待,耗尽了线程数
(2)客户端请求量过大,服务端无法及时处理,耗尽了线程数
(3)服务端由于fullgc等原因导致处理请求较慢,耗尽了线程数
(4)服务端由于数据库、Redis、网络IO阻塞问题,耗尽了线程数
原因可能很多,但究其根本,都是因为业务上出了问题,导致Dubbo线程池资源耗尽了。
问题定位分析:
可以利用阿里巴巴开源的Java 诊断工具Arthas(阿尔萨斯)中的dashboard命令和thread命令进行分析。
(1)使用dashboard命令查看线程全局信息
$ dashboard
【$ dashboard】命令可以查看到线程 ID、线程名、线程组名、线程优先级、线程的状态、线程消耗的 CPU 占比、线程运行总时间、线程当前的中断位状态、是否是 daemon 线程
利用下面命令可以根据线程池名筛选出 Dubbo 服务端线程:
dashboard | grep "DubboServerHandler"
(2)使用thread 命令查看线程具体情况
查看当前最忙的前 n 个线程:
$ thread -n 3
显示所有线程信息:
$ thread
显示当前阻塞其他线程的线程:
$ thread -b
显示指定状态的线程:
$ thread --state TIMED_WAITING
线程状态一共有RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, NEW, TERMINATED 6种。
查看指定线程的运行堆栈:
$ thread 46
分析线程池打满异常的常见问题:
(1)阻塞类问题。例如数据库连接不上导致卡死,运行中的线程基本都应该处于BLOCKED或者 TIMED_WAITING状态,我们可以借助thread --state
定位到;
(2)繁忙类问题。例如CPU密集型运算,运行中的线程基本都处于RUNNABLE状态,可以借助于thread -n
来定位出最繁忙的线程;
(3)GC类问题。很多外部因素会导致该异常,例如GC就是其中一个因素,这里就不能仅仅借助于thread
命令来排查了;
(4)定点爆破。还记得在前面我们通过grep筛选出了一批Dubbo线程,可以通过thread ${thread_id}
定向的查看堆栈,如果统计到大量的堆栈都是一个服务时,基本可以断定是该服务出了问题,至于说是该服务请求量突然激增,还是该服务依赖的某个下游服务突然出了问题,还是该服务访问的数据库断了,那就得根据堆栈去判断了。
Dubbo 线程池打满异常问题排查及Dubbo优化:
(1)排查业务异常
(2)合理设置客户端/服务端超时时间
(3)服务端扩容,调整Provider端的 dubbo.provider.threads参数大小,默认200,可以适当提高
(4)客户端限流,调整Consumer端的 dubbo.consumer.actives参数,控制消费者调用的速率
9. Dubbo的路由规则
路由是决定一次请求中需要发往目标机器的重要判断,通过对其控制可以决定请求的目标机器。我们可以通过创建这样的规则来决定一个请求会交给哪些服务器去处理。
9.1 路由规则快速入门
9.2 路由规则详解
10. Dubbo的管理控制台 dubbo-admin
10.1 dubbo-admin的作用
主要包含:服务管理 、 路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能
如我们在开发时,需要知道Zookeeper注册中心都注册了哪些服务,有哪些消费者来消费这些服务。我们可以通过部署一个管理中心来实现。其实管理中心就是一个web应用,原来是war(2.6版本以前)包需要部署到tomcat即可。现在是jar包可以直接通过java命令运行。
10.2 控制台安装步骤
dubbo.registry.address=zookeeper://zk所在机器ip:zk端口
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
(3)切换到项目所在的路径,使用mvn打包
mvn clean package -Dmaven.test.skip=true
(4)java命令运行
java -jar 对应的jar包
10.3 使用控制台
(1)访问http://IP:端口;
(2)输入用户名root,密码root;
(3)点击菜单查看服务提供者和服务消费者信息。
入骨相思知不知
玲珑骰子安红豆
入我相思门,知我相思苦,长相思兮长相忆,短相思兮无穷极。