赞
踩
相信大家在项目中一定会有用到异步定时任务,因为task在项目中应用场景还是非常广泛的,因为对于一些实时性要求不是很高的场景,如果用同步的话,就会造成资源浪费,并且再高并发场景下很容易就将服务器的资源耗光,影响用户的体验,同时同步的场景也增大了代码之间的耦合度,同时task也会经常用于后端大批量的操作,比方说补偿等等,因此task的使用场景还是非常广泛和不可或缺的。
一般用的比较多的task是基于spring的一个单线程task(当然了,你也可以设置成多线程并发调用,默认是单线程)。以下简称task。它有着以下几个好处,首先是轻量级,本身属于spring内置模块,因此无需进行额外的引入和配置。其次,基于注解的配置,让其上手非常的简单,学习成本很低。默认为单线程,消耗资源很少,并且他是spring家族成员,与spring框架完美集成。无需担心其他冲突。
理论上来说task任务的生命周期是伴随整个应用程序而存在的,是不会停止和销毁,除非项目本身挂掉,或者外部内存溢出等非应用本身的异常。但是笔者就遇到了这样的一个奇葩问题。
问题描述:本人所负责的电商项目系统中,由于需要跟很多外部系统对接数据,因此采用了task+调用远程接口的方式将数据接入进来,其中,有一个任务是调用京东的一个接口,但是这个任务经常会无缘无故就不跑了,时间不固定,平均一个月会出现几起,这样将导致公司在京东的订单无法接入到本公司内部系统中,这样会影响后面一些列流程,比如最严重的是影响客户收货。判断任务没跑的方式很简单,就是看有没有日志输出,因为task入口处都会加上日志输出,因此可以看出来,日志输出还是很重要的,尤其是排查线上问题的时候就显得非常的有用。通过拉取任务最后一次执行的日志来看并没有发现任何问题,好像一切正常一样。因此每次遇到这样的问题的时候都是直接重启应用来解决,但是这样肯定无法真正解决问题,因为不可能每时每刻都有人来重启的,而且重启后还要进行订单重新不抓的操作,风险太大。因此根本问题还是找到原因,同时这也是对自己的一个提升。
问题分析:为什么任务会停止?可能有哪些原因?首先task默认为单线程,也就是说他不会并发去执行,只有当前的线程执行完后,才会进行下一次调度,那么问题来了,如果当前线程一直卡死呢,造成阻塞呢?那是不是就是会导致下一次调度任务永远不会执行呢,带着这个问题去一行行的看代码,发现并没有什么问题?那问题是什么呢?或者哪里有遗漏呢?忽然想起其他任务都不会停,唯有这个任务。那这个任务有什么特殊的地方吗?一下就想到了,这个任务有调用京东提供的第三方接口,那会不会是京东接口内部实现有问题导致的呢?带着这样的疑问,找到了京东接口的相关jar包进行反编译,果然看到了一个可以之处。贴下代码
public DefaultJdClient(String serverUrl, String accessToken, String appKey, String appSecret) { this.connectTimeout = 0;//问题在这 this.readTimeout = 0;//问题在这 this.serverUrl = serverUrl; this.accessToken = accessToken; this.appKey = appKey; this.appSecret = appSecret; }
这是创建调用京东接口的一个client,调用的时候会传入相关的参数来构造DefaultJdClient对象并发起远程调用,底层是通过http进行请求。其中connectTimeout表示链接超时时间,readTimeout表示从接口中读取数据的超时时间,京东内部实现默认这个值为0。这其实是有问题的,0不是代表超时时间为0毫秒,恰恰相反是永不超时的意思。试想,如果某一个task在通过DefaultJdClient来进行远程调用的时候,如果出现了未知原因导致读取数据或者连接一直得不到返回(这很正常,比方说接口的服务端挂掉,或者出了未知的bug或者死锁,死循环之类的,也或者读取接口返回的数据产生了未知异常),那就会导致task的线程阻塞,因此永远都不会进行下一次的任务调度,因为task会认为当前线程的任务还没完成,因此会一直等待。
解决方式:利用java的多态的特性,新建该类的子类并重写构造方法,为connectTimeout和readTimeout属性设置合理的超时时间,改造完毕,部署上线,目前已经一个月的时间再也没有出现过这样的问题,因此可以认定问题已经解决。
结论:每一次排查和解决问题的过程就是自我成长的过程,同时也是对自己最大的一种激励。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。