避免死循环-记一次问题排查

Posted by chunpat on December 10, 2022

起因

最近有个简单的队列tp-queue时不时出现问题,出问题后就整个队列卡住了。 使用的是ThinkPHP的think-queue,项目地址:https://github.com/top-think/think-queue。

排查

执行程序

 nohup /www/server/php/72/bin/php think queue:listen --queue=storeSystemJobQueue --delay=0 --memory=2048 --sleep=3 --tries=0
  >> queue.log  2>&1

#  参数解析  
#      --queue[=QUEUE]      The queue to listen on
#      --delay[=DELAY]      Amount of time to delay failed jobs [default: 0]
#      --memory[=MEMORY]    The memory limit in megabytes [default: 128]
#      --timeout[=TIMEOUT]  Seconds a job may run before timing out [default: 60]
#      --sleep[=SLEEP]      Seconds to wait before checking queue for jobs [default: 3]
#      --tries[=TRIES]      Number of times to attempt a job before logging it failed [default: 0]

用的是数据库驱动,数据库存储数据。出问题后显示的是队列执行成功,但是没有消化,然后就卡住了。

数据库sql日志执行的是这三条:

  • 更新: 将处理数据,但是没有消化的队列数据初始化, 并更新尝试次数+1;
  • 查询: 查询一条有效数据;
  • 更新: 处理完业务,更新;

sql语句如下:

[ 2022-12-01T17:46:04+08:00 ][ sql ] [ SQL ] UPDATE `store_system_jobs`  SET `reserved` = 0 , `reserved_at` = NULL , `attempts` = `attempts` + 1  WHERE  `queue` = 'storeSystemWarehousingJobQueue'  AND `reserved` = 1  AND `reserved_at` <= 1669886164 [ RunTime:0.003657s ]
[ 2022-12-01T17:46:04+08:00 ][ sql ] [ SQL ] SELECT * FROM `store_system_jobs` WHERE  `queue` = 'storeSystemWarehousingJobQueue'  AND `reserved` = 0  AND `available_at` <= 1669887964 ORDER BY `id` ASC LIMIT 1   FOR UPDATE [ RunTime:0.004706s ]
[ 2022-12-01T17:46:04+08:00 ][ sql ] [ SQL ] UPDATE `store_system_jobs`  SET `reserved` = 1 , `reserved_at` = 1669887964  WHERE  `id` = 450 [ RunTime:0.004191s ]

这里我梳理下,简易流程图如下:

程序挂了,任务不跑,第一开始没思路,临时解决的是重启,但是执行一段时间又不行,特别是高峰的时候很绝望。然后想的是性能问题,是不是数据库介质不行。 开始查看数据库是不是产生了死锁了,删除锁。查看锁的语句如下:

//查锁
select * from information_schema.innodb_trx
//删锁 id
kill 738178711

然后又可以了。又过了一段时间,还是不行。只能重新排查,突然发现在重启队列的时候,查看数据库锁看到有条没读锁的sql,一直挂着,然后查看业务代码 ,发现这不就是查看据库数据是否重复数据的sql语句吗?,这里产生了死循环,效果就会出现下面流程图:

由于框架队列默认超时机制是60s,超过了就会重新执行,直到达到一定次数,程序就会die。这里面有日志,但是没留意这个超时异常中断的记录。

反思

  • 1、 优化队列,超时记录与报警。
  • 2、 代码不可以留有死循环的可能,一定杜绝,一开始就设定个次数。

迭代

  • 2022年12月12日 09:00:00 初稿

参考

1、Lock wait timeout exceeded; try restarting transaction


Creative Commons License
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 chunpat 及本文源链接。