【问题标题】:How to design task assignment system?如何设计任务分配系统?
【发布时间】:2015-09-06 21:58:02
【问题描述】:

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

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 是如何设计review 功能的吗?或者分享其他解决方案?我想知道SELECT ... FOR UPDATE 在这个用例中是否合适。

【问题讨论】:

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

标签: sql oracle system-design


【解决方案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 队列的工作方式。

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

【讨论】:

  • 谢谢,select ... for update skip locked 在这种情况下真的很有帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-07-22
  • 2016-01-25
  • 1970-01-01
  • 2011-03-27
  • 1970-01-01
  • 2021-03-28
  • 1970-01-01
相关资源
最近更新 更多