<返回更多

Dubbo线程池调优

2021-05-18  知否网  
加入收藏

1. Dubbo简介及线程池策略

Apache Dubbo 是一款高性能、轻量级的开源 JAVA 服务框架。提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。Dubbo之前是阿里开发,并得到广泛的应用,后来贡献给了Apache开源组织。

​ Dubbo默认的底层网络通讯使用的是Netty,服务提供方NettyServer使用两级线程池,其中 EventLoopGroup(boss) 主要用来接受客户端的链接请求,并把接受的请求分发给 EventLoopGroup(worker) 来处理,boss和worker线程组我们称之为IO线程。

​ 如果服务提供方的逻辑能迅速完成,并且不会发起新的IO请求,那么直接在IO线程上处理会更快,因为这减少了线程池调度。但如果处理逻辑很慢,或者需要发起新的IO请求,比如需要查询数据库,则IO线程必须派发请求到新的线程池进行处理,否则IO线程会阻塞,将导致不能接收其它请求。

2. 线程池报警

​ 生产环境,该服务大约QPS在1万左右,总共10个节点。最近该服务在高峰期,频繁触发流控和降级。查看dubbo日志,大量线程池耗尽的警告日志:

WARN  2021-05-11 **:**:** WARN AbortPolicyWithReport:65 -  [DUBBO] Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-**.**.**.**:**, Pool Size: 500 (active: 500, core: 500, max: 500, largest: 500), Task: 1285578 (completed: 1285135), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false)

​ 该服务线程池最大500,通过日志可以看到active线程已经达到500了,线程池耗尽了,这样势必造成请求的积压,触发流控和降级。

3. 问题排查

​ Dubbo可以通过配置,当线程池满时,会dump出JStack日志出来,便于分析排查问题。一般配置如下:

<dubbo:Application name="${server.name}" >
  <dubbo:parameter key="dump.directory"  value="${account.dubbo.dump.directory:/home/dubbo_dump/}${server.name}${server.id}" />
    </dubbo:application>  

​ 默认会输出到/home/java这个目录下。

​ 通过排查日志发现,大量线程是BLOCKED状态的,日志如下:

"DubboServerHandler-ip:port-thread-449" Id=633 BLOCKED on java.util.Collections$SynchronizedMap@2d796a15 owned by "DubboServerHandler-ip:port-thread-203" Id=325
    at java.util.Collections$SynchronizedMap.get(Collections.java:2584)
    -  blocked on java.util.Collections$SynchronizedMap@2d796a15
    at com.google.gson.Gson.getAdapter(Gson.java:332)
    at com.google.gson.Gson.fromJson(Gson.java:802)
    at com.google.gson.Gson.fromJson(Gson.java:768)
    at com.google.gson.Gson.fromJson(Gson.java:717)
    at com.google.gson.Gson.fromJson(Gson.java:689)
    ...

​ 通过查看日志发现,最后问题出现在Gson做json反序列化时造成的。再来查看下Gson的源码发现:

public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
    TypeAdapter<?> cached = typeTokenCache.get(type);
    if (cached != null) {
      return (TypeAdapter<T>) cached;
    }
    ...
  }

​ Gson这里是获取适配器,Gson是通过适配器设计模式,问题就出现在获取适配器这里。再来看下typeTokenCache的定义:

  private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache
      = Collections.synchronizedMap(new HashMap<TypeToken<?>, TypeAdapter<?>>());

​ 在早期的JDK版本中,使用线程安全的Map一般都是通过synchronizedMap这种方式,其实底层就是通过synchronized锁实现的。synchronized是互斥锁,也是重量级锁,虽然目前得到很多优化,但是当高并发下,线程获取不到锁,会立马进入BLOCKED状态,这就是Dubbo线程池满的原因。

​ 解决方式如下:

​ 在早期由于没有提供JUI包,也就是ConcurrentHashMap,所以使用synchronizedMap这种方式实现高并发。从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。相信新的Gson版本肯定会做相应的升级,于是查看Gson的2.8.5版本的源码,果然升级了,源码如下:

private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<TypeToken<?>, TypeAdapter<?>>();

​ 升级Gson到2.8.5版本后,问题解决。

​ 总结,线程池调优,主要关注线程的如下几种状态:

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>