RPC client OOM - RPC client 内存泄露

原因简述

  • 公司的rpc框架是内部开发并维护的
  • RPC server timeout时不回复机制使得client端大量请求对象一直存活而不被销毁(内存泄露),造成client端内存耗尽

现象及分析过程

  • 7.25(周四)晚某web卡死,发现是gc频繁所致,运维重启后服务正常

  • 7.26(周五)对该web增加gc监控,对gc.time.max,gc.tenuredUsed.percent, gc.throughput做监控

  • 7.29(周一)发现tenuredUsed.percent从上周五的40%升至50%. 找运维取jmap并用mat进行分析, 发现一个可疑的约900m的大对象odis.rpc2.RpcClient$ConnectionImpl(old区约4.5g),其中积存了大量的Call对象(运行三天多后约16*1000=1.6万个). 该RpcClient为向rpc server请求的RpcClient

  • 查看odis.rpc2.RpcClient$ConnectionImpl相关code,分析pendingCalls进行put和remove的相关逻辑, 可知:client端的call对象们在完成send后会被put进pendingCalls里wait. 再通过一个后台线程向server读取各个call的结果, 该线程读到某个call的返回时就将该call从pendingCalls里remove

      odis.rpc2.RpcClient$ConnectionImpl部分代码: 
    
      @Override
      public void sendParam(Call call) throws RpcException {
          boolean error = true;
          try {
              pendingCalls.put(call.id, call);                    // yinxj: 将call请求放入map中
              // check closed after put to pendingCalls. if we check closed first, 
              // we may: 
              // 1. we check closed, it is false.
              // 2. connection is closed, all pendingCalls are discard.
              // 3. we put our call in pendingCalls and wait.
              // 4. we wait forever if we do not have a timeout.
              if (closed) {
                  throw new RpcException("connection already closed");
              }
      ...
    
      @Override
      public void run() {
          try {
              while (running) {
                  lastActiveTime = System.currentTimeMillis();
                  int id;
                  try {
                      id = in.readInt();
                  } catch (SocketTimeoutException e) {
                      continue;
                  }
                  Call call = pendingCalls.remove(id);                    // yinxj: 将对应的call从map中remove. 注意:此为唯一进行remove的逻辑, 若读不到call.id则该call将不会被remove
                  if (call == null) {
                      LOG.severe("Unknown call ID received: " + id
                              + ". This is usually because readFields and "
                              + "writeFields is not match, or RpcServer and "
                              + "RpcClient has different writable implements");
                      continue;
                  }
      ...
    
  • jmap中odis.rpc2.RpcClient$ConnectionImpl中有众多的Call对象是因为其未被remove所致, 进一步可推断出是server端没有将call的请求回写导致call对象未从pendingCalls里remove. 与rpc相应开发人员确认: rpc server在处理某个请求时如果发现超时,就会直接结束该请求而不回复, 因此导致了pendingCalls的内存泄漏

解决办法

  • 建议增加一后台线程去遍历pendingCalls,移除已timeout的无用calls -- 与rpc开发人员确认,会提一个issue进行fix
  • 提升的rpc client的性能(超时情况较严重)

QA

    Q: 日志里向rpc server请求的timeout数量每天约有4-5万,服务运行近4天,约为10-20w,但对应pendingCalls里的数量只有1.6w,二者的不一致性如何解释?
    A: pendingCalls中未remove的call仅为rpc server发现超时后未回复的请求, server在回写过程中才超时的call是会被remove掉的, 所以pendingCalls中的数量要小于总timeout的数量

    Q: 为何这个问题之前一直没有被发现?
    A: 原因大概有三个方面. 一是其他rpc server的timeout比例应该没有这么高, 因此pendingCalls里未被remove的对象不会太多; 二是我们的call对象较大(其中一个参数为500个url的数组), 参数占用空间较小的call对象造成的内存开销较小不易被发现; 三是我们的服务运行了一个多月后才内存耗尽的, 而之前上线较为频繁, 很少有运行这么长的时间

    Q: 测试过程中为什么也没发现这个问题?
    A: 我们曾模拟一些异常环境(如rpc server卡死不响应请求)做测试,但此bug复现的环境则需要rpc server所在的机器有较大负载(忙不过来而超时较多),但亦能有一定的工作能力(能接收请求)而不是直接卡死(无法接收请求),这样的环境不是很好模拟。我们之后会多考虑这样的风险

经验教训

  • 运维:服务卡死时 如果知道是内存耗尽所致 最好取一下jmap再重启服务:jmap -dump:file=mem.dump <pid>, 以便更快的定位问题
  • 测试:压力测试时需要深入了解各服务的实现原理,考虑并设计不同的异常环境进行测试,这样才能更好的规避风险
  • 测试:在进行压力测试一段时间后,可以用jmap来取得内存dump并用mat来分析其中大小 top N 的对象, 排查异常的情况
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • 转自:http://blog.csdn.net/kesonyk/article/details/50924489 ...
    晴天哥_王志阅读 24,750评论 2 38
  • 亲爱的你:好久不见了,在没有你的这些时候里,我没有很难过,也没有想象中的寝食难安,整天以泪洗面。我还是要在阳光灿烂...
    Destiny文翊阅读 203评论 3 3
  • 1.Xcode证书路径: ~/Library/MobileDevice/Provisioning Profiles...
    frola_阅读 1,554评论 0 0