一.引言
Flink 程序内有读取 hbase 的需求,近期任务启动后偶发 sink 端背压 100% 导致无数据写入下游且无明显 exception 报错,重启任务后有较大概率恢复服务,但也有可能继续背压 100% 从而堵塞任务,遂开始排查。
二.问题描述
程序执行一段时间后,查看监控发现 Source + Process + Sink 端 back pressure 背压全部达到 100% ,很明显是数据发生堵塞
编辑
查看 on-cpu 无堆栈显示因此排除 cpu 问题,需要进一步查看任务执行、IO、网络等问题,随后查看 off-cpu 的 Flame Graph 看到堆栈最终定位在:
org.apache.hadoop.hbase.util.RetryCounter.sleepUntilNextRetry
编辑
三.问题分析
1.堆栈分析
上述任务定位在 hbase 的 retryCounter.sleepUnitlNextRetry ,虽然没有看过 Hbase 的源码,但是根据这个堆栈信息大致可以判断是 hbase 读取时遇到问题导致:
retry 重试 + sleepUntilNextRetry 等待并重试
编辑
二者结合导致任务卡死从而数据流处理堵塞,再影响后续数据,从而导致背压全部达到 100%。
2.代码定位
off-cpu 的 root 调用为下述语句,非常基本的 hbase get 操作:
Result result = hbaseTable.get(sampleGet);
按照堆栈看一下底层源码:
可以看到 Try 内逻辑真正执行的只有 1行,即 checkZk() 随后 getData(),本地测试 Get 没有问题,所以只能定位到 checkZk() 这里。
编辑
下面看一下 checkZk 主要负责什么事情:
编辑
checkZk 初始化新的 Zookeeper,如果初始化失败则返回 unable to create Zookeeper Connection,所以上面集群 hbase 无法获取数据基本定位在 Zk 创建失败。
3.问题解决
zookeeper 连接失败导致 Hbase Client 初始化失败从而数据无法获取导致 RetryAndSleep,一般服务器无法创建连接都因为访问过多导致,即服务过载,例如 JedisPool 的 resource,其使用有限制,超过后将无法获取连接从而导致获取数据失败。
查看 hbaes 对应 zk 下的服务器连接情况:
编辑
看到某个 ip 下存在大量 zk 连接,通过查询 zk server 的配置,查看当前单台客户端允许的最大连接数已全部被该 ip 占用,从而导致我的 Flink 程序无法初始化 zk。所以下面只需要解决这里连接过多的问题,经过排查发现该 ip 下对应 java 任务存在 zookeeper 泄露,即代码逻辑内不断申请 zookerper 连接,从而导致连接数过多,修改后空闲连接数上升,Flink-Hbase 服务也正常运行。
三.总结
1.Flink 问题定位
Flink 发生问题第一步查看 Excpetion,如果没有 Exception 就查看 Flame Graph,根据 on-cpu 和 off-cpu 的堆栈定位是 cpu 的问题还是自身代码的问题。
2.客户端初始化
Flink 初始化客户端的代码在 ProcessFunction 的 open 函数内,该方法可以保证一个 TaskManger 只初始化一个 Hbase Connection,所以很难突破单台机器初始化 zk 的限制,同学们在执行任务时也需要注意初始化无论 Hbase,Jedis 等客户端最后不要频繁初始化以及初始化过多。
编辑
这里我初始化了 35 个 TaskManager,每个 Manger 上只初始化了一个 connection。
3.重启解决问题
上面有一个现象是我的任务重启后有一定概率恢复正常,通过上面的问题排查我们也可以得到答案,由于某 ip 下占用过多 connection,如果我的任务恰巧提交到该任务对应的机器,则我的任务无法获取连接导致堵塞,而如果任务提交恰好避开该 ip 对应的机器则代码执行无误,所以任务重启会有一定概率修复。