定时推动异步任务
异步任务是每个系统几乎都会涉及到的,我这里讲一个工作中的例子。
一、 生成(插入)异步任务
生成异步任务其实就是一个insert语句,在需要生成异步任务的地方调用即可,如下:
1. jobManager.receiveJob(JobTypeEnum.APPLY_SYNC,applyDO.getApplySeqno(), "{'HANDLERS_GROUP':'applyInfoSyncHandler','ipRoleId':'"
2. +(String) message.get("ipRoleId")+"'}");
3. jobManager.receiveJob实现如下:
4. public JobDO receiveJob(JobTypeEnum jobType,String bizNo, String extJson) {
5. if (jobType == null) {
6. LOG.error("receiveJob()参数为空,jobType:"+ jobType);
7. throw new BizException(ErrorCode.ILLEGAL_ARGUMENT,"参数为空!");
8. }
9. JobDO jobDO = new JobDO();
10. jobDO.setStatus(JobStatusEnum.INIT.code());
11. jobDO.setJobType(jobType.code());
12. jobDO.setBizNo(bizNo);
13. jobDO.setExtJson(extJson);
14. jobDO.setCreatorIp(Util.getIp());
15. jobDO.setEnvFlag(SysEnvUtil.getSystemEvn());
16.
17. return jobDao.insert(jobDO);
18. }
可见就是将异步任务类型、业务编号、扩展字段、任务状态等组装起来然后插入的过程。
二、 启动分发器线程
异步任务已经生成了,就需要去查询这些待执行的异步任务然后执行就行了。但还牵涉到一个启动执行异步任务的点,就是任务的入口,而且还需要有定时功能,每隔一段时间就去跑待执行的异步任务。我这里触发的方式是创建一个类实现spring的org.springframework.context.ApplicationListener接口,然后实现里面的onApplicationEvent方法,如下:
19. public class JobDispatcherStarter implementsApplicationListener {
20. privatestatic final Logger LOG =LoggerFactory.getLogger("applyCenterJobLog");
21. privateJobDispatcher jobDispatcher;
22. privatestatic boolean isInited = false;
23.
24. @Override
25. publicvoid onApplicationEvent(ApplicationEvent event) {
26. if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())){
27. if (!isInited) {
28. LOG.info("任务分发器JobDispatcherStarter启动开始");
29. isInited = true;
30. jobDispatcher.dispatcher();
31. LOG.info("任务分发器JobDispatcherStarter启动完成");
32. }
33. }
34. }
35. publicvoid setJobDispatcher(JobDispatcher jobDispatcher) {
36. this.jobDispatcher = jobDispatcher;
37. }
38. publicvoid setInterval(long interval) {
39. JobDispatcherStarter.interval = interval;
40. }
41. }
这样一来JobDispatcherStarter会在spring容器加载完毕后调用onApplicationEvent方法,从而启动任务分发器jobDispatcher。但这样虽然会启动异步任务,但没有定时,就是不能每隔一段时间就跑一次。对于这个是任务分发器自己做到的,继续看下面就知道了。
三、 执行任务分发器
任务分发器就是具体执行异步任务的,一般都把他写成一个线程类(我这里是通过实现Runnable的),在run方法中用线程池管理和执行具体执行异步任务操作的线程类(也要自己建一个)。
定时执行(每隔一段时间跑一次)其实是靠java最常见的定时任务方式做到的,例子如下:
42. /**
43. * 普通thread
44. * 这是最常见的,创建一个thread,然后让它在while循环里一直运行着,
45. * 通过sleep方法来达到定时任务的效果。这样可以快速简单的实现,代码如下:
46. *
47. */
48. public class Task {
49. public static void main(String[] args) {
50. // 一秒跑一次
51. final long timeInterval = 1000;
52. Runnable runnable = new Runnable() {
53. public void run() {
54. while (true) {
55. System.out.println("Hello !!");
56. try {
57. Thread.sleep(timeInterval);
58. } catch (InterruptedException e) {
59. e.printStackTrace();
60. }
61. }
62. }
63. };
64. Thread thread = new Thread(runnable);
65. thread.start();
66. }
67. }
知道这种方式之后就明了多了,所以我这就是用线程池执行具体执行异步任务操作的线程类前套一个while循环一直跑,直到任务分发器对象被销毁,当没任务跑时就sleep一段时间,然后再跑,代码如下:
68. public class JobDispatcher implementsRunnable {
69. privatestatic final Logger LOG =LoggerFactory.getLogger("applyCenterJobLog");
70. /**守护线程名称 */
71. privateString name;
72. /**一天秒数 */
73. privatestatic final long ONE_DAY_SEC = 24 * 60 * 60;
74. /**线程池队列长度 */
75. privateint queueSize = 5;
76. /**初始处理线程数 */
77. privateint coreSize = 5;
78. /**最大处理线程数 */
79. privateint maxSize = 5;
80. /**空闲线程最大闲置时间 */
81. privatelong keepAliveTime = ONE_DAY_SEC;
82. /**线程池接收新任务阀值 */
83. privateint hungrySize = 2;
84. /**分发器运行状态标记 */
85. privateboolean isRunning = true;
86. /**无命令处理时休息时常(毫秒) */
87. privatelong noCmdSleepMillis = 1000;
88. /**出现系统异常时休息时常(毫秒),防止把系统拖垮 */
89. privatelong errorCmdSleepMillis =10000;
90.
91. privateJobManager jobManager;
92. /**handler产生工厂类 */
93. privateJobHandlerFactory jobHandlerFactory;
94.
95. privateList<String> jobTypeList;
96.
97. /**
98. *spring init
99. */
100. publicvoid init() {
101. LOG.info("分发器【" + name+ "】init!!!!!");
102. jobTypeList = jobHandlerFactory.getJobTypeList();
103. }
104.
105. /**
106. *spring destroy
107. */
108. publicvoid destroy() {
109. LOG.warn("收到分发器【" + name+ "】停止通知!!!!!");
110. isRunning = false;
111. }
112.
113. @Override
114. publicvoid run() {
115. LOG.info("分发器【" + name+ "】启动ing...");
116. BlockingQueue<Runnable> queue = newArrayBlockingQueue<Runnable>(queueSize);
117. ThreadPoolExecutor executor = newThreadPoolExecutor(coreSize, maxSize, keepAliveTime, TimeUnit.SECONDS, queue);
118. while (isRunning) {
119. try {
120. int i = 0;
121. if (queue.size() < hungrySize){
122. for (String jobType : jobTypeList){
123. List<JobDO> jobDOList= jobManager.assignJob(jobType, queueSize - queue.size());
124. for (JobDO jobDO : jobDOList){
125. i++;
126. JobHandler<JobDO>tmpJobHandler = jobHandlerFactory.getHandler(jobDO);
127. ExecuteJobThread<JobDO>executeCmdThread = new ExecuteJobThread<JobDO>(jobDO, tmpJobHandler);
128. executor.execute(executeCmdThread);
129. }
130. }
131. } else {
132. ThreadUtil.sleep(noCmdSleepMillis,LOG);
133. }
134. if (i == 0) {
135. ThreadUtil.sleep(noCmdSleepMillis,LOG);
136. } else {
137. i = 0;
138. }
139. } catch (Exception e) {
140. LOG.error("dispacher 调度异常" + e.getMessage(),e);
141. ThreadUtil.sleep(errorCmdSleepMillis,LOG);
142. }
143. }
144. executor.shutdown();
145. }
146.
147. /**
148. * 执行分发
149. */
150. publicvoid dispatcher() {
151. Thread thread = new Thread(this);
152. isRunning = true;
153. thread.start();
154. }
155. //一些get和set方法,在此省略
156. …
157. }
JobDispatcherStarter会调用jobDispatcher的dispatcher()方法执行分发,dispatcher()就是建一个thread来执行jobDispatcher,这样就触发了它的run()方法。Init()方法很明显就是在初始化bean时把异步任务的所有类型放到自己的list中,destroy()方法也很明显就是在销毁对象时将分发器运行状态标记改为停止。详细讲一下run()方法,首先新建一个线程池,新建时初始化一些参数。然后就是一个的while循环,只要没销毁对象就一直处于循环中。在循环里通过任务类型获取待执行的任务列表,每循环一个任务就计数一次,如果i还是0说明就是没有任务,就暂停1秒。有任务就根据任务类型获取执行器,然后在新建的任务工作线程类中调用执行器执行任务即可。任务工作线程类如下:
158. /**
159. * 任务工作者
160. */
161. public class ExecuteJobThread<T> implementsRunnable {
162. privatestatic final Logger LOG = LoggerFactory.getLogger("applyCenterJobLog");
163.
164.
165. /**处理job对象 */
166. privateT jobDO;
167. /**具体job处理类 */
168. privateJobHandler<T> jobHandler;
169.
170. publicExecuteJobThread(T jobDO, JobHandler<T> jobHandler) {
171. this.jobDO = jobDO;
172. this.jobHandler = jobHandler;
173. }
174.
175. publicvoid run() {
176. try {
177. jobHandler.doHandler(jobDO);
178. } catch (JobException e) {
179. LOG.warn(e.getMessage(), e);
180. } catch (Exception e) {
181. LOG.error("ExecuteJobThread处理异常," + jobDO.toString(),e);
182. } catch (Error e) {
183. LOG.error(e.getMessage(), e);
184. }
185. }
186.
187. }
这个线程类很明了,就是执行具体的任务,有异常就做对象的异常处理,一般这里必须捕获所有的异常,执行过程中的错没必要抛给任务分发器。
根据任务类型获取执行器是在jobHandlerFactory里做的,它里面建了一个放任务执行器的map,bean配置和getHandler方法如下:
188. <bean id="jobHandlerFactory" class="com.alifi.applycenter.job.JobHandlerFactoryImpl">
189. <propertyname="jobHandlerMap">
190. <map>
191. <entrykey="PUSH_LOAN_APPLY" value-ref="applyHandler" />
192. <entrykey="COMPANY_INFO" value-ref="companyInfoHandler" />
193. <entrykey="ADMISSION_CHECK" value-ref="admissionCheckHandler" />
194. <entrykey="SEND_MESSAGE" value-ref="msgSenderHandler" />
195. <entrykey="PUSH_CREDIT_MONITOR_APPLY" value-ref="creditMonitorApplyHandler"/>
196. <entrykey="CREDIT_APPLY_SCHEDULE" value-ref="scheduleHandler" />
197. <entrykey="QUARTZ_JOB" value-ref="scheduleHandler" />
198. <entrykey="APPLY_SYNC" value-ref="applySyncHandler" />
199. </map>
200. </property>
201. </bean>
getHandler()方法:
202. public JobHandler<JobDO>getHandler(JobDO jobDO) throws JobException {
203. return jobHandlerMap.get(jobDO.getJobType());
204. }
这样就根据任务名可以获取到执行器了。
然后调用线程池的execute()方法执行任务工作线程即可。
最后,当跳出while循环时要记得关闭线程池。
以上是我的一个定时推动异步任务的编程经历。