定时任务发展史(一)

定时任务是互联网行业里最常用的服务之一,本文给大家介绍定时任务在我司的发展历程。

linux系统中一般使用crontab命令来实现,在Java世界里,使用最广泛的就是quartz了。我司使用quartz就已经升级了三代,每一代在上一代系统之上有所优化,写这篇文章一方面介绍一下quartz的使用,另一方面可以根据此项目的变迁反应出我司平台架构升级的一个缩影。

定时任务的使用场景很多,以我们平台来讲:计息,派息、对账等等。

quartz 介绍
Quartz是个开源的作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制。Quartz允许开发人员根据时间间隔(或天)来调度作业。它实现了作业和触发器的多对多关系,还能把多个作业与不同的触发器关联。Quartz可以集成几乎任何的java应用程序—从小的单片机系统到大型的电子商务系统。Quartz可以执行上千上万的任务调度。

Quartz核心的概念:scheduler任务调度、Job任务、JobDetail任务细节、Trigger触发器

  • Scheduler:调度器,调度器接受一组JobDetail+Trigger即可安排一个任务,其中一个JobDetail可以关联多个Trigger
  • Job:Job是任务执行的流程,是一个类
  • JobDetail:JobDetail是Job是实例,是一个对象,包含了该实例的执行计划和所需要的数据
  • Trigger:Trigger是定时器,决定任务何时执行

使用Quartz调度系统的思路就是,首先写一个具体的任务(job),配置任务的触发时间(Trigger),Scheduler很根据JobDetail+Trigger安排去执行此任务。

Quartz 定时器的时间设置

时间的配置如下:0 30 16 ?

时间大小由小到大排列,从秒开始,顺序为 秒,分,时,天,月,年 *为任意 ?为无限制。由此上面所配置的内容就是,在每天的16点30分启动buildSendHtml() 方法

具体时间设定可参考 :

“0/10 ?” 每10秒触发
“0 0 12 ?” 每天中午12点触发 “0 14 * ?” 在每天下午2点到下午2:59期间的每1分钟触发
“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44触发
“0 15 10 ? * MON-FRI” 周一至周五的上午10:15触发
“0 0 06,18 ?” 在每天上午6点和下午6点触发

第一代定时任务系统
第一代定时任务系统使用的很简单,全部按照当时spring推荐的配置方式来进行,开发于2014年初。

首先在配置线程池

<bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="50" />
    <property name="maxPoolSize" value="100" />
    <property name="queueCapacity" value="500" />
</bean>

配置定时任务工厂和任务基类

<bean id="timerFactory" class="com.zx.timer.TimerFactory" />

<bean id="baseTask" class="com.zx.timer.core.BaseTask">
    <property name="machineId" value="${machine.id}"/>
    <property name="recordErrorDetail" value="${is.record.errordetail}"/>
</bean>
  • machineId:机器编码
  • recordErrorDetail:是否记录详细日志

通过timerFactory 来获取具体的任务和触发器

public class TimerFactory implements BeanFactoryAware {

    private BeanFactory beanFactory;

    public Object getTask(String taskCode) {
        return beanFactory.getBean(taskCode+"Task");
    }

    public Object getTrigger(String taskCode) {
        return beanFactory.getBean(taskCode+"Trigger");
    }

    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public BeanFactory getBeanFactory() {
        return beanFactory;
    }
}

baseTask集成了task,在里面做了一些基础的业务,比如定时任务开始执行的时候记录定时任务的开始执行时间,定时任务结束的时候记录执行的结果等。

public interface Task {
    public void executeTask();
}

配置具体的定时任务。以重发短信邮件的定时任务为例

<bean id="resendSmsAndEmailTask" class="com.zx.timer.core.tasks.ResendSmsAndEmailTask"
        parent="baseTask">
</bean>

<bean id="resendSmsAndEmailJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="resendSmsAndEmailTask" />
    <property name="targetMethod" value="executeTask" />
    <property name="concurrent" value="false" />
</bean>

<bean id="resendSmsAndEmailTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail" ref="resendSmsAndEmailJob" />
    <property name="cronExpression">
        <value>0 0 0   ?</value>
    </property>
</bean>
  • resendSmsAndEmailTask:具体的定时任务类
  • resendSmsAndEmailJob:包装成具体的Job
  • resendSmsAndEmailTrigger:设置具体执行的时间,包装成Trigger

具体的task类,删掉了部分业务代码:

public class ResendSmsAndEmailTask extends BaseTask{
    private static final String TASK_CODE = "resendSmsAndEmail";
    AtomicInteger ai = new AtomicInteger(0);

    public void execute(){
        try {
            ai = new AtomicInteger(0);
            // todo
        }catch (Exception e) {
            String exception = ExceptionUtils.getStackTrace(e);
            logger.error("stat error with exception[{}].", exception);
            this.recordTaskErrorDetail(this.taskRecordId, "ResendSmsAndEmailTask-" + e.getMessage(), exception);
        }finally{
            this.modifyTaskRecord(ai.get(), taskRecordId);
        }
    }

    public String getTaskNo() {
        return TASK_CODE;
    }
}

最后配置scheduler任务调度

<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="resendSmsAndEmailTrigger" />
        </list>
    </property>
    <property name="taskExecutor" ref="executor" />
</bean>
<bean class="com.zx.timer.core.scheduler.DynamicJobAssembler" init-method="init" scope="singleton"/>

DynamicJobAssembler类代码:

public class DynamicJobAssembler {

    private static Logger logger = LoggerFactory.getLogger(DynamicJobAssembler.class);

    @Resource
    Scheduler scheduler;

    @Resource
    TimerFactory timerFactory;

    @Resource
    TaskDao taskDao;

    public void init() {
        logger.info("start to assemble task from db.");
        List<TaskEntity> tasks = this.taskDao.getAllTask();
        if (tasks == null || tasks.size() <= 0) {
            return;
        }

        Map<String, String> jobNameMap = this.getAllJobNames();
        for (TaskEntity task : tasks) {
            logger.debug(task.toString());
            CronTriggerBean taskTrigger = (CronTriggerBean) timerFactory.getTrigger(task.getTaskNo());
            if (taskTrigger != null) {
                if (!task.getSchedulerRule().equals(taskTrigger.getCronExpression())) {
                    try {
                        taskTrigger.setCronExpression(task.getSchedulerRule());
                    } catch (ParseException e) {
                        logger.error("db task's cronExpression parse error:{}", e.getMessage());
                    }
                    try {
                        logger.info("rescheduleJob jobName:{}",task.getTaskNo());
                        scheduler.rescheduleJob(task.getTaskNo() + "Trigger", Scheduler.DEFAULT_GROUP, taskTrigger);
                    } catch (SchedulerException e) {
                        logger.error("revieved task[{},{}] reschedule error:{}", task.getTaskNo(), task.getSchedulerRule(), e.getMessage());
                    }
                }
                jobNameMap.remove(task.getTaskNo() + "Job");
            }
        }

        if (jobNameMap != null) {
            logger.info("=====================================");
            logger.info("Jobs need to be removed:" + Arrays.toString(jobNameMap.keySet().toArray()));
            logger.info("=====================================");
            for (String jobName : jobNameMap.keySet()) {
                try {
                    scheduler.deleteJob(jobName, jobNameMap.get(jobName));
                } catch (SchedulerException e) {
                    logger.error("Error occured when deleting Job[{}] with Exception:{}", jobName, e.getMessage());
                }
            }
        }
        logger.info("end to assemble task from db.");
    }

    private Map<String, String> getAllJobNames() {
        Map<String, String> jobNameMap = new HashMap<String, String>();
        try {
            String[] groups = scheduler.getJobGroupNames();
            for (String group : groups) {
                String[] jobs = scheduler.getJobNames(group);
                if (jobs != null) {
                    for (String job : jobs) {
                        jobNameMap.put(job, group);
                    }
                }
            }
        } catch (SchedulerException e1) {
            logger.error("Failed in geting all job names with exception:{}", e1.getMessage());
        }
        return jobNameMap;
    }

}

定时任务表,执行的时候以表里面的数据为准,方便编辑。

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `zx_task_informations`
-- ----------------------------
DROP TABLE IF EXISTS `zx_task_informations`;
CREATE TABLE `zx_task_informations` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `version` int(11) NOT NULL COMMENT '版本号:需要乐观锁控制',
  `taskNo` varchar(64) NOT NULL COMMENT '任务编号',
  `taskName` varchar(64) NOT NULL COMMENT '任务名称',
  `schedulerRule` varchar(64) NOT NULL COMMENT '定时规则表达式',
  `frozenStatus` varchar(16) NOT NULL COMMENT '冻结状态',
  `executorNo` varchar(128) NOT NULL COMMENT '执行方',
  `timeKey` varchar(32) NOT NULL COMMENT '执行时间格式',
  `frozenTime` bigint(13) DEFAULT NULL COMMENT '冻结时间',
  `unfrozenTime` bigint(13) DEFAULT NULL COMMENT '解冻时间',
  `createTime` bigint(13) NOT NULL COMMENT '创建时间',
  `lastModifyTime` bigint(13) DEFAULT NULL COMMENT '最近修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8 COMMENT='定时任务信息表';

-- ----------------------------
-- Records of zx_task_informations
-- ----------------------------
INSERT INTO `zx_task_informations` VALUES ('1', '0', 'resendSmsAndEmail', '重发短信和邮件', '10 /10    ?', 'FROZEN', '0', 'yyyy-MM-dd HH:mm', '0', '0', '0', '1486807296009');

这就是我们第一代定时任务系统,达到了定期执行定时任务的效果,但是同样有两个缺点:

1、定时调度和业务代码耦合在一起
2、每次调整定时任务的时间需要重启服务

作者:纯洁的微笑
出处:http://www.ityouknow.com/

时间: 2024-09-17 20:20:14

定时任务发展史(一)的相关文章

定时任务发展史(二)

第一代定时任务系统上线用了大概半年之后,就被我们厌倦了.于是就规划了第二代定时任务系统.第二代定时任务系统 第二代调度系统主要解决的是,避免每次修改定时任务的执行时间都需要重新启动整个项目.另外也可支持单独重新调度单个定时任务. 我们做了一个请求入口,当更新了库表里面的数据之后,重新请求一下特定的url就会自动重新加载定时任务. 使用scheduler删除定时任务 public void reScheduler() throws Exception { // 取消现有的任务 String[] j

sql语句-mysql定时任务 写下SQL语句

问题描述 mysql定时任务 写下SQL语句 想让MYSQL里面的jpzh表里面的isstaus在晚上00定时更新 那个大侠 写下SQL语句 解决方案 CREATE EVENT e_testON SCHEDULE EVERY 1 DAYSTARTS '2014-12-04 00:00:00'DO UPDATE jpzh SET isstaus=.....; 从 2014-12-04 00:00:00 开始,每天做一次 解决方案二: 你的逻辑好像不太对,按常理开发模式,一般是从应用端控制时间,然后

全球第一大浏览器Chrome发展史

自2008年发布以来,Google旗下的Chrome浏览器给网络技术行业带来了一股创新浪潮.在过去四年中,浏览器领域内涌现出了一系列新技术,HTML5.CSS3等等.除了速度一流之外,Chrome对这些新技术是支持的最积极的一个,并且加速了整个行业的浏览器更新速度,这也是为什么Chrome能够如此快速成长的一个重要原因.四年之后的今天,Chrome已经成为了全球第一大浏览器,用户为整个产业的33.8%,那么这期间经历了怎样的发展呢?下面就通过时间轴一起来了解下Chrome的发展史以及这四年来整个

Java:Web应用下实现定时任务的简便方法

web|定时 在WEB应用下实现定时任务的简便方法 在web方式下,如果我们要实现定期执行某些任务的话,除了用quartz等第三方开源工具外,我们可以使用Timer和TimeTask来完成指定的定时任务: 第一步:创建一个任务管理类,实现ServletContextListener 接口 public class TaskManager implements ServletContextListener { /**  * 每天的毫秒数  */ public static final long P

Java知识:Web应用下实现定时任务简便方法

web|定时 在WEB应用下实现定时任务的简便方法 在web方式下,如果我们要实现定期执行某些任务的话,除了用quartz等第三方开源工具外,我们可以使用Timer和TimeTask来完成指定的定时任务: 第一步:创建一个任务管理类,实现ServletContextListener 接口 以下是引用片段: public class TaskManager implements ServletContextListener { /** * 每天的毫秒数 */ public static final

中国互联网广告发展史

广告|互联网  [追本溯源,网络广告发轫于1994年的美国.当年10月14日,美国著名的Wired杂志推出了网络版Hotwired(www.hotwired.com),其主页上开始有AT&T等14个客户的广告Banner.这是广告史上里程碑式的一个标志. 中国互联网广告发展史 中国的第一个商业性的网络广告出现在1997年3月,传播网站是Chinabyte,广告表现形式为468×60像素的动画旗帜广告.Intel和IBM是国内最早在互联网上投放广告的广告主.我国网络广告一直到1998年初才稍有规模

windows 2008定时任务调用bat不成功如何解决

之前一直有在一台XP的机器上调用定时任务,现在这台机器换成了window 2008的操作系统,调用一直不成功,不过在偶然之间解决了. 选择"任务计划程序" 任务计划程序库 点击创建基本任务 ,名称写上监控数据库,点击下一步 任务触发器  每天,点击下一步 每日填上相应的时间,点击下一步 操作,启动程序,点击下一步 启动程序,程序或脚本中选中需要执行的脚本,在"起始于(可选)"这里一定要填写相应执行程序或是BAT文件的所在目录,要不然是执行不成功的. 完成. 更多精彩

kernel学习之调度器发展史

调度器的主要工作是在所有 RUNNING 进程中选择最合适的一个.作为一个通用操作系统,Linux 调度器将进程分为了三类:             交互进程:此类进程有大量的人机交互,因此进程不断地处于睡眠状态,等待用户输入.典型的应用比如编辑器 vi.此类进程对系统响应时间要求比较高,否则用户会感觉系统反应迟缓. 批处理进程:此类进程不需要人机交互,在后台运行,需要占用大量的系统资源.但是能够忍受响应延迟.比如编译器. 实时进程:实时对调度延迟的要求最高,这些进程往往执行非常重要的操作,要求

java中quartz调度在一些定时任务(job)的入门级应用

Quartz 执行详解:http://quartz-scheduler.org/   去下载相应的jar包 在maven中可直接把依赖拷贝过来复制到pom中去. 具体规则可查询quartz的文档 下面是一个非常详细的实例: 1.首先把需要执行的任务写到execute中去 并实现job package job; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import or