Skip to main content

Channel (IModel) | RabbitMQ | CSharp

  • Channel (IModel) | RabbitMQ | CSharp

O que é?

info

Representa uma sessão com o servidor RabbitMQ e é onde ocorre a maioria das operações (como declarar filas, publicar mensagens e assinar)


Ciclo de vida | Channel (IModel) | RabbitMQ

  • Recomenda ser scoped porque não é thread-safe e se tiver vários consumers usando ao mesmo tempo pode dar erros inesperados como:
    • race condition
    • ack errado
    • channel fechado inesperadamente
    • mensagens duplicadas

Visão Geral

No client do RabbitMQ para .NET, existem dois conceitos principais:

  • Connection (IConnection)
  • Channel (IChannel / IModel)

Cada um possui características diferentes de concorrência:

  • Connection é thread-safe
  • Channel NÃO é thread-safe

Isso significa que múltiplas threads acessando o mesmo channel simultaneamente podem causar comportamento indefinido.


Como o Channel Funciona Internamente

Quando você executa um publish:

await channel.BasicPublishAsync(...);

Isso não envia apenas um bloco de dados. Internamente, o client envia múltiplos frames no protocolo AMQP:

[Frame: Method - BasicPublish]
[Frame: Header - propriedades]
[Frame: Body - payload]

Esses frames precisam ser enviados:

  • em ordem
  • sem interrupção
  • respeitando o contexto da mensagem

Problema de Concorrência

Considere duas threads usando o mesmo channel:

Thread A:

Frame A1
Frame A2
Frame A3

Thread B:

Frame B1
Frame B2
Frame B3

Sem controle de concorrência, o envio pode virar:

Frame A1
Frame B1
Frame A2
Frame B2
Frame A3
Frame B3

Isso quebra o protocolo AMQP, pois o servidor não consegue reconstruir corretamente as mensagens.


Analogia com NetworkStream

O comportamento é equivalente a múltiplas threads escrevendo no mesmo stream TCP:

// Thread 1
stream.Write("HELLO");

// Thread 2
stream.Write("WORLD");

Resultado possível:

HEWOLLRLOD

No caso do RabbitMQ, o problema é mais grave, pois envolve protocolo estruturado e não apenas texto.


Natureza Stateful do Channel

O channel mantém estado interno durante a comunicação:

  • sequência de frames
  • controle de envio
  • confirmações (ack/nack)
  • buffers internos

Esse estado assume acesso sequencial.

Quando múltiplas threads escrevem ao mesmo tempo:

  • o estado interno fica inconsistente
  • a sequência de frames é corrompida
  • o servidor pode fechar a conexão

Problemas Observados em Produção

Uso concorrente de channel pode causar:

  • mensagens corrompidas
  • exceções intermitentes
  • fechamento inesperado de conexão
  • perda de mensagens
  • comportamento não determinístico

Exemplo Incorreto

app.MapPost("/resposta", async (HttpRequest request, IGerenciadorConexaoMensageria gerenciador) =>
{
var body = await ReadAllBytesAsync(request.Body);

await gerenciador.Channel.BasicPublishAsync(
exchange: "",
routingKey: "fila",
mandatory: false,
basicProperties: null,
body: body);

return Results.Ok();
});

Problema:

  • gerenciador.Channel é compartilhado
  • múltiplas requisições usam o mesmo channel simultaneamente

Abordagens Corretas

Channel por requisição

app.MapPost("/resposta", async (HttpRequest request, IGerenciadorConexaoMensageria gerenciador) =>
{
var body = await ReadAllBytesAsync(request.Body);

var channel = await gerenciador.Connection.CreateChannelAsync();

await channel.BasicPublishAsync(
exchange: "",
routingKey: "fila",
mandatory: false,
basicProperties: null,
body: body);

await channel.CloseAsync();

return Results.Ok();
});

Pool de Channels

  • reutiliza múltiplos channels
  • evita criação excessiva
  • mantém isolamento entre threads

Regra Prática

  • Nunca compartilhe um channel entre múltiplas threads sem sincronização

  • Prefira:

    • um channel por operação
    • ou pool de channels