2015-06-21 96 views
1

我想设计一个类似于stackoverflow的系统评论功能。也就是说:如何设计任务分配系统?

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

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

  • 在17:00,汤姆进入系统,并获得任务1,2,3
  • 在17:01,吉姆进入系统,并获得任务4,5,6,
  • 在17 :02,Jerry进入系统并获得任务7,8。
  • 在17:03,Bob进入系统并没有得到任务。
  • 在17:05,汤姆完成任务1,2,离开系统。
  • 在17:06,鲍勃再次进入系统,并获取任务3.

想我使用数据库来存储任务信息。

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

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

有人可以知道如何计算器设计审查功能?或分享其他解决方案?我想知道在这个用例中SELECT ... FOR UPDATE是否合适。

+0

这取决于你如何定义“进入/离开系统”。简单的方法:在每次请求期间,您都假设用户“进入系统”。如果用户已经分配任务,则一切正常。如果存在未完成的任务,则将一些任务分配给该用户(即更新数据库中的任务并记住用户和时间戳),除非用户分配了足够的任务。如果用户注销或者有一段时间没有被看到,您可以从任务表中删除用户及其时间戳。因此,您现在需要做的是:在每个请求期间更新任务及其分配。 –

回答

1

你需要实现的是一个FIFO堆栈或简单队列。在Oracle中,最好的东西是SELECT ... FOR UPDATESKIP LOCKED子句,除非你想实现一个带有AQ的实际队列。 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> 

这种方法的主要缺点是它要求用户在工作时保持有状态的会话e任务。它不是这种情况,那么你需要一个API,其中get_next_task()是一个离散事务,并忘记锁定。


顺便说一下,它可能会更好,让用户下载,而不是通过登录触发器为它们分配一个任务(或任何你在心里有“汤姆进入系统,并获得任务1,2,3”) 。拉取任务是SO审核队列的工作方式。

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

+0

谢谢,'select ... for update skip locked'在这种情况下真的很有用! – coderz