定时器与系统时间(续)
额外问题处理:
--------------------------------------------------------------------------------
13) 定时器之外的一些处理
a) window环境下,定时器通知执行定时任务的时间点可能误差1毫秒;Linux环境也有类似情况,但是误差频率低很多(只是在指定时间点前后1毫秒误差,心跳步长不差)
可能的影响:
以每秒任务为例, 10:00:00.000 TaskA -> 10:00:00.999 TaskB -> 10:00:02.000 TaskC -> 10:00:03.001 TaskD
会导致第2个任务(TaskB)丢失, 因为步长是1秒钟 + 抹去毫秒计算
代码解决方案:
定时器初始时间设置为 XX:XX:XX:500,以两秒的中间段为起点开始心跳,这样误差1毫秒时候,抹去毫秒不影响正常定时器执行
Calendar cal = Calendar.getInstance(); int millis = cal.get(Calendar.MILLISECOND); int delay = (1500 - millis) % 1000; // 下个0.5秒开始运行(定时器偶尔会前后误差1毫秒) timerService.scheduleAtFixedRate(new Runnable() {..}, delay, TIMER_STEP, TimeUnit.MILLISECONDS);
b) 线程池启动,大概会延迟200毫秒后才开始心跳
没有多大不良影响
c) 心跳时间点未 XX:XX:XX:500, 正常情况下没有问题。
问题是每小时同步一次服务器系统时间,可能依然导致心跳时间点押到 XX:XX:XX:000 上,从而引起问题a),当然这个概率极低
可以考虑心跳时长为 100毫秒
d) 任务执行接口传入当前时间作为参数
定时器(心跳)线程,定时挑选符合执行条件的任务,扔到任务池去执行。
因为任务执行时间过长、任务锁、线程池资源不足等条件导致的任务内部log时间不符合要求是任务本身的事情,反正定时器通知你了,不用纠结定时器问题
e) 任务有唯一id,防止重复添加任务, 一次性任务没有Id
任务基类对id做了处理,加了统一前缀,方便log跟踪
定时任务优化:
--------------------------------------------------------------------------------
14) 现在定时器定时执行任务的功能已经完成:我在指定的时间通知你做指定的任务。不过总有一些奇怪的任务,用这样更简单的语言不方便表达。
比如,每隔10分钟统计一次在线人数。
当前接口可以处理,不过感觉非常笨拙:
regPerHourTask (0, 0, TaskA); regPerHourTask (10, 0, TaskA); regPerHourTask (20, 0, TaskA); regPerHourTask (30, 0, TaskA); regPerHourTask (40, 0, TaskA); regPerHourTask (50, 0, TaskA);
当然也可以尝试
regPerMinuteTask (0, new Task () { 每10次执行1次,忽略9次 });
于是抽象出一个固定分钟执行的任务,你告诉我你要几分钟执行一次,然后实现我的执行接口就是了
这样又产生了一个新的问题,比如,每隔5分钟统计一次在线人数。
2014-12-20 13:58:12,503 INFO 人数为N // 当前log是 2014-12-20 13:03:12,503 INFO 人数为N 2014-12-20 13:08:12,503 INFO 人数为N 2014-12-20 13:13:12,503 INFO 人数为N 2014-12-20 13:18:12,503 INFO 人数为N 2014-12-20 13:22:53,321 INFO 人数为N // 重启服务器后: 2014-12-20 13:27:53,321 INFO 人数为N 2014-12-20 13:32:53,321 INFO 人数为N 2014-12-20 13:37:53,321 INFO 人数为N
看起来有点小晕
解决方案:
固定分钟执行的任务, 初始增加一个 baseTime 参数, 时间计算依赖 baseTime 去选择执行或忽略
2014-12-20 13:00:00,000 INFO 人数为N // 当前log是 2014-12-20 13:05:00,000 INFO 人数为N 2014-12-20 13:10:00,000 INFO 人数为N 2014-12-20 13:15:00,000 INFO 人数为N 2014-12-20 13:20:00,000 INFO 人数为N 2014-12-20 13:30:00,000 INFO 人数为N // 重启服务器后 2014-12-20 13:35:00,000 INFO 人数为N 2014-12-20 13:40:00,000 INFO 人数为N 2014-12-20 13:45:00,000 INFO 人数为N
这样 grep 出来log对时间就舒服多了
定时任务优化(二):
--------------------------------------------------------------------------------
15) 先做了到点通知功能,后来额外加了定时通知功能;突然发现,这个只是个正常的任务功能,还有不正常的情况。
比如,我们需要远程请求加载数据,可能这数据相对重要一些,但是只要一次取对的数据就可以了,问题它可能失败。
换个描述方式就是,我需要去其它地方拿数据,直到拿到为止。当然现在的定时器也支持这个任务,尽管不友好:
注册一个每分钟任务,如果发现任务完成了,就移除。
但是实际问题可能更复杂一些:
如果任务执行失败,我就5秒后再执行一次;
如果再失败,我就30秒后执行一次;
如果再失败,就3分钟后执行;
如果再失败,就算了,不能无休止执行;也可能过了一定期限后,执行成功也没有意义
在自己的任务里可以自行处理类似逻辑,不过这也是可以抽象的,于是需要增加一个固执的任务,失败后休息一定时间再执行;
规定的失败次数(可尝试次数)用完后,没有完成也不再执行了。
这里的需要通知任务执行的时候,执行函数是有成功与否的返回值。
总结
--------------------------------------------------------------------------------
16) 总结
a) 定时器通知【可以执行的指定任务】了
b) 定时器【定时】通知【可以执行的指定任务】了
c) 定时器【多次】通知【需要执行成功的指定任务】了
d) 定时器顺便解决了服务器系统时间问题