Pergunta

Estou trabalhando em um sistema que precisa lidar com várias condições de corrida ao servir empregos a várias máquinas de trabalho.

Os clientes consultariam o sistema para trabalhos com status = '0' (TODO), então, de maneira atômica, atualize a linha 'mais antiga' com status = '1' (bloqueado) e recupere o ID para essa linha (para atualizar o trabalho com informações do trabalhador, como qual máquina está trabalhando nela etc.).

A questão principal aqui é que pode haver qualquer número de clientes atualizando ao mesmo tempo. Uma solução seria bloquear cerca de 20 das linhas com status = '0', atualizar o mais antigo e liberar todos os bloqueios novamente depois. Eu tenho analisado o transactionMiddleware, mas não vejo como isso impediria o caso do mais antigo ser atualizado de mim depois de consultar.

Eu olhei para o queryset.update (), e parece promissor, mas no caso de dois clientes se apossarem do mesmo registro, o status simplesmente atualizaria e teríamos dois trabalhadores trabalhando no mesmo trabalho .. Estou realmente perdido aqui.

Eu também encontrei ingresso #2705 O que parece lidar bem com o caso, mas não tenho idéia de como obter o código a partir daí por causa da minha experiência limitada de SVN (as últimas atualizações são simplesmente diferenciadas, mas não sei como mesclar isso com o tronco do código ).

Código: resultado = trabalho

class Result(models.Model):
"""
Result: completed- and pending runs

'ToDo': job hasn't been acquired by a client
'Locked': job has been acquired
'Paused'
"""
# relations
run = models.ForeignKey(Run)
input = models.ForeignKey(Input)

PROOF_CHOICES = (
    (1, 'Maybe'),
    (2, 'No'),
    (3, 'Yes'),
    (4, 'Killed'),
    (5, 'Error'),
    (6, 'NA'),
)
proof_status = models.IntegerField(
    choices=PROOF_CHOICES,
    default=6,
    editable=False)

STATUS_CHOICES = (
    (0, 'ToDo'),
    (1, 'Locked'),
    (2, 'Done'),
)
result_status = models.IntegerField(choices=STATUS_CHOICES, editable=False, default=0)

# != 'None' => status = 'Done'
proof_data = models.FileField(upload_to='results/',
    null=True, blank=True)
# part of the proof_data
stderr = models.TextField(editable=False,
    null=True, blank=True)

realtime = models.TimeField(editable=False,
    null=True, blank=True)
usertime = models.TimeField(editable=False,
    null=True, blank=True)
systemtime = models.TimeField(editable=False,
    null=True, blank=True)

# updated when client sets status to locked
start_time = models.DateTimeField(editable=False)

worker = models.ForeignKey('Worker', related_name='solved',
    null=True, blank=True)
Foi útil?

Solução

Para mesclar #2705 em seu django, você precisa baixá -lo primeiro:

cd <django-dir>
wget http://code.djangoproject.com/attachment/ticket/2705/for_update_11366_cdestigter.diff?format=raw

Em seguida, rebobine o SVN para a versão Django necessária:

svn update -r11366

Em seguida, aplique:

patch -p1 for_update_11366_cdestigter.diff

Ele informará quais arquivos foram corrigidos com sucesso e quais não foram. No caso improvável de conflitos, você pode consertá -los manualmente olhando http://code.djangoproject.com/attachment/ticket/2705/for_update_11366_cdestigter.diff

Para desconsiderar o patch, apenas escreva

svn revert --recursive . 

Outras dicas

Se o seu django estiver em execução em uma máquina, existe uma maneira muito mais simples de fazê-lo ... desculpe o pseudo-código, pois os detalhes da sua implementação não são claros.

from threading import Lock

workers_lock = Lock()

def get_work(request):
    workers_lock.acquire()
    try:
        # Imagine this method exists for brevity
        work_item = WorkItem.get_oldest()
        work_item.result_status = 1
        work_item.save()
    finally:
        workers_lock.release()

    return work_item

Você tem duas opções no topo da minha cabeça. Uma é bloquear as linhas imediatamente após a recuperação e soltar apenas a trava depois que a apropriada for marcada como em uso. O problema aqui é que nenhum outro processo do cliente pode sequer olhar para os trabalhos que não são selecionados. Se você está sempre selecionando automaticamente o último, pode ser uma janela breve suficiente para ser bom para você.

A outra opção seria trazer de volta as linhas que estão abertas no momento da consulta, mas depois verificar novamente sempre que o cliente tenta pegar um trabalho para trabalhar. Quando um cliente tenta atualizar um trabalho para trabalhar nele, uma verificação seria feita primeiro para ver se ainda está disponível. Se alguém já o pegou, uma notificação será enviada de volta ao cliente. Isso permite que todos os clientes vejam todos os trabalhos como instantâneos, mas se eles estão constantemente pegando o mais recente, você poderá ter os clientes constantemente recebendo notificações de que um trabalho já está em uso. Talvez essa seja a condição de corrida a que você está se referindo?

Uma maneira de contornar isso seria devolver os trabalhos em grupos específicos aos clientes, para que eles nem sempre estejam obtendo as mesmas listas. Por exemplo, divida -os por área geográfica ou mesmo aleatoriamente. Por exemplo, cada cliente pode ter um ID de 0 a 9. Pegue o mod de um ID nos trabalhos e envie de volta esses trabalhos com o mesmo dígito final para o cliente. Não o limite apenas a esses trabalhos, pois você não quer que haja empregos que você não possa alcançar. Por exemplo, se você tivesse clientes de 1, 2 e 3 e um trabalho de 104, então ninguém seria capaz de chegar a ele. Portanto, uma vez que não haverá empregos suficientes com os trabalhos de dígitos finais corretos começariam a voltar com outros dígitos apenas para preencher a lista. Você pode precisar brincar com o algoritmo exato aqui, mas espero que isso dê uma idéia.

Como você bloqueia as linhas no seu banco de dados para atualizá -las e/ou enviar de volta, as notificações dependerão amplamente do seu RDBMS. No MS SQL Server, você pode envolver tudo isso funcionando bem em um procedimento armazenado, desde que a intervenção do usuário não seja necessária no meio dele.

Eu espero que isso ajude.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top