xxl-job在k8s中日志丢失的问题

  • 一、背景
  • 二、问题描述
  • 三、先看看xxl-job获取日志的流程图
  • 四、排查过程
  • 五、解决方案


一、背景

由于业务需求,公司大量的用到了xxl-job这个分布式任务调度框架,这边所有的服务都是k8s进行部署的,最近又有对于任务调度中心发生了生产事故,又去补充了监控报警等功能,然而在进行问题排查的时候,发现了一个比较奇怪的现象,其实之前也是一直有的,不过可能没有引起太多的关注,就是执行器的执行日志丢失的问题。

二、问题描述

这个问题也是比较的奇怪,初步的一个现象就是,在固定时间点以后的所有执行日志全部丢失,不仅仅是一个任务,而是这个执行器上的所有任务,在一个时间点之前的日志全部丢失,并且所有执行器都有这个问题。

看到这个问题,第一反应应该是可能配置了日志自动清除的功能,但是实际上每次去看,每个执行器每次日志丢失的时间都不一样,可能也就最近几天的执行器日志可以访问到,然后上apollo上看了一下配置,确定xxl.job.executor.logretentiondays = -1 ,说明日志是不会自动清理,永久保存的,说明了并不是这个问题。

由于初步判断,已经确定了不是由于配置导致的执行器日志自动删除的问题,所以只能从别的方面找原因。

三、先看看xxl-job获取日志的流程图

所有容器重启 容器重启日志丢失_Pod

获取调度日志:
直接通过调度中心访问存储在MySQL中的调度日志列表

获取执行器日志:
1.通过从MySQL中查询出来的调度日志记录,获取到该执行日志的历史IP地址、logId,时间戳等信息
2.发起一个netty请求,读取存储在执行器段该路径下的log日志文件

四、排查过程

首先,在任务调度中心的网页上登录后,找到那些不能查看执行器日志的记录

能够发现一点的是,查看调度备注如下所示,对于同一个执行器来说,能够访问的调度日志与不能够访问的调度日志,他们的触发调度的地址是不一样的。

能够访问的日志其触发调度地址与执行器注册在线地址是一样的,而不能访问的日志其触发调度日志与执行器地址不一样。

调度日志备注:

所有容器重启 容器重启日志丢失_所有容器重启_02


目前执行器的注册地址

所有容器重启 容器重启日志丢失_所有容器重启_03

由于k8s的特性,每次重新部署pod,其IP地址总是在不断变化的,从而会导致执行器的地址是会在经常变的

目前确定的现象就是:当历史调度日志的地址与目前执行器注册的地址不一致时,xxl-job-admin带着历史IP地址向执行器请求日志时,便会出现连接超时的情况

点击查看执行器日志,F12显示调用日志接口异常报错,网络连接的一个错误,显示ConnectionTimeoutException,而那些能正常显示的记录的接口是能够正常返回的,所以,这就是与该问题产生的最直接的原因。

所以这个时候就需要用到xxl-job的代码,在本地调试,复现该问题,能够看到,在调用admin的接口时,发起了一个远程调用去获取log数据,同时又出现了连接超时的情况。

所有容器重启 容器重启日志丢失_分布式_04


由于整个调度日志记录是存储在MySQL数据库中的,当任务被调度触发或者产生回调的时候,就会去更新数据库表中的数据,而执行器的日志却是以文件的方式存储在执行器所在本地服务器上的。

当我们去访问日志列表的时候,去加载数据库中的内容是完全没有问题的,然后要去访问某条调度日志的具体的执行器日志,因为每条调度日志都会存储有它的执行器的注册地址,所以它会通过原本记录好的执行器的注册地址去访问该地址来远程获取,而我们页面上出现的网络报错,就是因为在这里xxl-admin服务会去访问执行器地址而报错。

五、解决方案

目前已经确定了只要重新部署一次执行器Pod,就会导致该执行器日志全部丢失,由于k8s的特性,需要解决两个问题:

  1. 由于不稳定的IP地址导致的无法访问到执行器的问题
  2. 重新部署Pod后log文件直接丢失问题

方案一:
这个方案是目前已经在公司应用的。
对于问题2:

只需要将该执行器的xxl-job日志的路径,data/applogs/xxl-job/jobhandler 挂载到持久化数据卷下,实现该该路径的持久化,便能够解决。

对于问题1:

  1. 由于IP地址不稳定,那么就不再通过调度日志中的历史IP地址进行日志的访问
  2. 前端在点击执行日志时,将该调度日志的任务ID返回过来,代替之前的历史IP地址
  3. 后端通过任务ID关联查询到对应的执行器ID,再通过执行器ID查询执行器注册在线地址表,获取到目前最新的在线的执行器地址
  4. 取第一个在线地址,访问到该执行器的日志文件(由于同一个执行器的不同副本都会挂载日志到同一个持久化存储卷下,因此即使有多个Pod,也能访问到同一个日志文件)

方案二:
这个方案是最近在学习k8s的过程中,思考出来,觉得应该是可以这么做的。

直接使用Headless Service+StatefulSet来实现。

  1. StatefulSet用来提供稳定的网络标识和稳定的存储。
  2. 而Headless Service配合java使用k8s的restful api来访问,获取到相应的后端Pod信息,通过他来获取稳定的网络标识与不稳定的Pod IP之间的对应关系
  3. 然后xxl-job-admin这边的日志不再存储这个IP地址,而是存储这个稳定的网络标识
  4. 在需要访问后端执行器Pod的日志之前,先调用k8s api获取网络标识对应的Pod IP和端口号
  5. 再利用这个Pod IP去访问这个后端Pod的日志信息

不过这个方案比较复杂,有较大的改造,所以目前没有这么做,在k8s权威指南中看到,看很多数据库中间件啥的在k8s 中好像也是通过Headless Service+StatefulSet来实现服务集群之间的注册与发现的。