pl / sql使用触发器收集队列中的排队消息

时间:2014-05-26 14:40:59

标签: oracle triggers queue

我正在努力改变现有的排队系统......这让我很头疼......

队列背后的逻辑全部在Oracle DB中,如下所示:

  1. 数据库#1发送通知,这些通知在数据库#2中排队到特定队列中,我们将其命名为Q1。
  2. 有一个常规作业,它在包(DISPATCHER)中执行一个过程,它基本上是一个循环,它将Q1中的所有消息出列,直到没有消息存在。它的执行时间是每5分钟安排一次,但是队列条目的运行时间要长于它。
  3. Dispatcher程序将消息分发到其他队列,Java从中处理消息并进一步处理。
  4. 对调度JOB没有太多控制权,因此我决定寻找更好地调度工作的整个框架的方法。我必须添加它是一个旧的实现,可以工作很长一段时间,但大多数都不容易控制或透明。

    我的观点是,这个调度过程有没有安全的方法?

    1. 我在队列表上创建了一个触发器,它执行单个队列,并且在DISPATCHER中完成了所有消息的处理,但它确实有效,但我不知道这个想法是否最好。
    2. 查找创建订阅者,但无法添加任何订阅者,因为它是单个使用者队列。考虑到我必须更新框架,重新创建队列将删除已经在队列中的所有消息,这是我想要省略的...
    3. 尝试使用DBMS Scheduler作业来执行DISPATCHER以获得更多控制权,但它只是在“有资源时”运行。我指望一个能够持续工作并尽可能快地处理消息的解决方案。
    4. 也许有更复杂的方法来解决这个问题,但我仍然有点新鲜,似乎无法掌握整个高级排队机制......
    5. 提前致谢。

1 个答案:

答案 0 :(得分:1)

考虑到不断运行的工作,可以通过DBMS_PIPE发送消息来控制程序执行。
控制代码通过dbms_pipe发送stop消息 Dispatcher代码在DBMS_SCHEDULER作业中不断运行,并检查stop消息是否到达管道 也可以在一些超时后强制中断DBMS_SHCEDULER作业。

多年前,我已经实现了这样的逻辑,这个解决方案一直运行到现在。下面的代码是一个改编的样本,可以帮助您检查这种方法是否可以接受并适用于您的案例。

控制包标题:

create or replace package DispatcherControl is
  -- Package to control job state      

  procedure StartDispatcher;

  procedure StopDispatcher;

end;

控制包体:

create or replace package body DispatcherControl is

  -- User who owns dispatcher job in DBMS_SCHEDULER
  C_DISPATCH_PROGRAM_OWNER    constant varchar2(100) := 'DISPATCH_USER';
  -- Main procedure to handle dispatcher requests
  C_DISPATCH_JOB_PROC         constant varchar2(100) := 'DISPATCH_USER.DISPATCHER.MAINPROC';
  -- Name for dispatch program
  C_DISPATCH_PROGRAM_NAME     constant varchar2(100) := 'DISPATCH_PROGRAM';
  -- Description for dispatch program
  C_DISPATCH_PROGRAM_COMMENT  constant varchar2(100) := 'Q1 dispatch task';
  -- Dispatcher job name.
  C_DISPATCH_JOB_NAME         constant varchar2(100) := 'DISPATCH_JOB';
  -- Description for dispatcher job
  C_DISPATCH_JOB_COMMENT          constant varchar2(100) := 'Q1 dispatch process';
  -- Pipe name for dispatch job control.
  C_DISPATCH_PIPE_NAME        constant varchar2(100) := 'DISPATCH_CONTROL';
  -- Message text for pipe to stop dispatch job
  C_PIPE_STOP_MSG constant varchar2(100) := 'stop_dispatch';

  -- Check if DBMS_SCHEDULER program exists and create it if needed
  procedure CheckDispatcherJobExists
  is
  begin

    -- Return if program exists
    for cDummy in (
      select 1
      from all_scheduler_programs
      where
        owner = C_DISPATCH_PROGRAM_OWNER and program_name = C_DISPATCH_PROGRAM_NAME
        and
        rownum = 1
    ) loop
      return;
    end loop;

    -- Create disabled if not found
    sys.dbms_scheduler.create_program(
      program_name        => C_DISPATCH_PROGRAM_OWNER || '.' || C_DISPATCH_PROGRAM_NAME,
      program_type        => 'STORED_PROCEDURE',
      program_action      => C_DISPATCH_JOB_PROC,
      number_of_arguments => 0,
      enabled             => false,
      comments            => C_DISPATCH_PROGRAM_COMMENT
    );

    -- Enable program
    sys.dbms_scheduler.enable(C_DISPATCH_PROGRAM_OWNER || '.' || C_DISPATCH_PROGRAM_NAME);

  end;


  -- Check status of dispatcher job and run it if not found
  procedure CheckDispatcherJobState
  is
  begin

    CheckDIspatcherJobExists;

    -- Check if job is currently running according to scheduler info
    for cDummy in (
      select * from all_scheduler_jobs
      where
        owner = C_DISPATCH_PROGRAM_OWNER
    and
    job_name = C_DISPATCH_JOB_NAME
    ) loop

      -- Job found, check if running
      for cDummy2 in (
        select * from all_scheduler_running_jobs
    where
    owner = C_CIS_PROGRAM_OWNER  and job_name = C_DISPATCH_JOB_NAME
      ) loop

         -- Check if job really running by checking sessions list
        for cDummy3 in (
          select 1
          from
            all_scheduler_running_jobs jobs,
            sys.v_$session             sessions
          where
            jobs.owner = C_DISPATCH_PROGRAM_OWNER  and jobs.job_name = C_DISPATCH_JOB_NAME
            and
            sessions.sid = jobs.session_id and sessions.process = jobs.slave_os_process_id
            and
            sessions.action = jobs.job_name and sessions.username = jobs.owner
            and
            sessions.module = 'DBMS_SCHEDULER'
        ) loop

           -- Ok, return
           return;

        end loop;

        -- No process found for running job, stop and delete task before recreation
        sys.dbms_scheduler.stop_job(C_DISPATCH_JOB_NAME, true);

      end loop;

      -- process found, but restart needed
      sys.dbms_scheduler.drop_job(C_DISPATCH_JOB_NAME, true);

    end loop;

    -- Create one-time running job with manual start
    sys.dbms_scheduler.create_job(
      job_name        => C_DISPATCH_JOB_NAME,
      program_name    => C_DISPATCH_PROGRAM_OWNER || '.' || C_DISPATCH_PROGRAM_NAME,
      enabled         => true,
      auto_drop       => true,
      start_date      => add_months(sysdate,1000),
      repeat_interval => null,
      end_date        => null,
      comments        => C_DISPATCH_JOB_COMMENT
    );

    -- Run created task
    sys.dbms_scheduler.run_job(
      job_name            => C_DISPATCH_JOB_NAME,
      use_current_session => false
    );

  end;


  -- Stop and drop dispatch job.
  procedure DropDispatchJob
  is
    vSendRC integer;
  begin

    -- Send request through DBMS_PIPE and wait for timeout.
    sys.dbms_pipe.reset_buffer;
    sys.dbms_pipe.pack_message(C_PIPE_STOP_MSG);
    vSendRC := sys.dbms_pipe.send_message(C_DISPATCH_PIPE_NAME,1);
    if(vSendRC = 0) then
      -- wait if sent Ok
      sys.dbms_lock.sleep(0.25);
    end if;

    -- force job stop (not done in case of drop_job)
    for cDummy in (
      select 1 from all_scheduler_running_jobs
      where
        owner = C_DISPATCH_PROGRAM_OWNER
        and
        job_name = C_DISPATCH_JOB_NAME
    ) loop

      begin
        sys.dbms_scheduler.stop_job(C_DISPATCH_JOB_NAME, true);
      exception
        when others then begin
          -- If sqlcode = -27366 then task finished, in other case it's unexpected
          if(SQLCODE != -27366) then
            raise;
          end if;
        end;
      end;

    end loop;

    -- delete job if exists, allow stopping
    for cDummy in (
      select 1 from all_scheduler_jobs
      where
        owner = C_DISPATCH_PROGRAM_OWNER
        and
        job_name = C_DISPATCH_JOB_NAME
    ) loop

      sys.dbms_scheduler.drop_job(C_DISPATCH_JOB_NAME, true);

    end loop;

    -- drop program if exists
    for cDummy in (
      select 1 from all_scheduler_programs
        where
          owner = C_DISPATCH_PROGRAM_OWNER
          and
          program_name = C_DISPATCH_PROGRAM_NAME
    ) loop

      sys.dbms_scheduler.drop_program(C_DISPATCH_PROGRAM_NAME, true);

    end loop;

    -- Clear DBMS_PIPE messages after job stop (cover case of forced stop).
    sys.dbms_pipe.purge(C_DISPATCH_PIPE_NAME);

  end;

  -- Start dispatcher process
  procedure StartDispatcher
  is
  begin

    -- Clear message queue before starting
    sys.dbms_pipe.purge(C_DISPATCH_PIPE_NAME);

    CheckDispatchJobState;

  end;

  -- Stop dispatcher process
  procedure StopDispatcher
  is
  begin
    DropDispatchJob;
  end;

end;

Dispatcher包标题:

create or replace package DISPATCH_USER.Dispatcher is

  -- Main queue check procedure to run from job.
  procedure MainProc;

end;

Dispatcher包体:

create or replace package body DISPATCH_USER.Dispatcher is

  -- Normal wait time in seconds
  NORMAL_CHECK_INTERVAL constant number := 0.01;

  -- Checks if stop message received.
  function CheckIsStopped return boolean
  is
   vPipeRC integer;
   vMsg    varchar2(1024);
  begin
    -- check if message exists
    vPipeRC := sys.dbms_pipe.receive_message('DISPATCH_CONTROL',0);
    if(vPipeRC = 0) then
      -- check type of message content, must be varchar2 (look for constants in sys.dbms_pipe package).
      if(sys.dbms_pipe.next_item_type = 9) then

        -- check message content
        sys.dbms_pipe.unpack_message(vMsg);
        if(vMsg = 'stop') then
          return true;
        end if;

      end if;

    end if;

    return false;

  end;


  -- Checks if error caused by external interrupt
  function IsInterruptError(piSQLCode in number) return boolean
  is
  begin

    if( piSQLCode in (

          -1013,  -- ORA-01013: User requested cancel of current operation
          -28,    -- ORA-00028: Your session has been killed
          -13638, -- ORA-13638: The user interrupted the current operation
          -13639, -- ORA-13639: The current operation was interrupted because it timed out.
          -13668, -- ORA-13668: The current operation was aborted because it was blocking another session
          -48223, -- ORA-48223: Interrupt Requested - Fetch Aborted - Return Code [string] [string]
          -48495  -- ORA-48495: Interrupt requested

        )
      ) then

      return true;

    end if;

    return false;

  end;

  -- Main procedure for dispatcher job
  procedure MainProc
  is
    vIsFound       boolean;
    vSQLCode       number;
    vErrMsg        varchar2(2048);
    vCheckInterval number;
    vMESSAGE       SOME_CUSTOM_MESSAGE_DATA_TYPE;  -- Just for example
  begin

    vCheckInterval := NORMAL_CHECK_INTERVAL;

    while(true) loop

      vIsFound := false;

      begin

        vMESSAGE := GET_NEXT_MESSAGE_FROM_QUEUE; -- Just for example

        if(vMESSAGE is not null) then

          vIsFound := true;

          -- Process received message her
          DISPATCH_MESSAGE(vMESSAGE);  -- Just for example

          -- Commit changes to save a redo log from overflow.
          commit;

        end if;

      exception
        when others then begin

          vSQLCode := SQLCODE;
          vErrMsg := SQLERRM;

          if( IsInterruptError(vSQLCode) ) then
            -- Promote error if interrupted forcibly
            raise;
          end if;

          -- In other cases just write error conditions to log and continue.
          -- Log writing totally skipped from this example, so only comment here.

          -- Also it's a place to perform extra error analysis. 
          -- E.g. if it is some temporary error caused by remote database shutdown
          -- and so on, then increase vCheckInterval and don't stress server.

        end;
      end;

      -- Check if interrupted programmatically from control procedure
      if( CheckIsStopped ) then
        -- normal exit, job finished
        return;
      end if;

      -- If there are no new request then put process in sleep state
      -- for a short time to release resources
      if(not vIsFound) then
        dbms_lock.sleep(vCheckInterval);
      end if;

    end loop;

    -- This point never reached
    null;

  end;

end;

请注意,上面的代码是一个简化的示例(例如,没有记录执行的活动)。此外,采用示例代码来匹配问题条件,并包含一些假设,如Oracle用户名和GET_NEXT_MESSAGE_FROM_QUEUE等泛型。请随意询问示例代码中是否有不明确的内容。