问题现象
有个flink实时任务,读kafka和redis,中间有复杂的逻辑处理过程,最终结果写redis。flink实时任务运行一段时间后阻塞了,有时是几个小时后,有时是一两天后。
任务看起来正常是正常的,但kafka消费已经停止,checkpoint也失败。看日志,当问题出现后,kafka一直WARN,提示如下
Marking the coordinator xxxxxx dead.
Marking the coordinator xxxxxx dead.
Marking the coordinator xxxxxx dead.……
任务看起来正常运行,但不再消费kafka,checkpoint失败,除此之外,cpu、mem、io、日志,均看不出异常;集群节点登陆需要申请权限,比较麻烦。
原因分析
步骤1
刚开始以为是kafka消费的问题,导致了任务阻塞。于是寻找解决办法,发现也有部分人遇到类似的问题,但办法不管用。
后面了解到kafka集群版本之前是0.9,最近升级到了2.2.0,建议更新了kafka客户端版本。升级到2.2.0客户端版本后,没有"Marking the coordinator xxx dead."的日志了,但任务还是在运行一段时间后回挂起,问题还没有解决。
步骤2
由于没有权限登陆集群节点,只能通过flink ui,观察cpu、mem、io正常,查看job manager和task manager日志,也没有发现异常。
每次任务阻塞后查看checkpoint,都是失败。刚开始以为checkpoint间隔5秒太短,于是一直往上调整到10分钟,还是照样挂。
有次checkpoint刚挂不久,发现checkpoint挂在sink redis了,其他操作都是几到几十毫秒。那大概原因找到了,写redis有问题。
同时通过最近几次任务挂起后的观察,sink redis数量好像都是在1608,资源申请的是8核cpu、单核内存4G,配置文件prod.sink.redis.maxPools=200,(200+1)*8=1608,看起来有联系?
将prod.sink.redis.maxPools设置为1或者10,问题很快就复现了,可以基本确定,问题发生在sink redis操作了。
步骤3
分析代码,因为写redis需要设置key有效时间,所以没有用原生的flink-redis-connector,而是用了flink-redis-connector+jedis,基于二者做了稍显复杂的封装,用于读写redis三种集群模式:主从复制、哨兵模式、集群模式。
最终发现,setex实现方法有问题,使用完连接后,并没有释放资源。
基本代码逻辑如下:
@Override
public String setex(String key, int seconds, String value) {
String res = null;
Jedis jedis = null;
try {
jedis = getInstance();
res = jedis.setex(key, seconds, value);
} catch (Exception e) {
logger.error("Cannot get Redis message with command GET to key {} error message {}", key, e.getMessage());