避免并发访问相同的队列元素

时间:2017-12-12 14:57:28

标签: python django

我正在审查/重构一个队列,该队列最多可同时由20人内部使用,但截至目前,多人可以访问第一个元素(我们在本地尝试同时点击该链接。)

助焊剂与此类似:

views.py

[GET]
def list_operator(request, id):
  if request.POST:
    utilitary = Utilitary(id)
    pool = ThreadPool(processes=1)
    async_result = pool.apply_async(utilitary.recover_people, (id, ))
    return_val = async_result.get()
    person = People.objects.get(pk=return_val)
    return redirect('people:people_update', person.pk)

utilitary.py

此文件具有方法recover_people,该方法跨多个表执行大约4-5个查询(人们有flag_allocated=False)并对列表进行排序,以返回第一个元素。 最后一步是这一步:

for person in people:
  p_address = People_Address.objects.get(person_id=person.id)
  p_schedule = Schedules.objects.get(schedules_state=p_address.p_state)

  if datetime.now() > parse_time(p_schedule.schedules_hour):
    person = People.objects.get(pk=person.id)
    person.flag_allocated = True
    person.date_of_allocation = datetime.now()
    person.save()
    return person.pk

也许Utilitary方法逻辑中的某些内容是错误的?或者我应该期待这个问题同时调用这种方法吗?

可以使用缓存帮助吗?对不起,我是django和mvc的新手。

3 个答案:

答案 0 :(得分:2)

如果你没有做任何事情来防止并发进程之间的干扰,他们迟早会踩到彼此的脚趾。

在数据库中建模队列的一种历史悠久的方法是在执行作业之前以事务一致的方式将工作者注册到特定作业。

假设您有一个表work,其中包含作业ID或规范的列,初始为空状态以及最初为worker的空值。工作人员可以通过运行

等更新来“注册”工作
 Update `work` set worker = my_worker_id, status=initializing where status is null and worker is null limit 1.

由于“where”条款,只有一名工作人员可以“注册”下一份工作。

这并不完美 - 您仍然需要处理因失败的工人而成为孤儿的工作。状态列,在工作完成时更新,结合工人的某种心跳,以及围绕工作幂等性的精心设计,将为您提供基本原则,以确保工作不会卡在失败或AWOL工作人员身上。

答案 1 :(得分:2)

这里的规范解决方案是使用某种锁,这样你就不能有utilitary.recover_people的两个并发执行 - 该函数等待它获取锁,执行并释放锁。

鉴于Django通常由多个进程提供服务(你当然不想改变它),并且你不想要一个搞砸的电话来永远保持锁定,这里是一个很好的解决方案是使用像redis这样的东西来存储锁(所有django进程当然共享相同的redis数据库),将到期时间设置为合理的时间,这样它就不会永远保持设置状态。

有使用芹菜的这种设置的例子(不是说你必须在这里使用芹菜,它只是原理是相同的,因为它是使用芹菜以避免并发任务踩踏的常见用例彼此)。

您也可以使用您的SQL数据库来存储锁定但是您没有自动过期...

答案 2 :(得分:2)

在这种情况下,选择" person"似乎很多。再次在数据库中将其锁定在内存中,然后将其写入数据库。这个动作不是原子的。

您可以在这些操作之间通过其他进程锁定记录:

person = People.objects.get(pk=person.id)
person.flag_allocated = True
person.date_of_allocation = datetime.now()
person.save()

这就是你的问题所在。但是......如果你直接更新数据库上的记录,通过一个条件,更新只会在flag_allocated=False的记录上写入,你只需看看你的更新是否影响了任何行。如果没有,则转到队列中的下一个人。

类似的东西:

for person in people:
    rows = People.objects.filter(pk=person.id, flag_allocated=False).update(flag_allocated=True)
    if rows:
        break # Got the person... And nobody else will.

更新将锁定记录以将其写入allocation_flag(SQL原则)。如果两个更新试图弄乱同一行,一个将首先执行,然后第二个不会更新任何内容,并将尝试下一个人。