Como Usar Listas do Redis (LPUSH, RPOP) como Filas de Mensagens

Aprenda a transformar Listas do Redis em um sistema poderoso de filas de mensagens. Este tutorial aborda os comandos essenciais LPUSH e RPOP, demonstrando como enfileirar tarefas e permitir que workers as desenfileirem e processem de forma confiável. Explore exemplos práticos em Python e descubra considerações importantes para construir filas de mensagens FIFO robustas com Redis para processamento assíncrono de tarefas.

Como Usar Listas do Redis (LPUSH, RPOP) como Filas de Mensagens

As Listas do Redis podem formar uma fila de mensagens pequena e útil quando você precisa de algo mais leve que RabbitMQ, Kafka ou um framework completo de tarefas em segundo plano. O padrão usual é simples: produtores adicionam trabalhos com LPUSH, e workers retiram trabalhos com RPOP ou BRPOP.

Essa simplicidade é a razão pela qual as pessoas recorrem a ela. Uma requisição web pode colocar um trabalho de e-mail no Redis e retornar rapidamente. Um worker pode pegar esse trabalho um momento depois. Você não precisa de uma topologia de broker, exchanges, tópicos ou uma nova pilha operacional. Você, no entanto, precisa ser honesto sobre a troca: RPOP puro remove a mensagem antes que o worker tenha terminado o trabalho. Se o worker falhar no momento errado, esse trabalho se perde, a menos que você construa um padrão de confirmação em torno dele.

Entendendo Listas do Redis como Filas

Uma Lista do Redis é uma coleção ordenada de strings. Ela pode ser vista como uma sequência de elementos, e o Redis fornece comandos para adicionar ou remover elementos de qualquer extremidade da lista. Essa natureza de dupla extremidade torna as listas inerentemente adequadas para implementações de filas.

  • Enfileiramento (Adicionando Mensagens): Podemos adicionar novas mensagens à fila empurrando-as para uma extremidade da lista. O comando LPUSH empurra elementos para o início (lado esquerdo) de uma lista.
  • Desenfileiramento (Processando Mensagens): Podemos recuperar e remover mensagens da fila retirando-as da outra extremidade da lista. O comando RPOP retira elementos do final (lado direito) de uma lista.

Essa combinação específica (LPUSH para enfileirar e RPOP para desenfileirar) cria uma fila Primeiro a Entrar, Primeiro a Sair (FIFO), que é o comportamento mais comum e esperado para uma fila de mensagens.

Comandos Principais: LPUSH e RPOP

Vamos nos aprofundar nos dois comandos primários que formam a espinha dorsal da nossa fila de mensagens Redis.

LPUSH key value [value ...]

O comando LPUSH insere um ou mais valores de string no início (lado esquerdo) da lista armazenada em key. Se a key não existir, uma nova lista é criada e os valores são inseridos.

Exemplo:

Imagine que você tem uma tarefa que precisa ser processada, como enviar um e-mail. Você pode empurrar esta tarefa como uma mensagem para uma lista Redis chamada email_tasks.

# Empurrar uma única tarefa de e-mail
LPUSH email_tasks "{'to': '[email protected]', 'subject': 'Welcome!', 'body': 'Thanks for signing up!'}"

# Empurrar outra tarefa, ela será colocada antes da anterior
LPUSH email_tasks "{'to': '[email protected]', 'subject': 'New User Registration', 'body': 'A new user has registered.'}"

Após esses comandos, a lista email_tasks ficará assim (do início ao fim):

1) "{'to': '[email protected]', 'subject': 'New User Registration', 'body': 'A new user has registered.'}"
2) "{'to': '[email protected]', 'subject': 'Welcome!', 'body': 'Thanks for signing up!'}"

RPOP key

O comando RPOP remove e retorna o último elemento (do final, lado direito) da lista armazenada em key. Se a lista estiver vazia, ele retorna nil.

Exemplo:

Um processo worker pode periodicamente consultar a lista email_tasks por novas tarefas usando RPOP.

# Um worker tenta recuperar uma tarefa
RPOP email_tasks

Se a lista não estiver vazia, RPOP retornará o último elemento empurrado (que é o primeiro elemento do final). No nosso exemplo acima, a primeira chamada a RPOP retornaria:

"{'to': '[email protected]', 'subject': 'Welcome!', 'body': 'Thanks for signing up!'}"

Chamadas subsequentes então recuperariam a próxima tarefa disponível do final.

Construindo um Sistema Básico de Fila de Mensagens

Vamos delinear o fluxo típico de uma fila de mensagens simples usando LPUSH e RPOP.

1. Produtor (Enfileiramento de Tarefas)

Qualquer parte da sua aplicação que precise delegar trabalho pode atuar como produtor. Ela constrói uma mensagem (frequentemente uma string JSON representando os detalhes da tarefa) e a empurra para uma lista Redis usando LPUSH.

Lógica do Produtor (Exemplo Conceitual em Python):

import redis
import json

r = redis.Redis(host='localhost', port=6379, db=0)

def send_email_task(to_email, subject, body):
    task_message = {
        'type': 'send_email',
        'payload': {
            'to': to_email,
            'subject': subject,
            'body': body
        }
    }
    # LPUSH adiciona ao início da lista 'email_queue'
    r.lpush('email_queue', json.dumps(task_message))
    print(f"Tarefa de e-mail enviada para a fila: {to_email}")

# Exemplo de uso:
send_email_task('[email protected]', 'Olá do Produtor', 'Esta é uma mensagem de teste.')
send_email_task('[email protected]', 'Atualização Importante', 'Novos recursos disponíveis.')

2. Consumidor (Desenfileiramento e Processamento de Tarefas)

Processos workers, executando independentemente, monitorarão continuamente a lista Redis por novas mensagens. Eles usam RPOP para buscar e remover uma mensagem da fila.

Lógica do Consumidor (Exemplo Conceitual em Python):

import redis
import json
import time

r = redis.Redis(host='localhost', port=6379, db=0)

def process_tasks():
    while True:
        # RPOP tenta obter uma mensagem do final da lista 'email_queue'
        message_bytes = r.rpop('email_queue')
        if message_bytes:
            message_str = message_bytes.decode('utf-8')
            try:
                task = json.loads(message_str)
                print(f"Processando tarefa: {task}")
                # Simular processamento da tarefa
                if task.get('type') == 'send_email':
                    print(f"  -> Enviando e-mail para {task['payload']['to']}...")
                    # Substituir pela lógica real de envio de e-mail
                    time.sleep(1) # Simular trabalho
                    print(f"  -> E-mail enviado para {task['payload']['to']}.")
                else:
                    print(f"  -> Tipo de tarefa desconhecido: {task.get('type')}")
            except json.JSONDecodeError:
                print(f"Erro ao decodificar JSON: {message_str}")
            except Exception as e:
                print(f"Erro ao processar tarefa {message_str}: {e}")
        else:
            # Nenhuma mensagem, esperar um pouco antes de consultar novamente
            # print("Nenhuma tarefa disponível, aguardando...")
            time.sleep(0.5)

if __name__ == "__main__":
    print("Worker iniciado. Aguardando tarefas...")
    process_tasks()

Quando você executa o produtor, ele empurra mensagens. Quando você executa o consumidor, ele começará a pegá-las e processá-las. A ordem de processamento corresponderá à ordem em que foram empurradas (FIFO) porque LPUSH adiciona ao início e RPOP remove do final.

Considerações de Confiabilidade

Embora LPUSH e RPOP forneçam um mecanismo básico de fila, construir uma fila de produção significa decidir o que deve acontecer quando workers falham, trabalhos falham ou produtores enviam payloads malformados.

1. Perda de Mensagem Durante o Processamento

Se um processo worker falhar depois que RPOP removeu a mensagem, mas antes de terminar o processamento, essa mensagem é perdida. Para evitar isso:

  • Use BRPOP em vez de consulta intensa: BRPOP bloqueia até que uma lista tenha um elemento ou até que um tempo limite ocorra. Isso não torna o processamento confiável por si só, mas impede que workers acordem a cada poucos milissegundos apenas para encontrar uma fila vazia.
    # Pop bloqueante da direita, com um timeout de 0 (bloquear indefinidamente)
    BRPOP email_queue 0
    
  • Use uma lista de processamento para confirmação: Um padrão comum é mover atomicamente uma mensagem de email_queue para email_processing, processá-la e, em seguida, removê-la de email_processing somente após o trabalho ser bem-sucedido. Se um worker morrer, um processo ceifador separado pode procurar itens obsoletos na lista de processamento e movê-los de volta para a fila principal. RPOPLPUSH é o comando clássico para esse padrão, e versões mais novas do Redis também fornecem LMOVE/BLMOVE.

2. Lidando com Tarefas com Falha

O que acontece se uma tarefa falhar durante o processamento (por exemplo, devido a um problema de rede temporário ou dados ruins)?

  • Mecanismos de Repetição: Implemente lógica de repetição dentro do worker. Após algumas falhas, mova a tarefa para uma lista 'failed_tasks' para inspeção manual.
  • Fila de Mensagens Mortas (DLQ): Uma lista Redis dedicada (ou outro armazenamento) para onde as mensagens que falham repetidamente são enviadas. Isso é crucial para depuração e recuperação.

3. Múltiplos Consumidores

Se você tiver várias instâncias de worker consumindo da mesma fila, RPOP (e BRPOP) garante que cada mensagem seja processada por apenas um worker. Isso ocorre porque RPOP remove atomicamente o elemento.

4. Ordenação de Mensagens

Embora LPUSH e RPOP criem uma fila FIFO, essa garantia é tão forte quanto sua lógica de processamento. Se os consumidores reenfileirarem mensagens com falha sem o tratamento adequado, ou se você introduzir outras operações, a ordem FIFO estrita pode ser comprometida.

5. Formato do Payload e Idempotência

Trate o corpo da mensagem como um pequeno contrato. JSON é comum porque é fácil de inspecionar no redis-cli, mas use JSON válido em vez de dicionários com aspas simples no estilo Python:

{"type":"send_email","id":"email-1842","payload":{"to":"[email protected]","template":"welcome"}}

O campo id é importante. Se um worker tentar novamente após um tempo limite, ou se um trabalho obsoleto for reenfileirado de uma lista de processamento, o mesmo trabalho lógico pode ser executado mais de uma vez. Projete o manipulador para que uma duplicata seja inofensiva. Para um worker de e-mail, isso pode significar registrar email-1842 no banco de dados da aplicação antes de enviar e, em seguida, verificar esse registro antes que qualquer repetição envie outra mensagem.

6. Tamanho da Fila e Contrapressão

Monitore o tamanho da fila com LLEN email_queue. Uma fila crescente não é automaticamente ruim; pode significar apenas que os workers estão alcançando após um pico de tráfego. Uma fila que cresce por horas geralmente significa que os produtores são mais rápidos que os consumidores, os workers estão falhando ou uma dependência lenta está segurando tudo.

Na prática, gosto de alertar também sobre a idade, além do tamanho. As Listas do Redis não armazenam o tempo de enfileiramento separadamente, então coloque um timestamp no payload se a idade do trabalho for importante:

{"type":"resize_image","id":"img-991","created_at":"2026-05-24T08:15:00Z","payload":{"image_id":991}}

Então, os logs do seu worker podem dizer se os trabalhos estão sendo processados com segundos de atraso ou horas de atraso. Isso é muito mais útil do que apenas o tamanho quando você está depurando um incidente real.

Técnicas Avançadas (Brevemente)

  • RPOPLPUSH: Remove atomicamente uma mensagem de uma lista e a empurra para outra (por exemplo, uma lista de 'processamento'). Este é um comando chave para implementar processamento confiável com confirmações.
  • BLPOP / BRPOP com Múltiplas Chaves: Bloqueia e remove da primeira lista que se tornar não vazia. Útil para consumir de múltiplas filas.
  • Scripts Lua: Para operações atômicas complexas que RPOPLPUSH não cobre, scripts Lua podem ser usados para garantir que sequências críticas de comandos sejam executadas sem interrupção.

Um Formato de Worker Mais Confiável

Para qualquer coisa mais importante que uma notificação de melhor esforço, evite o worker simples de "remover e torcer". Um formato mais seguro se parece com isso:

RPOPLPUSH email_queue email_processing

O worker recebe a mensagem e o Redis já a moveu para email_processing. Após o e-mail ser enviado e a aplicação registrar o sucesso, o worker remove esse payload exato da lista de processamento:

LREM email_processing 1 '{"type":"send_email","id":"email-1842"}'

Isso ainda não é uma fila empresarial perfeita. LREM deve corresponder ao payload, listas de processamento grandes podem se tornar complicadas, e você precisa de um processo ceifador que saiba quando uma mensagem é velha o suficiente para repetir. Mas isso muda o modo de falha de uma forma útil. Uma falha do worker não exclui mais a única cópia do trabalho.

Se você usar essa abordagem, coloque metadados de repetição na mensagem ou armazene-os ao lado do ID da mensagem em outra chave. Por exemplo, um ceifador pode mover uma mensagem obsoleta de volta para email_queue nas primeiras vezes e, em seguida, movê-la para email_failed após o limite de repetições. Isso lhe dá um lugar para inspecionar mensagens envenenadas em vez de observar o mesmo payload ruim falhar para sempre.

Quando as Listas do Redis São a Fila Errada

As Listas do Redis são fáceis de entender, mas nem sempre são a ferramenta certa. Se você precisa de trabalhos atrasados, prioridades de trabalho, repetições programadas, visibilidade do fluxo de trabalho ou histórico de auditoria de longo prazo, uma biblioteca de trabalhos ou um broker dedicado pode ser menos trabalhoso no final. Os Streams do Redis também valem a pena considerar porque têm grupos de consumidores e semânticas de confirmação embutidos no tipo de dado.

Ainda gosto de Listas para pequenas filas internas: geração de miniaturas, aquecimento de cache, fan-out de webhook onde o sistema de origem pode repetir, ou trabalhos simples em segundo plano pertencentes a uma aplicação. No momento em que várias equipes dependem do contrato da fila, anote as expectativas de entrega. "Pelo menos uma vez", "no máximo uma vez" e "melhor esforço" não são termos acadêmicos durante um incidente. Eles decidem se duplicatas são aceitáveis, se mensagens perdidas são toleráveis e quanta maquinaria de recuperação você precisa.

As Listas do Redis são uma boa escolha quando você precisa de uma fila pequena e compreensível e já opera o Redis. Comece com LPUSH e BRPOP para trabalhos simples em segundo plano. Adicione uma lista de processamento, contagem de repetições e lista de mensagens mortas assim que perder um trabalho se tornar importante. Se você precisar de agendamento atrasado, prioridades, fan-out, retenção longa ou fortes garantias de entrega em muitos serviços, esse é geralmente o ponto em que uma fila construída para o propósito se torna mais fácil de conviver do que uma pilha crescente de convenções do Redis.