如何设计任务分配系统?

时间:2015-06-21 09:47:10

标签: sql oracle system-design

我想设计一个类似于stackoverflow 审核 功能的系统。也就是说:

n个任务,应该分配给用户(用户数量未知)。同时,一个任务应该分配给最多一个用户,不应该为不同的用户分配相同的任务。

例如,n = 8,如果一个用户进入系统默认值,则为他分配3个任务。

  • 17:00,Tom进入系统并完成任务1,2,3。
  • 17:01,Jim进入系统并完成任务4,5,6。
  • 17:02,Jerry进入系统并完成任务7,8。
  • 在17:03,Bob进入系统并且没有任务。
  • 17:05,Tom完成了任务1,2,并离开了系统。
  • 在17:06,Bob再次进入系统并获得任务3。

假设我使用Database来存储任务信息。

我的解决方案是,当将任务1,2,3分配给Tom时,从DB中删除3条记录并将其存储到内存中。然后其他人将无法获得3条记录。当Tom离开系统时,将已完成的任务和未完成的任务再次插入DB(任务状态"已完成"或"未完成")。

虽然缺点是存储记录到内存不是100%安全,但如果系统崩溃可能导致数据丢失问题。

有人知道stackoverflow如何设计 审核 功能吗?或分享其他解决方案?我想知道SELECT ... FOR UPDATE在这个用例中是否有用。

1 个答案:

答案 0 :(得分:1)

您需要实现的是FIFO堆栈或简单队列。在Oracle中,最好的事情(除非你想用AQ实现一个实际的队列)就是SELECT ... FOR UPDATESKIP LOCKED子句。 SKIP LOCKED允许我们轻松操作多个用户的堆栈。

这是一个简单的界面:

create or replace package task_mgmt is

    function get_next_task return tasks.id%type;

    procedure complete_task (p_id in tasks.id%type);

    procedure release_task (p_id in tasks.id%type);

end task_mgmt;
/

这是一个简单的实现:

create or replace package body task_mgmt is

    function get_next_task return tasks.id%type
    is
        return_value tasks.id%type;
        cursor c_tsk is
            select id
            from tasks
            where status = 'open'
            order by date_created, id
            for update skip locked;

    begin
        open c_tsk;
        fetch c_tsk into return_value;
        update tasks
        set status = 'progress'
            , assigned = user
        where current of c_tsk;
        close c_tsk;
        return return_value;
    end get_next_task;

    procedure complete_task (p_id in tasks.id%type)
    is
    begin
        update tasks
        set status = 'complete'
            , date_completed = sysdate
        where id = p_id;
        commit;
    end complete_task;

    procedure release_task (p_id in tasks.id%type)
    is
    begin
        rollback;
    end ;

end task_mgmt;
/

当用户弹出堆栈时更新状态会创建锁定。由于SKIP LOCKED子句,下一个用户将看不到该任务。这比删除和重新插入记录更清晰。

以下是一些数据:

create table tasks (
    id number not null
    , descr varchar2(30) not null
    , date_created date default sysdate not null
    , status varchar2(10) default 'open' not null
    , assigned varchar2(30)
    , date_completed date
    , constraint task_pk primary key (id)
    )
/

insert into tasks (id, descr, date_created) values (1000, 'Do something', date '2015-05-28')
/
insert into tasks (id, descr, date_created) values (1010, 'Look busy', date '2015-05-28')
/
insert into tasks (id, descr, date_created) values (1020, 'Get coffee', date '2015-06-12')
/

让我们流行吧!这是第一节:

SQL> var    tsk1 number;
SQL> exec :tsk1 := task_mgmt.get_next_task ;

PL/SQL procedure successfully completed.

SQL> print :tsk1

      TSK1
----------
      1000

SQL>

同时在第二场:

SQL> var    tsk2 number;
SQL> exec :tsk2 := task_mgmt.get_next_task ;

PL/SQL procedure successfully completed.

SQL> print :tsk2

      TSK2
----------
      1010

SQL>

返回第一节:

SQL> exec task_mgmt.complete_task (:tsk1);

PL/SQL procedure successfully completed.

SQL> exec :tsk1 := task_mgmt.get_next_task ;

PL/SQL procedure successfully completed.

SQL> print :tsk1

       TSK
----------
      1020

SQL> 

这种方法的主要缺点是它要求用户在处理任务时维护有状态会话。事实并非如此,您需要一个API,其中get_next_task()是一个离散的事务,而忘记了锁定。

顺便说一下,让用户抓住任务而不是通过登录触发器分配任务(或者通过“Tom进入系统并获取任务1,2,3”时的任何想法)可能更好。拉取任务是SO Review队列的工作方式。

另外,一次只分配一个任务。这样你就可以有效地分配工作。你想避免汤姆在他的盘子上有三个任务的情况,其中一个他不会完成,鲍勃无所事事。也就是说,除非你是鲍勃。