使用 NameNode RPC 退避机制改善服务质量

问题描述

HDFS 集群中,有时会因为 NameNode 的超负荷运行(一般是有异常任务大量访问 NN),导致其它客户端无法和 NameNode 创建 TCP 连接,最终导致业务响应非常缓慢,具体表现如下:

  1. NN 9000 端口 TCP 连接队列打满,新的 TCP 连接无法建立:


  1. NN 9000 端口的 RPC callQueue 打满,一般表现为,高优先级队列还有空间,但最低优先级队列被打满(默认情况下是第4级,队列长度默认为6400),这说明有一个访问量特别大的 ugi,但需要注意的是,还有很多其他情况也会导致 rpc callQueue 打满,因此需要进一步判断:


  1. 进一步,NN 9000 端口第4级 RPC 队列所处理的 RPC 总量,明显远远多于其它队列,证明有一个访问量超大的 ugi:


这个问题的链条比较长,但根本原因很清楚,就是有一个访问量巨大的异常 ugi 存在,完整的推导链条如下:

  1. 某一个异常 ugi 的 rpc 请求量太大,导致 NN 256 个 handler (NN handler 个数配置为 256)处理不过来
  2. 这个异常 ugi 的所有 rpc 请求,都会直奔 NN 的第四级 RPC 队列而去,导致 NN 的该 RPC callQueue 积压 6000多个 call
  3. NN 的4个 reader 无法把该 ugi 的新 rpc call 请求加入到第四级 callQueue,陷入阻塞
  4. NN 的所有4个 reader 的连接队列都无法得到处理,全部打满(默认每个 reader 的连接队列 size 都是 100)
  5. NN 的 listener 无法把新的连接加到4个 reader 的连接队列,陷入阻塞
  6. NN 的 listener 无法 accept 新的 TCP 连接
  7. 客户端无法和 NameNode 无建立新的 TCP 连接.

此时集群对外的表现是:客户端访问 NN 时,会间歇性的抽疯,具体表现为:

  1. 只要它能连上 NN, 上了这趟车,那么它就能较快处理完毕并得到响应
  2. 但如果它甚至都连不上 NN(即无法建立 TCP 连接),那么它将等到天荒地老。

原因分析

原因很明确,某一个异常 ugi 的请求太大,打满 NN 第四级队列(实际上 NN 此时的前三级队列基本空闲),且新的 RPC call 还要持续向第4级队列加入,导致整体阻塞。

在这种情况下:

  1. 调大 RPC 队列长度无效,根本问题没有解决,增大 RPC 队列后,一样会马上打满,并且由于队列变长,NN 的 RPC 响应时间也相应的变得更长。
  2. 仅仅依靠 NN RPC 公平队列不够,公平队列只能对 NN 已经收到的 RPC 请求进行优先级区分,但如果一个客户端压根连不上 NN,那么它也不可能享受到公平队列的红利。

解决方案

此时,在 NameNode 侧,作为公平队列的补充,需要启用 RPC 退避机制,对这个异常的 ugi 做出更强力的惩罚,要点如下:

  1. 若一个 RPC 请求目前无法加入 callQueue,则它立即失败,且客户端开始间歇性重试。需要注意的是,在公平队列之下,高优先级的用户会享受额外的红利,这些用户的请求能使用所有4个队列,因此一般都能正常入队,但对于那些访问量异常频繁的用户,它们只能使用优先级最低的那个队列(即第4级队列),如果无法入队,则立刻失败并开始执行退避机制。可以看到,访问量越大的用户,越容易触发退避,这也是我们想要看到的结果。

  2. 多个任务共用同一个 ugi 的情况(如 hdfsadmin)
    这种情况下,虽然多个任务共用同一个 ugi,导致这个 ugi 的 RPC 量较大,优先级统一偏低。但是,即便使用同一个 ugi,这些任务也不是每个都有异常频繁的访问,也依然遵循我们的原则,即:RPC 请求量越大的任务,触发退避的概率越高,因此问题不大。退一步讲,就算偶尔误伤了正常的任务,也只是让它重试 RPC 请求而已,并不会立刻失败。

  3. 业务侧处理
    在被惩罚之后,异常 ugi 的任务会变慢,如果此时业务认为他的 RPC 请求退避的过于严重,已经无法容忍,那么他需要优化业务逻辑,减少 RPC 请求,或者更换其他负载较低的 HDFS 集群。

  4. 其它
    除了依据 RPC 请求队列是否已满执行退避之外,NN 还支持手动配置各个 RPC 队列的最大响应时间,并根据响应时间来执行退避(即:若该队列目前的响应时间已超阈值,则不再接受新的 RPC 请求),时间退避机制不适合现网的情况,主要原因是:

    1. 现网集群很多,各个集群的性能不一,不能找出一个普适于所有集群的阈值。
    2. 时间退避机制在实现中,本身有不合理的地方,例如:RPC 请求无法加入高优先级队列之后,会直接执行退避,而不是继续尝试加入低优先级队列。

具体配置项

NN 侧 hdfs-site.xml 中,配置 ipc.9000.backoff.enable 为 true,并重启 NN。

引入的风险和处理方法

主要的风险有下面两个:

  1. 日志问题
    目前,NN 的 RPC 退避对业务方不可感知,HDFS 客户端也不会打印任何日志,这会导致业务无所适从,不知道到底发生了什么,只是觉得业务突然变慢,但去查 NN 的话,又会发现 NN 的各项 RPC 指标都很好,最后陷入迷惑。这个问题需要解决,具体思路为:客户端侧应打印出具体的日常日志。

  2. 问题转嫁风险
    启用 RPC 退避机制后,可能引入的另一风险是:由于 NN 本身的问题,导致 NN 处理 RPC 变慢, RPC 队列打满,并触发退避机制,开始惩罚客户端。这本质上是一个风险转嫁,把原本属于 NN 的问题,推给了客户端去承受结果,这是无法接受的。

这个问题没有理想的解决方案,最好的情况是,NN 本身已经非常稳定,能够确保不会因为自身 bug 导致变慢,这时候就可以放心大胆的启用退避机制,但是,根据目前的现状,我们显然无法做出这个承诺。

因此,RPC 退避现在还不能默认打开,只有在 NN 出现问题之后,人为介入,确定是异常 ugi rpc 请求太大导致的问题,那么才可以作为一个应急手段,启用退避机制,配置相关选项并重启 NN 生效。

相关日志和监控项

  1. 客户端在进行退避时,打印如下日志,关键字(Server too busy):


  1. NN JMX 中,RpcClientBackoff 表示当前端口产生的的 RPC 退避请求数:
  1. ganglia 中,RpcClientBackoff 表示当前端口产生的的 RPC 退避请求数:
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容