一.问题描述
我们线上有两台xxljob的机器,ip分别是39和40,但是运行一段时间后,有人反馈有些任务会执行两次,导致重复执行
二.问题排查
2.1 排查日志记录
通过对触发日志的查询,确实同一时刻有两条日志都触发了
但是我记得xxl是支持集群部署的,多台机器同一时刻只有一台机器会执行,通过查看代码,得知
while (!scheduleThreadToStop) {
// Scan Job
long start = System.currentTimeMillis();
Connection conn = null;
Boolean connAutoCommit = null;
PreparedStatement preparedStatement = null;
boolean preReadSuc = true;
try {
conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
connAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
// todo 这里会获取xxl_job_lock表的锁,哪一台机器抢到锁,哪一台会执行
preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
preparedStatement.execute();
但是通过程序发现这个锁没有启动用,于是查询数据库得知 select * from xxl_job_lock where lock_name = 'schedule_lock' ; 表中为空......啊,原来是忘记插入这个表的数据了,于是两台机器都能执行,导致任务会重复触发
三.问题修复过程中的问题
问题很容易就排查出来了,那么就很简单了,原来是数据忘记插入了,那直接执行sql语句插入数据不就行了
INSERT INTO xxl_job.xxl_job_lock ( lock_name) VALUES ( 'schedule_lock');
但是紧接着问题就来了
3.1 问题
在插入过程中,会等待很久,插入数据失败,过了51秒后,会报超时
然后就查询mysql设置的过期时间
确实是50秒,没问题,能对的上
3.2 设置超时时间,再次执行sql
SET innodb_lock_wait_timeout = 150; 我把mysql的超时时间设置为150秒,再次执行插入语句,执行多次后依旧插入数据失败,于是我感觉问题不是那么简单
3.3 超时时间分析
为什么超时时间边长后,还是执行不了呢,原因是线上环境存在大量定时任务,通过查询有360多个任务,线上两台机器还在一些死循环获取这个锁,哪一台机器获取锁之后,就会执行这些任务,导致我本地很难抢到锁,咋办?难道要把程序stop一次,那就算是事故了.....
3.4 问题解决
既然被锁住了,肯定有一些进程在等待,思路就是找到这些线程,然后杀死 使用 show processlist 这个命令不能添加where过滤条件
于是使用这个语句 select * from information_schema.processlist where db='xxl_job' 把id查出来后,批量杀死进程
kill 3952509;
kill 3952510;
kill 3952511;
kill 3952522;
kill 3952513;
kill 3952520;
kill 3952514;
kill 3952515;
kill 3952507;
kill 3952525;
kill 3952521;
kill 3952523;
kill 3952506;
kill 3952474;
kill 3952517;
kill 3952505;
kill 3952518;
kill 3952519;
kill 3952508;
kill 3952516;
kill 3952524;
然后再次执行
INSERT INTO xxl_job.xxl_job_lock ( lock_name) VALUES ( 'schedule_lock');语句,成功插入,至此数据插入完成,程序也不在报错
四.验证
我把这个任务触发时间修改了下,等待任务触发
修改后,任务确实只触发一次,不会重复执行