对很多系统而言,作业调度event, 'Scheduler');" target="_self">Scheduler是不可缺少的部分。大数据量集中批量处理、OLAP数据聚集都需要利用业务空闲时段(如夜间)进行处理。Oracle自身提供了较为可靠的运行作业调度器机制,为我们提供了现成的Scheduler组件。
调度作业有两种大类型:基于时间(Time-Based)和基于事件(Event-Based)。基于时间的调度作业顾名思义,就是设置特定的时间调度规则。依据时间规则在特定的时间点触发执行代码程序。例如:每天夜间22:00执行数据聚合操作,生成聚合数据。在Oracle中,大部分的作业都是这种类型。比如从10G出现的统计信息收集作业,就是规定在工作日夜间22:00开始进行的基于时间作业。
基于事件的作业调度则是依据特定的事件场景。比如:在应用程序发生故障的时候,启动数据清理程序,将中间数据结果还原。这样的作业,调度时间是不确定的,依据具体的业务和程序场景。而且,基于事件作业执行的过程中,要具有作业的特性,也就是作业执行代码执行和触发作业程序之间是异步执行关系。
在Oracle中,我们可以方便的时候dbms_scheduler包进行基于时间作业的定义。同样,我们可以借助Oracle消息队列的特性,来实现Event Based作业类型。
1、原理和环境准备
为了更好说明问题,笔者选择Oracle 10R2作为实验环境,并且构建一个新的用户schema环境。
SQL> show user;
User is "SYS"
SQL> select * from v$version;
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE 10.2.0.1.0 Production
TNS for 32-bit Windows: Version 10.2.0.1.0 - Production
NLSRTL Version 10.2.0.1.0–Production
创建一个新用户testuser,并授予相应的系统和角色权限。
SQL> create user testuser identified by testuser ;
User created
SQL> alter user testuser quota unlimited on users;
User altered
SQL> grant connect to testuser;
Grant succeeded
SQL> grant create table to testuser;
Grant succeeded
SQL> grant create sequence to testuser;
Grant succeeded
SQL> grant create type to testuser;
Grant succeeded
SQL> GRANTAQ_ADMINISTRATOR_ROLETO testuser;
Grant succeeded
SQL> GRANT CREATE JOB TO testuser;
Grant succeeded
只有设置了AQ_ADMINISTRATOR_ROLE角色,才能使用Oracle的Advanced Queue组件功能。
原理上:我们要利用Oracle的Advanced Queue组件的功能。要求在特定的Event发生时,我们需要向队列中传入一个标记对象。Scheduler会根据特定的标记对象标识来调用特定的作业Job代码程序。这样就实现了基本的Event Based Job。
2、日志插入作业
我们希望实现一个功能,就是在特定事件发生的时候,会向数据表中插入一条记录。
首先,我们准备代码数据表。
SQL> conn testuser/testuser@ots;
Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.1.0
Connected as testuser
SQL> create sequence seq_test;
Sequence created
SQL> create table test_log (id number not null, comments varchar2(100), created date);
Table created
SQL> alter table test_log add constraint pk_test primary key (id);
Table altered
我们创建了日志数据表。当触发事件的时候,直接向该数据表中插入一条记录。
3、配置调度作业
首先,需要定义一个类型type,用于向AQ中触发作业。该type相当于事件发生的信息单元。
SQL> create or replace type t_event_que_payload as object (event_name varchar2(30));
2 /
Type created
创建事件表,用来记录消息队列AQ中消息信息。
SQL> exec dbms_aqadm.create_queue_table(queue_table => 'event_queue_table',queue_payload_type => 't_event_que_payload',multiple_consumers => true,comment => 'Test Event Queue');
PL/SQL procedure successfully completed
使用dbms_aqadm方法create_queue_table中,两个最重要的参数:queue_table是创建消息表的名称,queue_payload_type则是规定了队列中存放对象的type类型。
执行后,的确创建了数据表event_queue_table。
SQL> desc event_queue_table;
Name Type Nullable DefaultComments
----------------- ------------------- -------- ------- --------
Q_NAME VARCHAR2(30) Y
MSGID RAW(16)
CORRID VARCHAR2(128) Y
PRIORITY NUMBER Y
(篇幅原因,有省略……)
下面需要创建队列对象,单独执行出队列名称和队列数据表名称。
SQL> exec dbms_aqadm.create_queue(queue_name => 'event_queue',queue_table => 'event_queue_table');
PL/SQL procedure successfully completed
SQL> select name, queue_table, qid, queue_type,user_comment from user_queues;
NAME QUEUE_TABLE QID QUEUE_TYPE USER_COMMENT
------------------------------ ------------------------------ ----------
EVENT_QUEUE EVENT_QUEUE_TABLE 111623 NORMAL_QUEUE
AQ$_EVENT_QUEUE_TABLE_E EVENT_QUEUE_TABLE 111622 EXCEPTION_QUEUE exception queue
注意,为了队列AQ,Oracle要创建出多个数据表,用于进行不同的消息存储。同时,处于性能等多方面的考量,很多这样的数据表是采用IOT(Index-Organized Table)结构的。
SQL> select table_name, tablespace_name, iot_name,iot_type from user_tables;
TABLE_NAME TABLESPACE_NAME IOT_NAME IOT_TYPE
------------------------------ ------------------------------ ----------------
TEST_LOG USERS
EVENT_QUEUE_TABLE USERS
AQ$_EVENT_QUEUE_TABLE_S USERS
SYS_IOT_OVER_111613 USERS AQ$_EVENT_QUEUE_TABLE_G IOT_OVERFLOW
AQ$_EVENT_QUEUE_TABLE_I IOT
AQ$_EVENT_QUEUE_TABLE_G IOT
AQ$_EVENT_QUEUE_TABLE_H IOT
AQ$_EVENT_QUEUE_TABLE_T IOT
8 rows selected
最后,启动创建出的AQ队列event_queue。
SQL> EXEC DBMS_AQADM.start_queue (queue_name => 'event_queue');
PL/SQL procedure successfully completed
注意,此时队列状态开启为可用。
SQL> select name, queue_table, qid, queue_type,ENQUEUE_ENABLED, DEQUEUE_ENABLED from user_queues;
NAME QUEUE_TABLE QID QUEUE_TYPE ENQUEUE_ENABLED DEQUEUE_ENABLED
----------------------- ------------------------------- --------------- ---------------
EVENT_QUEUE EVENT_QUEUE_TABLE 111623 NORMAL_QUEUE YES YES
AQ$_EVENT_QUEUE_TABLE_E EVENT_QUEUE_TABLE 111622 EXCEPTION_QUEUE NO NO
队列创建到此结束。下面创建作业,使用dbms_scheduler方法。
SQL> begin
2 dbms_scheduler.create_job(job_name => 'event_based_job',
3 job_type => 'PLSQL_BLOCK',
4 job_action => 'begin
5 insert into test_log values (seq_test.nextval, ''TT'', sysdate);
6 commit;
7 end;
8 ',
9 start_date=>systimestamp,
10 event_condition => 'tab.user_data.event_name = ''test_signal''',
11 queue_spec => 'event_queue',
12 enabled => TRUE);
13 end;
14 /
PL/SQL procedure successfully completed
SQL> select job_name, job_creator from user_scheduler_jobs;
JOB_NAME JOB_CREATOR
------------------------------ ------------------------------
EVENT_BASED_JOB TESTUSER
注意包方法的几个主要参数。Job_name定义了新增加job的名称。Job_action定义了当这个Job被触发的时候,需要执行哪段代码。Event_condition规定了调度数据对象值为’test_signal’的时候才会执行这个作业。Queue_spec制定了监视事件的队列名称。
4、测试作业情况
我们模拟向队列中插入事件消息对象的场景。
SQL> DECLARE
2 l_enqueue_options DBMS_AQ.enqueue_options_t;
3 l_message_properties DBMS_AQ.message_properties_t;
4 l_message_handle RAW(16);
5 l_queue_msg t_event_que_payload;
6 BEGIN
7 l_queue_msg :=t_event_que_payload('test_signal'); --创建事件event消息对象;
8
9 DBMS_AQ.enqueue(queue_name =>'event_queue',
10 enqueue_options => l_enqueue_options,
11 message_properties => l_message_properties,
12 payload =>l_queue_msg,
13 msgid => l_message_handle);
14 COMMIT;
15 END;
16 /
PL/SQL procedure successfully completed
在实际使用的时候,只需要向AQ队列中插入消息体。Oracle就可以根据消息的内容调用特定的作业。观察结果:
--作业效果;
SQL> select * from test_log;
ID COMMENTS CREATED
---------- ---------- -----------
1 TT 2012/1/29 1
--作业执行记录;
SQL> col job_name for a20;
SQL> select log_date, job_name, job_class, status from user_scheduler_job_log;
LOG_DATE JOB_NAME JOB_CLASS STATUS
-------------------- -------------------- ------------------------------ ----------
29-1月 -12 01.43.47. EVENT_BASED_JOB DEFAULT_JOB_CLASS SUCCEEDED
484000 下午 +08:00
5、结论
event_based作业类型,在实际中出现的机率并不是很高。主要是一些数据现场恢复和清理工作。使用Oracle的AQ和调度器机制,我们可以方便的将这种类型作业加以实现。