前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Netty17# 实战|Young GC时间过长导致RPC超时

Netty17# 实战|Young GC时间过长导致RPC超时

作者头像
瓜农老梁
发布2021-08-06 10:41:18
9821
发布2021-08-06 10:41:18
举报
文章被收录于专栏:瓜农老梁瓜农老梁

引言

前几天一个业务负责的同事找老梁,说每次发布SOA拉入时就有少量报错。

报错的集中在RPC设置超时时间比较短的上游服务,比如设置300ms,发布完就好了。

我说最近没有发布新版本,应该不是中间件变更引起的。

同事说这问题存在好几个月了,他们一直想抓原因,一直没找到。

你咋早点不反馈到我这呢?就自己这么琢磨了几个月,够执着的。

老梁让他组织个会议,拉了小组两个同事一起参与下,聚焦复盘下问题。

忘了说了,我们SOA框架使用gRPC通信,gRPC底层使用Netty。

一、问题复盘

GC 日志

从GC日志现象来看,在第4次和第5次Young GC的时间过长,线上达到了900ms。

在测试环境复现,第4次Young GC的时间也超过500ms。

dump文件情况

dump一份看看当时内存情况,可以看到主要是MpscArrayQueue这个队列给占了。

小结: 通过日志和dump文件看出,由于MpscArrayQueue对象占用过多,导致Young GC时间过长。

二、根因分析

解决方式

这个问题到时网上也有人遇到,下面帖子指出通过以下设置解决。

代码语言:javascript
复制
-Dio.netty.allocator.useCacheForAllThreads=false 
-Dio.grpc.netty.shaded.io.netty.allocator.useCacheForAllThreads=false

设置GC参数后,再看下GC日志发现,Young GC耗时100ms左右,通过线上灰度QPS和资源均可满足需求。

源码分析

先看下useCacheForAllThreads参数,用于设置DEFAULT_USE_CACHE_FOR_ALL_THREADS的值,默认为true,指会对所有线程对象进行缓存。

代码语言:javascript
复制
private static final boolean DEFAULT_USE_CACHE_FOR_ALL_THREADS;

DEFAULT_USE_CACHE_FOR_ALL_THREADS = SystemPropertyUtil.getBoolean(
                "io.netty.allocator.useCacheForAllThreads", true);

如下代码,开启线程缓存会构造PoolThreadCache时会有一系列的值传入。

代码语言:javascript
复制
@Override
protected synchronized PoolThreadCache initialValue() {
  final PoolArena<byte[]> heapArena = leastUsedArena(heapArenas);
  final PoolArena<ByteBuffer> directArena = leastUsedArena(directArenas);

  final Thread current = Thread.currentThread();
  // 线程缓存开关
  if (useCacheForAllThreads || current instanceof FastThreadLocalThread) {
    final PoolThreadCache cache = new PoolThreadCache(
      heapArena, directArena, tinyCacheSize, smallCacheSize, normalCacheSize,
      DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL);

    if (DEFAULT_CACHE_TRIM_INTERVAL_MILLIS > 0) {
      final EventExecutor executor = ThreadExecutorMap.currentExecutor();
      if (executor != null) {
        executor.scheduleAtFixedRate(trimTask, DEFAULT_CACHE_TRIM_INTERVAL_MILLIS,
                                     DEFAULT_CACHE_TRIM_INTERVAL_MILLIS, TimeUnit.MILLISECONDS);
      }
    }
    return cache;
  }
  // No caching so just use 0 as sizes.
  return new PoolThreadCache(heapArena, directArena, 0, 0, 0, 0, 0);
}

跟到下面可以看到通过构造MpscArrayQueue实现的,下面为对象数组。与dump文件显示的图示一致。

小结: 其实这部分在《Netty14# 池化内存之线程缓存》 有详细的分析,会形成如下图结构,顺带着把小结也摘录下。

  • Netty以chunk为单位(16M)向系统申请物理内存,Netty池化内存分成了4种内存类型。Tiny(0~512Byte),Small(512Byte~8KB),Normal(8KB~16MB),Huge(>16M)
  • Netty对Tiny、Small、Normal做了缓存,针对不同的类型通过”数组+队列“继续切成不同的尺寸,每个尺寸内的缓存ByteBuffer大小相同,不同尺寸之间缓存的Buffer大小以2的N次增长。
  • Tiny类型从0到496被划分为32个尺寸(数组)
  • Small类型从512到4096(4K)被划分4个尺寸
  • Normal类型从8192(8K)到32768(32K)被划分为3个尺寸
  • 在内存分配时,先根据需要分配的内存大小判断属于那种内存类每个尺寸都维护有队列Queue,定位到尺寸规格也就拿到Queue中的实际缓存(PoolChunk)和指针(handle)并完成所需分配内存buffer的初始化。于该内存类型的哪个尺寸。
  • 每个尺寸都维护有队列Queue,定位到尺寸规格也就拿到Queue中的实际缓存(PoolChunk)和指针(handle)并完成所需分配内存buffer的初始化。

当把缓存关闭-Dio.netty.allocator.useCacheForAllThreads=false 时,上面这个结构也就不存在,构建的对象少了自然Young GC时间就短了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 瓜农老梁 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档