现在的位置: 首页 > 综合 > 正文

XPlatform Job机制设计分析

2012年04月02日 ⁄ 综合 ⁄ 共 4995字 ⁄ 字号 评论关闭

XPlatform  Job机制设计分析

AuthorWenchu.cenwc 岑文初

Date2007-5-10

Emailwenchu.cenwc@alibaba-inc.com 

Job机制概述... 2

XPlatform Job解构... 2

平台服务使用的补充... 5

Quartz的简单介绍... 5

Quartz和平台的Job的比较... 10

XPlatfrom 新任务机制设计与实现... 11

新任务机制设计... 11

接口... 11

实现... 11

测试... 12

关于Quartz的一点补充... 13

 


Job机制概述

XPlatform提供了比较不错的Job机制,由于早期没有仔细分析内部的设计,所以一直没有对Job引起足够的重视,同时正好也看了Quartz的这个设计,最后将会把这两种Job机制设计做一个对比,当然都各有各的优点,也有各自的不足,在后面的时间中如果能够将两者优点结合起来,那无疑将会把XPlatform的任务机制设计的更完善和灵活。(异步执行的job部分尚未研究)

XPlatform Job解构

一.数据库设计

XPlatformJob实现是基于数据库存储来实现的,因此先介绍一下它的表结构设计,同时也是Job对象的设计。

一共是四张表xp_runtime_data, xp_job_sandbox, xp_recurrence_info, xp_recurrence_rule


1 数据库表结构图

xp_runtime_data是用来存储任务内容部分(类似于QuartzJob的含义)。

xp_recurrence_rule是用来配置fire job的规则(类似于Quartztrigger,不过更确切的说要加上后面提到的xp_recurrence_info才算是完整的trigger),里面包含的信息为频率,间隔,次数,结束时间。

xp_recurrence_info也是配置fire job的一部分信息以及一些与rule配合信息,例如recurrence_count字段就是用来标注当前的job已经被执行了多少次了。

xp_job_sandbox就是要执行的具体的任务记录表,表中记录了开始时间,job名称,服务名称,前面几张表的id。这张表中的信息每一条就代表一条被执行的任务,有些已经被执行了有些还未被执行,都通过表中的字段可以区分出来,后面涉及到流程的时候将会有比较详细的说明。

 

二.类结构关系

2 类结构关系图

 

JobManager:负责管理和维护所有的定制任务,任务内容从数据库中获得。

JobPoller:负责管理维护任务处理线程池,任务执行列表。

JobInvoker:负责获取任务并且执行任务。

 

3 任务后台执行的基本流程

 

需要注意的:

首先JobPooler内部有两个linkedList,一个用来作为任务执行线程池,另一个用来作为可执行任务列表。

配置任务执行线程池是配置model/components/service/config目录下的serviceengine.xml文件。以下说明一下参数的含义:红色的表示我们都没有使用的,其实很有用。

<thread-pool ttl="18000000"

                 wait-millis="750"

                 jobs="2"

                 min-threads="5"

                 max-threads="15"

                 poll-enabled="true"

                 poll-db-millis="20000"/>

ttl="18000000"//线程池中线程的生命周期,到了时间就会自动被结束。(毫秒单位)

jobs="2"//每个线程负责的job数量,根据这个参数会动态的扩展线程池当时的线程数量

min-threads="5"//最小线程数,初始化就在池中放入这些线程

max-threads="15"//最大线程数

poll-enabled="true"//是否允许线程池

poll-db-millis="20000"//JobPooler运行时的间隔时间(毫秒单位)

wait-millis="750"//是每个JobInvoker线程运行的间隔时间(毫秒单位)

failed-retry-min//失败后下一次执行的时间间隔,也就是当前执行的时间点+failed-retry-min为下一次执行的时间。(这个参数如果不填,默认30分钟)

run-from-pool//这个不能作为属性,要作为子element来配置,如果配置了多个,那么在执行查询符合条件可以执行的job的时候就需要在附加jobpoolId必须包含在这个列表中。十分适合分布式任务的处理。

 

JobManager从数据库中获取符合条件可以执行的任务,条件依据如下:

runTime小于等于当前时间,startDateTime is null ,cancelDateTime is null以及前面提到的run-from-pool参数配置情况。

 

平台服务使用的补充

这次查找问题的时候发现了一个很严重的问题,由于平台早期没有什么文档参考,业务配置都是参看老的配置范例,那么导致对于服务配置的错误使用。

Service会配置在每个模块自己的service目录下面,每个配置文件都会有多个service的配置。那么service的属性究竟有多少个呢,具体怎么使用呢,我这边重要的强调两个参数,是现在配置文件中多没有使用的。

平台的事务使用的是JTA事务,一方面考虑原来可能是不同的数据源,另外这里也可以处理一些非JDBC事务的情况。但是平台对于服务的事务配置十分灵活,不是对所有的事务统一一种配置,而是自己可以在服务定义的属性中自己配置。属性名称分别为:use-transactiontransaction-timout,前者是是否需要使用事务,后者是是否配置事务超时时间。

举一下例子,在我们的日程提醒的邮件部分,有一个邮件服务定义,里面这两项都没有配置。

那么后台将会怎么处理呢,首先获取数据库连接处理一些信息获取操作以及预先更新操作,然后调用发送邮件服务函数,最后在结束数据库处理,事务结束,如果中间出现异常,那么就回滚整个事务。当没有配置是否需要事务参与以及事务超时时间的话,那么默认就是需要事务处理,超时时间就是0,也就是无超时,当发送邮件时间无限拉长的话,那么首先就会死等,然后数据库连接就被占用,在平台数据库资源我想大家都知道十分宝贵,那么这样的情况发生,数据库连接资源耗尽,就很容易发生。

因此,需要说明的就是,首先如果服务中没有必要配置事务的情况,就不要配置事务,把use-transaction=”false”,特别是中间有些对外的操作或者十分耗时的操作,就算要使用事务,那么请一定要设置超时时间(单位为秒)。也就是说平台不允许在需要事务的情况下不配置超时时间。这点十分重要,需要引起所有的开发人员以及配置人员注意。

 

Quartz的简单介绍

Quartz是开源的一个项目,现在广泛用于各个项目的任务机制实现中。

 

下面是个人的初步学习的内容,有很多不一定正确后续需要进一步实践验证。

 

Quartz的类图

4 Quartz的类图

       Quartz内部主要就是这些类:

       Job接口只有一个方法需要实现,其中JobExecutionContext可以作为运行时参数传递的上下文。用户自己定义自己的JobImpl就可以了。

       JobDetail类用来保存Job的状态信息(Job的名称,Job所属组,Job实际调用的实现类)。

       Trigger配置激发Job运行的参数。

       Schedule就是将JobJob关联的Trigger放入到执行队列中,然后通过线程池内的线程动态处理Job

       其实在很多设计中可以将JobDetailTrigger合在一起,但是为了更好的重用Job定义以及灵活配置,将两者分开是理所当然的。

 

Quartz 结构是模块化的,如果要使它正常运行需要以下各个模块配合工作:

a.ThreadPool

b.JobStore

c.DataSources (if necessary)

d.The Scheduler itself

 

ThreadPool的介绍

       ThreadPool提供了线程池来执行任务。有些用户一般设置为5个可以应付100个左右的任务,但不是同时并发的情况下。任务一般情况下都是耗时比较短。还有用户设置10,15,50主要是根据各自的情况不同配置。如果线程不够,那么Quartz就会被阻塞,这就可能造成misfiremisfire可以配置策略来处理后续问题。

 

JobStore的介绍

       在上面提到的这些类的背后,其实还有很重要的一个内容,就是JobStoreJobStore负责保存所有关于scheduler的工作数据:job,triggers,calendars等。选择一个适合的JobStore来配置Scheduler十分重要。在前面提到过平台现在的任务机制是基于数据库方式的,而Quartz却是可以分为两种方式内存方式和数据库方式。

       RAMJobStore是最简单的JobStore,效率最高,他将所有信息放在RAM中,缺点就是信息会丢失。

JDBCJobStore是将所有信息都放入到数据库中。数据库中需要建好索引来提高效率。首先需要创建一系列的表,可以在docs/dbTables下面找到一些数据库脚本,所有的表都是QRTZ_开头,前缀可以替换,只需要配置文件中配置即可。需要配置一下事务处理的类型(org.quartz.impl.jdbcjobstore.JobStoreTX or org.quartz.impl.jdbcjobstore.JobStoreCMT)。配置数据源的范例文件在docs/config下面。主要是下面几个配置要注意:

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegateoracle也有自己的delegate//数据库驱动代理类

org.quartz.jobStore.tablePrefix = QRTZ_   //数据库表前缀

org.quartz.jobStore.dataSource = myDS    //数据源的配置

如果你的Scheduler十分忙,基本上总是线程池满负荷,那么将数据库连接数设置为线程池大小+1

       可以设置参数org.quartz.jobStore.usePropertiestrue,默认是false,如果JobDataMaps里面的所有的值都是字符串,那么就会以name-value对被保存在数据库中,不用序列化以后保存到Blob字段中,极大地提高效率。

 

需要说明的几点:

1.  Schedule中每一次如果符合了Job被激发的情况时,后台将会根据JobDetail提供的类名,生成一个类实例,然后执行这个Job实现类的默认执行方法,因此需要这个类起码要提供一个无参数的构造函数。

2.  JobDataMap,它分别在TriggerjobDetail两个对象中都有,都可以put参数,然后到job运行期的时候获取,两者的差别很简单一个是关联到这个Trigger的,一个是关联到jobDetail的。 jobDetail.getJobDataMap().put("jobSays", "Hello World!");可以通过这种方式将需要传入的参数放置到JobDetailJobDataMap当中,然后通过JobExecutionContext可以获得jobDetailJobDataMap,这样就可以在运行过程中获得传入的参数。(如果你使用持久化的JobStore的话,就需要注意存储在JobDataMap中的内容,因为存储进去的内容将会被序列化,这可能就会出现class的版本问题。)

3.  Job的实例可以分为statefulnon-stateful两类。无状态的job只有在被加入到sched

抱歉!评论已关闭.