记一次job不跑的问题
现象:同步数据的脚本突然不执行了,但从日志上来看job的确跑了(日志打印了xxx开始执行...)。
原因:多个job使用一个线程池,其中一个job因为第三方数据和代码的问题导致资源未能释放掉,从而引发了同步数据脚本执行不了。
如何发现的:
- 首先日志有打印说明job肯定执行了,所以肯定是job内部的代码导致业务逻辑未执行。
- 沿着这个方向去看了下具体的业务实现,发现它使用了ThreadPoolExecutor和CountDownLatch来处理;而ThreadPoolExecutor又定义为成员变量(多个job使用一个ThreadPoolExecutor),而非局部变量。
- 根据这些特性便猜测应该是有哪个job在使用线程资源时未能及时的释放掉,接着我挨个排查,终于在一个job中看到了奇葩的while(true);它的逻辑是直到拿到想要的数据才会break掉,当然正常情况下是没有问题的,但当第三方数据异常便会出现数据积压,直到堆满线程池,让其它job都无法正常执行下去。
解决方案:
- 当时比较紧急,先采用了最简单,且改动最小的办法:让那个while(true)的job使用私有的线程池,这样就不会影响到其它的job了。
- 之后再询问当事人后,了解到有问题的那个job对数据的实时性要求没那么高,所以我们去除了while(true)的逻辑,让处于中间态的数据直接结束,到下一次执行job的时候再同步数据。
当时的代码(伪代码):
1 public class Job { 2 3 /** 4 * 线程池(我为了方便才使用Executors,实际开发不建议使用Executors来勾线线程池) 5 */ 6 private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(15); 7 8 /** 9 * 影响业务的job,也就是上面所说突然不跑的job 10 */ 11 public void importantJob() { 12 try { 13 final CountDownLatch latch = new CountDownLatch(size); 14 for (final Orders order : orderList) { 15 THREAD_POOL.execute(new Runnable() { 16 @Override 17 public void run() { 18 try { 19 log.info("{} importantJob start", order.getId()); 20 // 这里是业务处理 21 log.info("{} importantJob end", order.getId()); 22 } 23 catch (Exception e) { 24 log.error("importantJob handler error", e); 25 } 26 finally { 27 latch.countDown(); 28 } 29 } 30 }); 31 } 32 33 latch.await(); 34 } 35 catch (Exception e) { 36 log.error("importantJob error", e); 37 } 38 } 39 40 /** 41 * 影响业务的job,也就是上面所说突然不跑的job 42 */ 43 public void minorJob() { 44 try { 45 final CountDownLatch latch = new CountDownLatch(size); 46 for (final Data data : dataList) { 47 THREAD_POOL.execute(new Runnable() { 48 @Override 49 public void run() { 50 try { 51 log.info("{} minorJob start", data.getId()); 52 minorJobHandler(); 53 log.info("{} minorJob end", data.getId()); 54 } 55 catch (Exception e) { 56 log.error("minorJob handler error", e); 57 } 58 finally { 59 latch.countDown(); 60 } 61 } 62 }); 63 } 64 65 latch.await(); 66 } 67 catch (Exception e) { 68 log.error("minorJob error", e); 69 } 70 } 71 72 private void minorJobHandler() { 73 // 一直拿第三方数据,直到拿到想要的结果 74 while (true) { 75 // 获取第三方数据 76 Data data = getData(); 77 if (data.status == 1) { 78 // 拿到想要的数据,结束循环 79 break; 80 } 81 // 否则一直去获取 82 } 83 } 84 85 }
这是不是很有意思(✪ω✪)