问题描述
HDFS 集群中,有时会因为 NameNode 的超负荷运行(一般是有异常任务大量访问 NN),导致其它客户端无法和 NameNode 创建 TCP 连接,最终导致业务响应非常缓慢,具体表现如下:
-
NN 9000 端口 TCP 连接队列打满,新的 TCP 连接无法建立:
-
NN 9000 端口的 RPC callQueue 打满,一般表现为,高优先级队列还有空间,但最低优先级队列被打满(默认情况下是第4级,队列长度默认为6400),这说明有一个访问量特别大的 ugi,但需要注意的是,还有很多其他情况也会导致 rpc callQueue 打满,因此需要进一步判断:
-
进一步,NN 9000 端口第4级 RPC 队列所处理的 RPC 总量,明显远远多于其它队列,证明有一个访问量超大的 ugi:
这个问题的链条比较长,但根本原因很清楚,就是有一个访问量巨大的异常 ugi 存在,完整的推导链条如下:
- 某一个异常 ugi 的 rpc 请求量太大,导致 NN 256 个 handler (NN handler 个数配置为 256)处理不过来
- 这个异常 ugi 的所有 rpc 请求,都会直奔 NN 的第四级 RPC 队列而去,导致 NN 的该 RPC callQueue 积压 6000多个 call
- NN 的4个 reader 无法把该 ugi 的新 rpc call 请求加入到第四级 callQueue,陷入阻塞
- NN 的所有4个 reader 的连接队列都无法得到处理,全部打满(默认每个 reader 的连接队列 size 都是 100)
- NN 的 listener 无法把新的连接加到4个 reader 的连接队列,陷入阻塞
- NN 的 listener 无法 accept 新的 TCP 连接
- 客户端无法和 NameNode 无建立新的 TCP 连接.
此时集群对外的表现是:客户端访问 NN 时,会间歇性的抽疯,具体表现为:
- 只要它能连上 NN, 上了这趟车,那么它就能较快处理完毕并得到响应
- 但如果它甚至都连不上 NN(即无法建立 TCP 连接),那么它将等到天荒地老。
原因分析
原因很明确,某一个异常 ugi 的请求太大,打满 NN 第四级队列(实际上 NN 此时的前三级队列基本空闲),且新的 RPC call 还要持续向第4级队列加入,导致整体阻塞。
在这种情况下:
- 调大 RPC 队列长度无效,根本问题没有解决,增大 RPC 队列后,一样会马上打满,并且由于队列变长,NN 的 RPC 响应时间也相应的变得更长。
- 仅仅依靠 NN RPC 公平队列不够,公平队列只能对 NN 已经收到的 RPC 请求进行优先级区分,但如果一个客户端压根连不上 NN,那么它也不可能享受到公平队列的红利。
解决方案
此时,在 NameNode 侧,作为公平队列的补充,需要启用 RPC 退避机制,对这个异常的 ugi 做出更强力的惩罚,要点如下:
若一个 RPC 请求目前无法加入 callQueue,则它立即失败,且客户端开始间歇性重试。需要注意的是,在公平队列之下,高优先级的用户会享受额外的红利,这些用户的请求能使用所有4个队列,因此一般都能正常入队,但对于那些访问量异常频繁的用户,它们只能使用优先级最低的那个队列(即第4级队列),如果无法入队,则立刻失败并开始执行退避机制。可以看到,访问量越大的用户,越容易触发退避,这也是我们想要看到的结果。
多个任务共用同一个 ugi 的情况(如 hdfsadmin)
这种情况下,虽然多个任务共用同一个 ugi,导致这个 ugi 的 RPC 量较大,优先级统一偏低。但是,即便使用同一个 ugi,这些任务也不是每个都有异常频繁的访问,也依然遵循我们的原则,即:RPC 请求量越大的任务,触发退避的概率越高,因此问题不大。退一步讲,就算偶尔误伤了正常的任务,也只是让它重试 RPC 请求而已,并不会立刻失败。业务侧处理
在被惩罚之后,异常 ugi 的任务会变慢,如果此时业务认为他的 RPC 请求退避的过于严重,已经无法容忍,那么他需要优化业务逻辑,减少 RPC 请求,或者更换其他负载较低的 HDFS 集群。-
其它
除了依据 RPC 请求队列是否已满执行退避之外,NN 还支持手动配置各个 RPC 队列的最大响应时间,并根据响应时间来执行退避(即:若该队列目前的响应时间已超阈值,则不再接受新的 RPC 请求),时间退避机制不适合现网的情况,主要原因是:- 现网集群很多,各个集群的性能不一,不能找出一个普适于所有集群的阈值。
- 时间退避机制在实现中,本身有不合理的地方,例如:RPC 请求无法加入高优先级队列之后,会直接执行退避,而不是继续尝试加入低优先级队列。
具体配置项
NN 侧 hdfs-site.xml 中,配置 ipc.9000.backoff.enable
为 true,并重启 NN。
引入的风险和处理方法
主要的风险有下面两个:
日志问题
目前,NN 的 RPC 退避对业务方不可感知,HDFS 客户端也不会打印任何日志,这会导致业务无所适从,不知道到底发生了什么,只是觉得业务突然变慢,但去查 NN 的话,又会发现 NN 的各项 RPC 指标都很好,最后陷入迷惑。这个问题需要解决,具体思路为:客户端侧应打印出具体的日常日志。问题转嫁风险
启用 RPC 退避机制后,可能引入的另一风险是:由于 NN 本身的问题,导致 NN 处理 RPC 变慢, RPC 队列打满,并触发退避机制,开始惩罚客户端。这本质上是一个风险转嫁,把原本属于 NN 的问题,推给了客户端去承受结果,这是无法接受的。
这个问题没有理想的解决方案,最好的情况是,NN 本身已经非常稳定,能够确保不会因为自身 bug 导致变慢,这时候就可以放心大胆的启用退避机制,但是,根据目前的现状,我们显然无法做出这个承诺。
因此,RPC 退避现在还不能默认打开,只有在 NN 出现问题之后,人为介入,确定是异常 ugi rpc 请求太大导致的问题,那么才可以作为一个应急手段,启用退避机制,配置相关选项并重启 NN 生效。
相关日志和监控项
-
客户端在进行退避时,打印如下日志,关键字(Server too busy):
- NN JMX 中,
RpcClientBackoff
表示当前端口产生的的 RPC 退避请求数:
- ganglia 中,
RpcClientBackoff
表示当前端口产生的的 RPC 退避请求数: