Processe Tarefas da Fila sem Efeitos Duplicados

AWSBeginner
Pratique Agora

Introdução

Tarefas repetidas para o mesmo pedido devem preservar o resultado de negócio concluído. Você adicionará uma proteção de gravação atômica a um consumidor fornecido, testará pedidos repetidos e independentes e verificará a confirmação bem-sucedida das mensagens.

Conclua primeiro Isole Tarefas com Falha em uma Fila de Mensagens Mortas, Impeça pedidos duplicados com gravações condicionais e Configure e Diagnostique uma Função Lambda. Esta VM independente fornece o consumidor inicial, a função IAM e a tabela de pedidos vazia.

Relação com as certificações

Este laboratório oferece prática nos seguintes tópicos de exame.

Conecte uma Fila Vazia ao Consumidor

Nesta etapa, crie uma fila e conecte o consumidor fornecido antes de enviar tarefas de negócio.

Use AWS View ao lado do Terminal para comparar as filas atuais, os resultados do consumidor e os pedidos armazenados. Preserve os dados de referência não relacionados.

cd /home/labex/project

Crie a fila Standard. A substituição de comando, $(...), salva a URL retornada da fila para operações posteriores:

QUEUE_URL=$(aws sqs create-queue --queue-name labex-q05-jobs --attributes VisibilityTimeout=30 --query QueueUrl --output text)

Selecione seu ARN e crie um mapeamento de origem de eventos com tamanho de lote um:

QUEUE_ARN=$(aws sqs get-queue-attributes --queue-url "$QUEUE_URL" --attribute-names QueueArn --query Attributes.QueueArn --output text)
MAPPING_ID=$(aws lambda create-event-source-mapping --function-name labex-q05-worker --event-source-arn "$QUEUE_ARN" --batch-size 1 --enabled --query UUID --output text)

Inspecione a conexão:

aws lambda get-event-source-mapping --uuid "$MAPPING_ID" --query '{Source:EventSourceArn,Function:FunctionArn,Batch:BatchSize,State:State}'

Espere a origem labex-q05-jobs, labex-q05-worker, tamanho de lote 1 e estado Enabled. A função IAM fornecida pode consultar e excluir mensagens apenas nesta fila e gravar apenas na tabela de pedidos, além de registrar logs de execução. AWS View mostra uma fila vazia, nenhum pedido e nenhuma decisão de gravação. Não envie uma tarefa até implantar a proteção na próxima etapa.

Implante uma Proteção Atômica por Chave de Negócio

Nesta etapa, substitua a gravação incondicional do consumidor e comprove que a primeira tarefa protegida é bem-sucedida.

ID de mensagem e chave de negócio

Mensagens distintas da fila podem carregar o mesmo id de negócio. O consumidor confirma uma duplicata correspondente sem gravar o pedido novamente.

Um consumidor idempotente preserva o mesmo resultado de negócio quando chega trabalho repetido. O DynamoDB avalia ConditionExpression='attribute_not_exists(id)' na mesma operação de gravação de PutItem. Isso evita uma condição de corrida entre uma leitura e uma gravação separadas. O id de negócio é a chave; usar o messageId do SQS permitiria que uma nova cópia do mesmo pedido enviada pelo produtor gravasse novamente.

Quando uma condição falha, ReturnValuesOnConditionCheckFailure='ALL_OLD' retorna o item existente. O código captura apenas ConditionalCheckFailedException e compara esse item ao item solicitado. Detalhes idênticos representam uma duplicata já concluída; detalhes conflitantes ou outro erro continuam causando falha, em vez de serem confirmados silenciosamente.

Escreva o consumidor completo abaixo. cat > app.py substitui o arquivo, e o here-document fornece seu conteúdo até o terminador PY. As aspas em 'PY' impedem a expansão pelo shell dentro do código Python:

cat > app.py <<'PY'
import json
import os
import boto3
from botocore.exceptions import ClientError


def handler(event, context):
    print('EVENT '+json.dumps(event,sort_keys=True))
    if set(event)!={'Records'} or len(event['Records'])!=1:
        raise ValueError('Expected one SQS record')
    job=json.loads(event['Records'][0]['body'])
    print('JOB '+json.dumps(job,sort_keys=True))
    if set(job)!={'id','quantity'} or not isinstance(job['id'],str):
        raise ValueError('Use an id and quantity')
    quantity=job['quantity']
    if isinstance(quantity,bool) or not isinstance(quantity,int) or not 1<=quantity<=10:
        raise ValueError('Quantity must be an integer from 1 to 10')
    database=boto3.client('dynamodb',endpoint_url='http://127.0.0.1:5000',region_name='us-east-1')
    item={'id':{'S':job['id']},'quantity':{'N':str(quantity)},'total_cents':{'N':str(quantity*250+100)}}
    try:
        database.put_item(TableName=os.environ['TABLE_NAME'],Item=item,
                          ConditionExpression='attribute_not_exists(id)',
                          ReturnValuesOnConditionCheckFailure='ALL_OLD')
        result={'id':job['id'],'quantity':quantity,'processed':True,'duplicate':False}
    except ClientError as error:
        if error.response['Error']['Code']!='ConditionalCheckFailedException':
            raise
        if error.response.get('Item')!=item:
            raise ValueError('Order ID already exists with different details') from error
        result={'id':job['id'],'quantity':quantity,'processed':False,'duplicate':True}
    print('RESULT '+json.dumps(result,sort_keys=True))
    return result
PY

A análise do evento e as verificações de quantidade são o contexto fornecido. A mudança principal é o único put_item condicional, o tratamento restrito de sua falha e o resultado processed/duplicate. Uma duplicata retorna com sucesso para que o consumidor possa confirmar sua cópia SQS depois que o resultado de negócio já tiver sido preservado.

Empacote app.py na raiz do ZIP. O manipulador da função existente é app.handler, portanto o nome do arquivo do módulo importa:

zip -q function.zip app.py

Envie o ZIP binário com fileb://:

aws lambda update-function-code --function-name labex-q05-worker --zip-file fileb://function.zip --query CodeSha256 --output text

O hash retornado identifica o código implantado; o processamento real comprovará seu comportamento. Envie o primeiro pedido:

aws sqs send-message --queue-url "$QUEUE_URL" --message-body '{"id":"dedup-order","quantity":4}'

Observe AWS View até a fila ficar vazia, o consumidor informar processed: true e o pedido aparecer. Depois, leia-o:

aws dynamodb get-item --table-name labex-q05-orders --key '{"id":{"S":"dedup-order"}}' --consistent-read --query Item

Espere quantidade 4 e total 1100. A decisão de gravação mostra a condição, Accepted, nenhum item anterior e esse item armazenado depois. Uma condição configurada ou um arquivo enviado, por si só, não comprova trabalho bem-sucedido.

Consuma Tarefas Repetidas sem Repetir a Gravação

Nesta etapa, envie duas novas cópias do mesmo pedido de negócio e comprove que outro pedido ainda é concluído de forma independente.

Envie o mesmo conteúdo de negócio duas vezes. Cada resposta de SendMessage tem um novo ID de mensagem SQS, mas a chave do pedido continua sendo dedup-order:

aws sqs send-message --queue-url "$QUEUE_URL" --message-body '{"id":"dedup-order","quantity":4}'
aws sqs send-message --queue-url "$QUEUE_URL" --message-body '{"id":"dedup-order","quantity":4}'

Envie uma chave de negócio independente:

aws sqs send-message --queue-url "$QUEUE_URL" --message-body '{"id":"other-order","quantity":1}'

Observe AWS View até quatro execuções reais retornarem e a fila ficar vazia. As duas execuções repetidas informam processed: false, duplicate: true. O DynamoDB rejeita as duas gravações condicionais com ConditionalCheckFailedException; cada item antes e depois permanece inalterado. O primeiro pedido e other-order têm, cada um, uma gravação aceita. O consumidor confirma as cópias repetidas em vez de tentar novamente um trabalho já concluído.

Leia os resultados de negócio:

aws dynamodb scan --table-name labex-q05-orders --query Items

Espere exatamente dedup-order/4/1100 e other-order/1/350; a ordem dos itens pode variar. Inspecione os resultados reais do consumidor. Um evento de log pode conter várias linhas; esse pipeline envia a saída JSON nativa para jq -r, divide cada evento em linhas e seleciona apenas as linhas RESULT, deixando os identificadores de recebimento fora dos resultados exibidos:

aws logs filter-log-events --log-group-name /aws/lambda/labex-q05-worker --filter-pattern '"RESULT"' --output json | jq -r '.events[].message | split("\n")[] | select(startswith("RESULT "))'

Verifique a confirmação das mensagens:

aws sqs get-queue-attributes --queue-url "$QUEUE_URL" --attribute-names ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible

As duas contagens são zero. Houve quatro entregas reais de mensagens e execuções da função, duas gravações de negócio aceitas e duas rejeições condicionais nativas. Contar apenas os itens da tabela não revelaria sobrescritas incondicionais; use também as decisões reais e os resultados correspondentes do consumidor.

A proteção cobre um item de negócio. Ela não transforma a gravação no DynamoDB e a confirmação no SQS em uma única operação atômica. O registro do pedido é a proteção durável contra duplicatas. Removê-lo permite que a mesma chave crie um item novamente, portanto a retenção e o projeto das chaves de negócio fazem parte da política de um sistema real. Essas repetições sintéticas demonstram trabalho seguro para o mesmo negócio; não estabelecem uma garantia de concorrência em produção nem de exatamente uma vez de ponta a ponta.

Exemplo de AWS View: quatro execuções reais produzem dois pedidos e duas rejeições condicionais que preservam o item existente, com a fila vazia.

Remova a Conexão do Consumidor e os Resultados de Negócio

Nesta etapa, remova a conexão e os recursos que você criou após confirmar o tratamento de duplicatas.

Interrompa seu mapeamento de origem de eventos antes de excluir sua fila de origem:

aws lambda delete-event-source-mapping --uuid "$MAPPING_ID" --query UUID --output text
aws sqs delete-queue --queue-url "$QUEUE_URL"

Remova os dois itens sintéticos de negócio e os logs de execução deste laboratório:

aws dynamodb delete-item --table-name labex-q05-orders --key '{"id":{"S":"dedup-order"}}'
aws dynamodb delete-item --table-name labex-q05-orders --key '{"id":{"S":"other-order"}}'
aws logs delete-log-group --log-group-name /aws/lambda/labex-q05-worker

Confirme a ausência dos recursos e preserve os dados de referência não relacionados:

aws lambda list-event-source-mappings --function-name labex-q05-worker --query EventSourceMappings
aws sqs list-queues
aws dynamodb scan --table-name labex-q05-orders --query Items
aws dynamodb scan --table-name labex-q05-reference --query Items

Os mapeamentos e os pedidos estão vazios, nenhuma URL de fila permanece e o item de referência ainda diz keep unchanged. A função e as estruturas das tabelas fornecidas permanecem no ambiente. Um erro de rede ou de autenticação não comprova a exclusão. AWS View mantém as decisões históricas de gravação enquanto mostra os recursos atuais vazios.

Remova seus arquivos comuns de código e de arquivo compactado:

rm -f app.py function.zip

Execute a verificação da limpeza antes de encerrar o ambiente.

Resumo

Você implantou uma condição atômica por chave de negócio do DynamoDB em um consumidor real de fila. O primeiro pedido foi concluído; duas cópias enviadas separadamente foram consumidas e confirmadas após a rejeição condicional nativa preservar o item original. Uma chave diferente ainda produziu seu próprio resultado. Gravações reais, resultados do consumidor e o estado da fila vazia comprovaram o resultado antes da limpeza dos recursos deste laboratório.

O desafio aplica o isolamento de falhas com tentativas limitadas, e o projeto sem servidor combina depois a recuperação pela DLQ com essa proteção por chave de negócio.