1

我想设计一个类似于 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。

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

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

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

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

4

1 回答 1

1

您需要实现的是 FIFO 堆栈或简单队列。在 Oracle 中,对于这样的事情,最好的事情(除非你想用 AQ 实现一个实际的队列)是SELECT ... FOR UPDATE使用SKIP 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> 

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


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

此外,一次只分配一项任务。这样你就可以得到有效的工作分配。您想避免 Tom 有三项任务,其中一项他不会完成,而 Bob 无事可做的情况。也就是说,除非你是鲍勃。

于 2015-06-21T16:14:11.393 回答