`
cenwenchu
  • 浏览: 161229 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

XPlatform Job机制设计分析

阅读更多

XPlatform  Job机制设计分析<o:p></o:p>

AuthorWenchu.cenwc 岑文初<o:p></o:p>

Date<st1:chsdate isrocdate="False" month="5" islunardate="False" day="10" w:st="on" year="2007" style="BACKGROUND-POSITION: left bottom; BACKGROUND-IMAGE: url(res://ietag.dll/#34/#1001); BACKGROUND-REPEAT: repeat-x">2007-5-10</st1:chsdate><o:p></o:p>

Emailwenchu.cenwc@alibaba-inc.com  <o:p></o:p>

Job机制概述... 2<o:p></o:p>

XPlatform Job解构... 2<o:p></o:p>

平台服务使用的补充... 5<o:p></o:p>

Quartz的简单介绍... 5<o:p></o:p>

Quartz和平台的Job的比较... 10<o:p></o:p>

XPlatfrom 新任务机制设计与实现... 11<o:p></o:p>

新任务机制设计... 11<o:p></o:p>

接口... 11<o:p></o:p>

实现... 11<o:p></o:p>

测试... 12<o:p></o:p>

关于Quartz的一点补充... 13<o:p></o:p>

<o:p> </o:p>


Job机制概述<o:p></o:p>

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

XPlatform Job解构<o:p></o:p>

一.数据库设计

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

一共是四张表xp_runtime_data, xp_job_sandbox, xp_recurrence_info, xp_recurrence_rule<o:p></o:p>

<v:shapetype o:spt="75" coordsize="21600,21600" filled="f" stroked="f" id="_x0000_t75" path=" m@4@5 l@4@11@9@11@9@5 xe" o:preferrelative="t"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0 "></v:f><v:f eqn="sum @0 1 0 "></v:f><v:f eqn="sum 0 0 @1 "></v:f><v:f eqn="prod @2 1 2 "></v:f><v:f eqn="prod @3 21600 pixelWidth "></v:f><v:f eqn="prod @3 21600 pixelHeight "></v:f><v:f eqn="sum @0 0 1 "></v:f><v:f eqn="prod @6 1 2 "></v:f><v:f eqn="prod @7 21600 pixelWidth "></v:f><v:f eqn="sum @8 21600 0 "></v:f><v:f eqn="prod @7 21600 pixelHeight "></v:f><v:f eqn="sum @10 21600 0 "></v:f></v:formulas><v:path o:extrusionok="f" o:connecttype="rect" gradientshapeok="t"></v:path><o:lock v:ext="edit" aspectratio="t"></o:lock></v:shapetype><v:shape coordsize="21600,21600" id="_x0000_i1025" type="#_x0000_t75" style="WIDTH: 5in; HEIGHT: 270pt"><v:imagedata src="A8D73532.files/image001.png" o:title="cwc"></v:imagedata></v:shape><o:p></o:p>

1 数据库表结构图<o:p></o:p>

xp_runtime_data是用来存储任务内容部分(类似于QuartzJob的含义)。<o:p></o:p>

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

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

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

<o:p> </o:p>

二.类结构关系<o:p></o:p>

<v:shape coordsize="21600,21600" id="_x0000_i1026" type="#_x0000_t75" style="WIDTH: 414.75pt; HEIGHT: 270.75pt"><v:imagedata src="A8D73532.files/image003.emz" o:title=""></v:imagedata></v:shape><o:p></o:p>

2 类结构关系图<o:p></o:p>

<o:p> </o:p>

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

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

JobInvoker:负责获取任务并且执行任务。<o:p></o:p>

<o:p> </o:p>

<v:shape coordsize="21600,21600" id="_x0000_i1027" type="#_x0000_t75" style="WIDTH: 415.5pt; HEIGHT: 315pt"><v:imagedata src="A8D73532.files/image005.emz" o:title=""></v:imagedata></v:shape><o:p></o:p>

3 任务后台执行的基本流程<o:p></o:p>

<o:p> </o:p>

需要注意的:<o:p></o:p>

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

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

<thread-pool ttl="18000000"<o:p></o:p>

                 wait-millis="750"<o:p></o:p>

                 jobs="2"<o:p></o:p>

                 min-threads="5"<o:p></o:p>

                 max-threads="15"<o:p></o:p>

                 poll-enabled="true"<o:p></o:p>

                 poll-db-millis="20000"/><o:p></o:p>

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

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

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

max-threads="15"//最大线程数<o:p></o:p>

poll-enabled="true"//是否允许线程池<o:p></o:p>

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

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

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

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

<o:p> </o:p>

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

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

<o:p> </o:p>

平台服务使用的补充<o:p></o:p>

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

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

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

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

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

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

<o:p> </o:p>

Quartz的简单介绍<o:p></o:p>

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

<o:p> </o:p>

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

<o:p> </o:p>

Quartz的类图

<v:shape coordsize="21600,21600" id="_x0000_i1028" type="#_x0000_t75" style="WIDTH: 409.5pt; HEIGHT: 212.25pt"><v:imagedata src="A8D73532.files/image007.emz" o:title=""></v:imagedata></v:shape>

4 Quartz的类图

       Quartz内部主要就是这些类:

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

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

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

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

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

<o:p> </o:p>

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

a.ThreadPool

b.JobStore

c.DataSources (if necessary)

d.The Scheduler itself

<o:p> </o:p>

ThreadPool的介绍<o:p></o:p>

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

<o:p> </o:p>

JobStore的介绍<o:p></o:p>

       在上面提到的这些类的背后,其实还有很重要的一个内容,就是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字段中,极大地提高效率。

<o:p> </o:p>

需要说明的几点:

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

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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics