Conclua os ingressos da fila com segurança

SQLiteIntermediate
Pratique Agora

Introdução

O Nightjar Print Studio aceita inscrições para oficinas e prepara ingressos para download. Uma inscrição pode esperar com segurança enquanto o worker está parado, mas o worker atual reconhece a entrega cedo demais. Depois, repetir trabalho concluído pode gerar ingressos duplicados.

Você precisa de uso básico do terminal e saber ler uma pequena função Python, indentação e uma consulta a dicionário. A breve ponte abaixo revisa esses elementos e apresenta SQL e transações. RabbitMQ, Python, o banco SQLite e o site completo estão preparados; você edita apenas handler.py. Não precisa de instalação, conta externa, pagamento ou e-mail.

Use Nightjar para as páginas reais de inscrição e ingressos e Workstation para os comandos. Cada unidade começa de forma independente. O site e os rótulos exatos continuam em inglês. Este é um exercício de interrupção de worker em uma máquina, não uma promessa de alta disponibilidade ou entrega exatamente uma vez.

Acompanhe uma inscrição até seu ingresso

Nesta etapa, comece em Nightjar e veja o funcionamento normal do negócio antes de alterar o worker.

Abra Explore the evening, leia os detalhes e siga Plan your visit. Volte por The evening. Em Guest name, digite Normal guest e selecione Register for the evening. A nova página mostra um ID, como R-0002, e Your ticket is being prepared. Salvar a inscrição não significa emitir seu ingresso. Your registrations também contém o ingresso existente de Mina Patel; preserve-o.

Formulário real de inscrição da oficina Nightjar

O formulário Guest name pertence à página da oficina; o estado da inscrição e o ingresso ficam em páginas separadas.

Mude para Workstation. pwd mostra o diretório atual e cat mostra um arquivo de texto. O diretório deve ser /home/labex/project/ticket-desk:

pwd
cat handler.py

Uma fila guarda mensagens aguardando trabalho. RabbitMQ é o broker real; um worker recebe a mensagem com ID e nome, grava o ingresso e reconhece a entrega. Um reconhecimento avisa ao RabbitMQ que pode remover aquela entrega. Ele não cria o ingresso por si só.

Em Python, def handle(...) define uma função e suas linhas indentadas formam o corpo. O worker preparado chama a função para cada mensagem. job é um dicionário: job['id'] e job['name'] consultam seus valores. ch.basic_ack(delivery.delivery_tag) reconhece esta entrega no canal que a recebeu. checkpoint é uma pausa didática fornecida: normalmente não faz nada; uma opção --pause correspondente espera Enter ou Ctrl+C. Ela não simula trabalho nem eventos do broker.

O banco contém tabelas, coleções de linhas. db.execute(...) executa SQL. INSERT INTO tickets... adiciona uma linha de ingresso. Cada ? recebe um valor da tupla seguinte, para que nomes sejam dados e não comandos SQL. with db: confirma as gravações quando seu bloco indentado termina normalmente ou as desfaz se uma exceção sai do bloco. O commit torna as gravações persistentes. O código atual reconhece antes de trabalhar; você corrigirá essa ordem depois.

python3 worker.py executa o worker preparado uma vez: recebe uma mensagem pronta, chama seu manipulador e termina. Execute sem interromper:

python3 worker.py

Espere Received R-0002 redelivered=False e Handler returned.. Inscrições extras podem mudar os IDs. redelivered vem do RabbitMQ, não do banco de ingressos. Uma execução normal cria um ingresso real apesar da ordem insegura.

Volte a Nightjar, abra Your registrations, Normal guest e Open ticket. Selecione Download ticket e leia o arquivo: deve conter o mesmo ID e nome. Siga Back to registration. O ingresso de Mina Patel também deve abrir. Este é o percurso do cliente que sua correção deve preservar.

Se o worker disser No ready registrations, consulte a lista antes de repetir: uma execução anterior pode ter processado a mensagem. Para um nome digitado errado, inscreva novamente o nome pedido; todos os IDs continuam separados. Não edite o banco para forçar o resultado.

Reconheça somente o trabalho concluído

Nesta etapa, comece em Workstation. Mova o reconhecimento para depois da transação, interrompa um trabalho incompleto e veja o RabbitMQ devolvê-lo.

Se o worker reconhecer primeiro e depois falhar, não haverá entrega pendente para o RabbitMQ repetir. Reconhecer após o commit mantém a entrega sem reconhecimento até o ingresso existir. Isso protege contra a interrupção do worker; não combina RabbitMQ e SQLite em uma única transação.

Substitua apenas handler.py com o bloco abaixo. cat > handler.py sobrescreve o arquivo; <<'PYTHON' começa um here-document literal. Cole tudo, incluindo o último PYTHON sozinho em uma linha. Mantenha a indentação de quatro espaços. A transação permanece igual; apenas o reconhecimento vai para depois dela:

cat > handler.py <<'PYTHON'
from common import checkpoint

def handle(ch, delivery, job, db, pause):
    checkpoint(pause, 'before-write')
    with db:
        db.execute('INSERT INTO tickets(registration_id,name) VALUES (?,?)',
                   (job['id'], job['name']))
    checkpoint(pause, 'after-commit')
    ch.basic_ack(delivery.delivery_tag)
PYTHON

Em Nightjar, abra The evening, registre Retry guest e deixe sua página aberta. Volte a Workstation. A opção --pause before-write para no ponto imediatamente anterior a qualquer gravação:

python3 worker.py --pause before-write

Espere Paused before-write. e pressione Ctrl+C no terminal. Essa interrupção intencional termina com estado 130. Imediatamente mostre o estado do comando anterior com $?:

echo $?

Espere 130. Em Nightjar, atualize a inscrição Retry guest: ainda não há ingresso. Volte a Workstation e execute sem pausa:

python3 worker.py

Agora espere redelivered=True. O RabbitMQ realmente devolveu a entrega não reconhecida quando a conexão anterior fechou. Atualize a inscrição em Nightjar, abra o ingresso e baixe-o. O ingresso recuperado pertence ao mesmo ID.

Se a pausa não aparecer, revise a opção e cat handler.py. Se pressionou Enter sem querer, o worker concluiu normalmente; registre outro Retry guest e repita a interrupção. Se o código antigo reconheceu antes da interrupção, python3 desk.py status lista o ID e python3 desk.py retry R-0003 pede outra entrega para essa inscrição existente; substitua pelo ID real. Repetir é uma recuperação explícita do aluno, nunca da avaliação. Não repita ingressos concluídos até adicionar a deduplicação na próxima etapa.

Torne segura a repetição do resultado no banco

Nesta etapa, comece em Workstation. Resta uma segunda janela de falha: SQLite pode confirmar o ingresso e o worker parar antes do reconhecimento ao RabbitMQ. Repetir essa entrega criaria outro ingresso com o código atual.

Idempotência aqui significa que processar o mesmo ID novamente deixa um único resultado de negócio. Nomes não identificam inscrições: duas pessoas chamadas Same name ainda precisam de ingressos separados. A tabela preparada completed tem a chave primária id, que impede duas linhas com o mesmo ID.

INSERT OR IGNORE tenta inserir esse ID. Seu rowcount vale 1 para uma linha nova e 0 quando o ID já existe. (job['id'],) é uma tupla de um valor; a vírgula importa. if added: executa a inserção indentada apenas para um ID novo. As duas gravações devem ficar no mesmo bloco with db:. Caso contrário, uma falha pode salvar a chave sem o ingresso e ignorar permanentemente uma inscrição válida.

Substitua a função por esta implementação pequena e completa. A pausa interna está propositalmente entre as gravações para observar o rollback:

cat > handler.py <<'PYTHON'
from common import checkpoint

def handle(ch, delivery, job, db, pause):
    checkpoint(pause, 'before-write')
    with db:
        added = db.execute('INSERT OR IGNORE INTO completed VALUES (?)',
                           (job['id'],)).rowcount
        checkpoint(pause, 'inside-transaction')
        if added:
            db.execute('INSERT INTO tickets(registration_id,name) VALUES (?,?)',
                       (job['id'], job['name']))
    checkpoint(pause, 'after-commit')
    ch.basic_ack(delivery.delivery_tag)
PYTHON

Em Nightjar, registre Same name duas vezes. Os IDs são separados, normalmente R-0004 e R-0005. Volte a Workstation e pare o primeiro dentro da transação:

python3 worker.py --pause inside-transaction

Em Paused inside-transaction., pressione Ctrl+C. Atualize essa inscrição em Nightjar: não existe ingresso e a chave de conclusão também não deve sobreviver. Em Workstation, a próxima execução repete o mesmo ID, agora pausando após o commit:

python3 worker.py --pause after-commit

Em Paused after-commit., abra a inscrição em Nightjar: já existe um ingresso. Volte a Workstation, pressione Ctrl+C e processe novamente a entrega não reconhecida:

python3 worker.py

Espere redelivered=True, mas ao atualizar Nightjar deve continuar havendo exatamente um ingresso. A inserção da chave duplicada retornou 0; o manipulador pulou o segundo ingresso e ainda reconheceu a entrega.

Volte a Workstation. --drain repete a operação de uma mensagem até não haver mais mensagens prontas:

python3 worker.py --drain

A segunda inscrição Same name agora recebe seu próprio ingresso. desk.py status lê as linhas reais:

python3 desk.py status

Para testar uma entrega explicitamente duplicada, use o primeiro ID Same name mostrado. Na sequência normal é R-0004; substitua o seu se for diferente:

python3 desk.py retry R-0004
python3 worker.py --drain

Em Nightjar, cada ID Same name tem um ingresso para download, e Mina Patel, Normal guest e Retry guest permanecem. Nenhuma inscrição deve aguardar. Só No ready registrations não prova isso: trabalhos perdidos também podem deixar a fila vazia.

Se Python informar erro de sintaxe ou indentação, copie a função novamente e repita; o trabalho não reconhecido volta ao RabbitMQ. Mantenha as gravações juntas. Não apague ingressos nem chaves para fazer uma abordagem quebrada parecer correta.

Se processou acidentalmente um ID concluído com o código anterior não idempotente, corrija primeiro handler.py. A ação preparada repair oferece recuperação local sem reiniciar outros convidados. Encontre o ID com python3 desk.py status. Por exemplo, se Normal guest é R-0002, visualize:

python3 desk.py repair R-0002

A prévia lista o ingresso original mais antigo a manter, os números extras a remover e a conclusão somente deste ID. Nada mudou ainda. --apply aplica explicitamente o plano em uma transação:

python3 desk.py repair R-0002 --apply

Substitua o ID afetado. O auxiliar rejeita IDs desconhecidos, sem ingresso ou com nomes divergentes da inscrição. Preserva o número e o conteúdo do ingresso mais antigo, as outras inscrições e seus ingressos. Registra o ID reparado como concluído para que o manipulador corrigido possa ignorá-lo com segurança. Nunca consome nem reconhece mensagens. Abra o ingresso mantido em Nightjar e use python3 worker.py --drain para processar as entregas restantes com o código reparado. O reparo não faz um manipulador inseguro passar nas verificações isoladas.

A avaliação lê seus resultados reais e executa seu manipulador inalterado contra outro broker RabbitMQ e bancos descartáveis. Testa interrupção, reentrega real e rollback sem consumir, reconhecer ou reparar trabalhos ativos. São efeitos do banco seguros de repetir, não uma garantia de entrega exatamente uma vez; e-mails e pagamentos externos precisam de projeto próprio.

Resumo

Você recuperou tarefas de ingressos, distinguiu entrega de conclusão do negócio e preservou inscrições distintas com nomes iguais. Reconhecer após o commit permite repetir; uma chave de conclusão e o ingresso em uma transação tornam o novo processamento seguro para este resultado no banco. Isso não garante entrega exatamente uma vez nem cobre e-mail, pagamentos, falhas do broker ou todos os modos de falha de produção.