Under Pressure: Hur kösystem hanterar baktryck med exempel i C (Svenska (Swedish))

Under Pressure: Hur kösystem hanterar baktryck med exempel i C

Sunday, 23 November 2025

//

15 minute read

"Backpressure" är den okända hjälten i distribuerade system. ., det är vad som hindrar köerna från att bli fulla när producenter skjuter upp meddelanden snabbare än kunder kan tugga igenom dem." ., enkelt uttryckt: "" :, "" det är ', systemet säger:

Här är det som händer. teknikerna i den här artikeln gäller varenda dag message queue and service bus—RabbitMQ, KafkaM SK2 Azure Service BusMSC3 AWS SQSMska4 NATSM Ska5 du namnge det Mska6 Specifikiteten varierarMka7 men de principerna är universellaMkka8 JagMske9 använder RabbitMQ för de flesta exempel eftersom det är vad jag känner till bästMcka11 men jagMssa12 ska visa hur dessa mönster överförs över plattformarMsa13

En bekännelse: Även de flesta högt uppsatta utvecklare implementerar inte rätt backpressure handling. De bygger lyckliga system som fungerar bra i dev och staging. ,. Då undrar man varför produktionen går ner under "" Black Friday "". .. Backpressure-handling är en av de tekniker som skiljer ", det fungerar, ", från ", det skalar, M SK8. Om du inte tänker på det, ,, så bygger du ett system som slutligen misslyckas under last.

Vad är baktryck

I grunden är baktrycket en feedback-loop som saktar ned producenter när konsumenterna halkar efter.1 Tänk på det som trafikljus på en skidväg.2 Du kan.3 Du kan inte bara stapla på motorvägen när du vill.4 Lamporna kontrollerar flödet.5 Att låta bilar sammanföras utan att orsaka en hög.6 Upp.7

Utan baktryck.,. En snabb producent överväldiger en långsam konsument... meddelanden staplar upp i köer, ,. minnet blir utmattat, ,. och slutligen faller ditt system över.M SK4. baktrycket säger: "" ", håll igång! "", ", innan katastrofen slår till.

flowchart LR
    P[Producer] --> Q[Queue]
    Q --> C[Consumer]
    C -. "Slow down!" .-> P

    style P stroke:#f59e0b,stroke-width:2px
    style Q stroke:#0ea5e9,stroke-width:2px
    style C stroke:#10b981,stroke-width:2px

skönheten med baktrycket är att det konversation mellan producent och konsument.

Hur RabbitMQ hanterar baktrycket

RabbitMQ är en open-source message broker , som implementerar Advanced Message Queuing Protocol (AMQP). Det är en av de mest utbretta messagbrokererna, ', som används av företag från nystart till företag som Bloomberg. , Pivotal , och VMware ( som nu underhåller den

Vad RabbitMQ är bäst på:

  • Kompleks ruttering - Utbyter , bindningar , och spårningskyltar låter dig bygga sofistikerade meddelandenriktade topologier utbytestyper (redrigt , strömmen ut, , ämne, , opskrifte, M SK4 har du tagit itu med?
  • Tillförlitlighetsgarantier - Publisher bekräftar, konsumenternas erkännande, och ständiga meddelanden innebär att du kan bygga system där meddelandets förlust är oacceptabelt.
  • Flexibel användning - kör den på en enda nod för utveckling kluster med kvorum köer för hög tillgänglighet i produktion.
  • Protokollstöd - Utöver AMQP MQTT (IoT), STOMP (enlig textprotocol), och har en strömmar egenskaper för Kafka

När man ska överväga alternativ Om du behöver massiv genomspetsning med en enklare routering (miljoner meddelanden /sekunderM SK2 Kafka'loggen, -baserad arkitektur kan vara mer lämpligtMSC5 Om du är allihopa på ett cloud-tillhandahållare

RabbitMQ har flera inbyggda mechanismer för att hantera baktrycket, ,, och det är viktigt att förstå dem om du designar system som behöver hålla sig upprätt under last. RabbitMQ-dokumentation är utmärkt—I'l linkar till specifika sidor medan vi går

Flow Control

När RabbitMQ's minnesmängd eller köd djup överskrider konfigurerade tröskeln, aktiverar den flödekontroll. Det här blockerar tillfälligt redaktörernas anslutningar — redaktörerna kan ' inte skicka nya meddelanden förrän دلالn har rensat tillräckligt med backlog minnesalarmer och diskalarmer som aktiverar flödetskontroll.

flowchart TD
    subgraph "RabbitMQ Flow Control"
        A[Publisher Sends Message] --> B{Memory/Queue<br/>Threshold OK?}
        B -->|Yes| C[Message Accepted]
        C --> D[Add to Queue]
        B -->|No| E[Connection Blocked]
        E --> F[Publisher Waits]
        F --> G{Threshold<br/>Cleared?}
        G -->|No| F
        G -->|Yes| H[Connection Unblocked]
        H --> A
    end

    style E stroke:#ef4444,stroke-width:3px
    style H stroke:#10b981,stroke-width:2px

Den viktigaste insikten är att RabbitMQ inte bara lägger ned meddelanden under tryck, det saktar ner källan. Det här är en mycket mer civiliserad metod än att skämtvisa bort data.

Konsumenterkännelser

Konsumenterna kontrollerar hastigheten genom erkännelser ( ACKs och NACKs ). Ett meddelande är inte borttaget från köen tills konsumenten explicit erkänner det, . Om en konsument inte gör det, ' inte tar ACK- meddelanden tillräckligt snabbt, , sträcker sig köen upp, M SK6 vilket i slutändan driver flödetskontroll uppstream.

Du kan också använda förfetch gränser (, QoS, ), för att kontrollera hur många okända meddelanden en konsument kan få i flyg på samma gång.

// Set prefetch count to limit unacknowledged messages
channel.BasicQos(prefetchSize: 0, prefetchCount: 10, global: false);

Detta säger åt RabbitMQ: "Snälla skicka mig messages på samma gång . När jag har fått några,, kan du skicka fler,." Det är konsumenten som uttryckligen säger hur mycket tryck den kan hantera .NET-klientdokumentation täcker API i detalj.

Hur andra system hanterar baktryck

De mönsterna är universella, men implementeringarna skiljer sig. Här är hur några andra populära kommunikationssystem möter samma problem.

Kafka: Konsumenten-Kontrollerad val

Apache Kafka är en distribuerad event-streaming-plattform designad för hög - , genomspetsning, , , fel, - , tolerans med meddelanden och . . Till skillnad från traditionella messag-operatörer, lagrar Kafka meddelanden i en appen , - ”, bara registrerar dem och låter kunderna följa sin egen position.

Kafka tar ett helt annat tillvägagångssätt att dra meddelanden istället för att trycka på dem. Kafka Consumer-dokumentation för mer information om valmodellen.

// Kafka consumer with explicit backpressure control
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe("orders");

while (!cancellationToken.IsCancellationRequested)
{
    // Only fetch what you can handle - this IS your backpressure
    var result = consumer.Consume(timeout: TimeSpan.FromSeconds(1));

    if (result != null)
    {
        await ProcessMessageAsync(result.Message.Value);

        // Manual commit = explicit acknowledgement
        consumer.Commit(result);
    }

    // If processing is slow, you simply poll less frequently
    // Kafka doesn't push more messages at you
}

Den smarta biten : Kafka's konsumentgrupper återbalanserar automatiskt partitionerna M SK2 Om en konsument halkar efter , kan du lägga till fler konsumenter i gruppen och partitioner blir omfördelade MSC4 baktrycket blir ett skalerande beslut

// Control batch size to manage memory pressure
var config = new ConsumerConfig
{
    BootstrapServers = "localhost:9092",
    GroupId = "order-processors",
    AutoOffsetReset = AutoOffsetReset.Earliest,
    MaxPollIntervalMs = 300000,      // 5 mins max between polls
    MaxPartitionFetchBytes = 1048576, // 1MB max per partition fetch
    FetchMaxBytes = 52428800          // 50MB max total fetch
};

Azure Service Bus: samtida budskapkontroll

Azure Service Bus är Microsofts fullkomligt skötta företags meddelandesmakare med queues och publiceringar.

Azure Service Bus använder MaxConcurrentCalls stället på att' är vackert enkelt — det kontrollerar hur många meddelanden din processor hanterar samtidigt att processa meddelanden täcker prestationen i detalj.

var processor = client.CreateProcessor("orders-queue", new ServiceBusProcessorOptions
{
    // This IS your backpressure - only process 10 at a time
    MaxConcurrentCalls = 10,
    AutoCompleteMessages = false,
    PrefetchCount = 20  // Buffer 20 messages locally
});

processor.ProcessMessageAsync += async args =>
{
    try
    {
        await ProcessOrderAsync(args.Message.Body.ToString());
        await args.CompleteMessageAsync(args.Message);
    }
    catch (Exception ex)
    {
        // Abandon returns message to queue for retry
        await args.AbandonMessageAsync(args.Message);
    }
};

processor.ProcessErrorAsync += args =>
{
    Console.WriteLine($"Error: {args.Exception.Message}");
    return Task.CompletedTask;
};

await processor.StartProcessingAsync();

Azure Service Bus stödjer också -sessioner för Auftragsbearbeitning och död- bokstäver köer för meddelanden som misslyckas upprepade gånger

AWS SQS: Visibility Timeout Dance

Amazon Simple Queue Service (SQS) är ett fullt skött meddelande queuing service från AWS.

SQS använder syntydsperrningar som dess baktryckmekanism.

var sqsClient = new AmazonSQSClient();

// Receive with explicit backpressure control
var response = await sqsClient.ReceiveMessageAsync(new ReceiveMessageRequest
{
    QueueUrl = queueUrl,
    MaxNumberOfMessages = 10,           // Batch size = backpressure control
    WaitTimeSeconds = 20,               // Long polling
    VisibilityTimeout = 300             // 5 mins to process before retry
});

foreach (var message in response.Messages)
{
    try
    {
        await ProcessAsync(message.Body);

        // Only delete after successful processing
        await sqsClient.DeleteMessageAsync(queueUrl, message.ReceiptHandle);
    }
    catch
    {
        // Don't delete - message will become visible again after timeout
        // Optionally, change visibility timeout to retry sooner
        await sqsClient.ChangeMessageVisibilityAsync(queueUrl,
            message.ReceiptHandle, visibilityTimeout: 0);
    }
}

Den smarta SQS-tricken ApproximateNumberOfMessages för att övervaka ködjupet och automatiserat

var attributes = await sqsClient.GetQueueAttributesAsync(new GetQueueAttributesRequest
{
    QueueUrl = queueUrl,
    AttributeNames = new List<string> { "ApproximateNumberOfMessages" }
});

var depth = int.Parse(attributes.Attributes["ApproximateNumberOfMessages"]);

if (depth > 1000)
{
    // Signal to scale up consumers
    await TriggerAutoScalingAsync();
}

NATS JetStream: Flow Control Built In

NATS är en lightweight, hög JetStream är NATS, ', byggt, -, i en uthållig lager som tillhandahåller hållbara flöden. ,, exakt, M SK3, först levererad,, och —, vilket är viktigt för oss,explicit flödeskontroll.

NATS JetStream har explicit flödeskontroll med konsumenterkännelser och max upphängande meddelandesgränser:

var js = connection.CreateJetStreamContext();

var subscription = js.PushSubscribeAsync("orders.>", (sender, args) =>
{
    try
    {
        ProcessMessage(args.Message.Data);
        args.Message.Ack();
    }
    catch
    {
        args.Message.Nak();  // Negative ack - redeliver
    }
}, new PushSubscribeOptions.Builder()
    .WithConfiguration(new ConsumerConfiguration.Builder()
        .WithMaxAckPending(100)     // Max unacked messages - THIS is backpressure
        .WithAckWait(30000)         // 30 seconds to ack
        .Build())
    .Build());

Den vanliga tråden

Notera vad alla dessa system har gemensamt

  1. Explicita lotsmängder/konkurensgränser - du kontrollerar hur mycket du
  2. Erkänning-baserad ström - meddelanden stannar tillgängliga tills du bekräftar bearbetandet
  3. Timeout-baserad återhämtning - om du misslyckas, kommer meddelanden att återkomma för att pröva igen
  4. Längsstyrning - kan du alltid fråga

Syntaxen skiljer sig, men dansen är densamma. "" Här är vad jag kan hantera. Hur mycket kan jag hantera? "" "" Sa till mig när jag klarade det. ""

C# Kodexexamlar

Regnerkant, släpper ' in i koden . Här är praktiska exempel på att implementera och reagera på baktrycket i dina C-applikationer

Uppföljning av ködepten

Först och främst: —, du kan , ', man kan inte sköta vad du kan, ', man cannot mäta, ., här kan man se hur många meddelanden väntar på kö:

var queue = channel.QueueDeclare(
    queue: "tasks",
    durable: true,
    exclusive: false,
    autoDelete: false);

Console.WriteLine($"Messages ready: {queue.MessageCount}");

// React to queue depth
if (queue.MessageCount > 1000)
{
    Console.WriteLine("Queue backing up - consider throttling producers");
}

Den här biten kontrollerar hur många meddelanden väntar.

Publisher bekräftar

Publisher bekräftar låter dig veta när RabbitMQ har fått och bearbetat din meddelande framgångsrikt. Om erkännelser saktar nerM SK1 så är det' ett tydligt tecken på baktryck

// Enable publisher confirms
channel.ConfirmSelect();

var body = Encoding.UTF8.GetBytes("Hello, Queue!");

channel.BasicPublish(
    exchange: "",
    routingKey: "tasks",
    basicProperties: null,
    body: body);

// Wait for confirmation - timeout indicates backpressure
bool confirmed = channel.WaitForConfirms(TimeSpan.FromSeconds(5));

if (!confirmed)
{
    Console.WriteLine("Message not confirmed - broker may be under pressure");
}

Om RabbitMQ kämpar med problem

För högupplösta scenarior, -, m sk1 vill du ha asynkrona bekräftelseer, mnsk3

channel.ConfirmSelect();

var outstandingConfirms = new ConcurrentDictionary<ulong, string>();

channel.BasicAcks += (sender, ea) =>
{
    if (ea.Multiple)
    {
        var confirmed = outstandingConfirms.Where(k => k.Key <= ea.DeliveryTag);
        foreach (var entry in confirmed)
        {
            outstandingConfirms.TryRemove(entry.Key, out _);
        }
    }
    else
    {
        outstandingConfirms.TryRemove(ea.DeliveryTag, out _);
    }
};

channel.BasicNacks += (sender, ea) =>
{
    // Message was rejected - implement retry logic
    Console.WriteLine($"Message {ea.DeliveryTag} was nacked - broker under pressure");
    // Back off before retrying
};

Försök igen med Exponential Backoff

När du upptäcker baktrycket, så är det värsta du kan göra att direkt pröva om full hastighet. Det är som att reagera på trafikstockning genom att trycka på acceleratorn hårdare. Istället, implementera exponentiell bakslag.

public async Task PublishWithBackpressureAsync(
    IModel channel,
    byte[] body,
    int maxRetries = 5)
{
    int attempt = 0;

    while (attempt < maxRetries)
    {
        try
        {
            channel.ConfirmSelect();
            channel.BasicPublish(
                exchange: "",
                routingKey: "tasks",
                basicProperties: null,
                body: body);

            if (channel.WaitForConfirms(TimeSpan.FromSeconds(5)))
            {
                return; // Success
            }

            throw new Exception("Publish not confirmed");
        }
        catch (Exception ex)
        {
            attempt++;

            if (attempt >= maxRetries)
            {
                throw new Exception($"Failed to publish after {maxRetries} attempts", ex);
            }

            // Exponential backoff: 1s, 2s, 4s, 8s, 16s
            var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt - 1));
            Console.WriteLine($"Backpressure detected - retry {attempt} after {delay}");

            await Task.Delay(delay);
        }
    }
}

Det här påminner om HTTP 's 429 | (Too många begäran ) mönster M SK4 Istället för att hamma brokern, sätter vi stilla innan vi försöker igen MSC6 och ger systemet tid att återhämta sig

Att använda kanaler för In-Process baktryck

Om du bygger en inre ledning, så är det så här: Channel<T> erbjuder elegant baktrycksstöd:

// Create a bounded channel - backpressure is automatic
var channel = Channel.CreateBounded<WorkItem>(new BoundedChannelOptions(100)
{
    FullMode = BoundedChannelFullMode.Wait // Block producer when full
});

// Producer - will automatically wait when channel is full
async Task ProduceAsync(ChannelWriter<WorkItem> writer)
{
    for (int i = 0; i < 10000; i++)
    {
        var item = new WorkItem { Id = i };

        // This awaits if the channel is at capacity
        await writer.WriteAsync(item);

        Console.WriteLine($"Produced item {i}");
    }

    writer.Complete();
}

// Consumer - processes at its own pace
async Task ConsumeAsync(ChannelReader<WorkItem> reader)
{
    await foreach (var item in reader.ReadAllAsync())
    {
        // Simulate slow processing
        await Task.Delay(100);
        Console.WriteLine($"Processed item {item.Id}");
    }
}

// Run both concurrently
await Task.WhenAll(
    ProduceAsync(channel.Writer),
    ConsumeAsync(channel.Reader)
);

Den begränsade kanalen använder automatiskt baktrycket, —, producent blockerar när kanalen är full.,, saktar ned på ett naturligt sätt för att matcha konsumentens hastighet.

Ett fullständigt baktryck-Ärligt publicerat

Här är ett mer komplett exempel som sammanför övervakning. ständiga meddelanden för att se till att meddelanden överlev دلال restarts:

public class BackpressureAwarePublisher : IDisposable
{
    private readonly IConnection _connection;
    private readonly IModel _channel;
    private readonly string _queueName;
    private readonly int _queueDepthThreshold;

    public BackpressureAwarePublisher(
        string hostName,
        string queueName,
        int queueDepthThreshold = 1000)
    {
        // See https://www.rabbitmq.com/docs/connections for connection best practices
        var factory = new ConnectionFactory { HostName = hostName };
        _connection = factory.CreateConnection();
        _channel = _connection.CreateModel(); // Creates a channel - see https://www.rabbitmq.com/docs/channels
        _queueName = queueName;
        _queueDepthThreshold = queueDepthThreshold;

        _channel.QueueDeclare(
            queue: queueName,
            durable: true,
            exclusive: false,
            autoDelete: false);

        _channel.ConfirmSelect();
    }

    public async Task<bool> PublishAsync(byte[] body, CancellationToken ct = default)
    {
        // Check queue depth first
        var queueInfo = _channel.QueueDeclarePassive(_queueName);

        if (queueInfo.MessageCount > _queueDepthThreshold)
        {
            Console.WriteLine($"Queue depth {queueInfo.MessageCount} exceeds threshold - applying backpressure");

            // Wait for queue to drain a bit
            while (queueInfo.MessageCount > _queueDepthThreshold * 0.8)
            {
                await Task.Delay(1000, ct);
                queueInfo = _channel.QueueDeclarePassive(_queueName);
            }
        }

        // Publish with retry
        for (int attempt = 1; attempt <= 3; attempt++)
        {
            try
            {
                var properties = _channel.CreateBasicProperties();
                properties.Persistent = true;

                _channel.BasicPublish(
                    exchange: "",
                    routingKey: _queueName,
                    basicProperties: properties,
                    body: body);

                if (_channel.WaitForConfirms(TimeSpan.FromSeconds(5)))
                {
                    return true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Publish attempt {attempt} failed: {ex.Message}");
            }

            if (attempt < 3)
            {
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)), ct);
            }
        }

        return false;
    }

    public void Dispose()
    {
        _channel?.Dispose();
        _connection?.Dispose();
    }
}

De bästa metoderna

För en överblick på hur man bygger pålitliga system med RabbitMQ,, se den officiella Tillförlitlighetshandboken.

Stanna lugna

Stanna inte i panik när köerna växer. 1. En bit djup är normalt och hälsosamt. 2. Det betyder att din system absorberar lastspetsar graciöst. 3. målet är att stabila kö som inte växer obegränsat

Titta på ködjupet över tiden... Leta efter trender.,, inte några korta bilder, .. En kö som consistentt står vid 100, meddelanden är bra.

Var pragmatisk

Applicera mönster på ett pragmatiskt sätt. Alla meddelanden behöver inte utlagaren bekräftar . Alla köer behöver inte sofistikerad bakpressurbehandling M SK2 En kö som processar 10 meddelanden per timme behöver förmodligen inte ' samma dragbarhetsteknik som en enda processing | 10,000 | per sekund

Fråga dig själv.: " Vad är den faktiska kostnaden ifall detta meddelande förloras eller fördröjers? ?" Om svaret är: ", inte mycket, ,", inte ', inte över -, ingenjör ., om svaret är "" ", stor påverkan på finansen eller datans integritet "", ,", investera i rätt backpressurbehandling.

Skala Smart

När köer håller på att komma tillbaka, så är svaret: "'". Det är inte alltid så. " "", "" lisää fler producenter "", ." "", det är som att försöka fixa trafikstockning genom att lägga till fler bilar.

Tänk på:

  • Skala konsumenter först - kan du lägga till fler anställda för att processa backlogn
  • Se om bottlenecks - är en långsam nedströms beroende som orsakar bakställningen
  • Koll där möjligt - kan konsumenter processa flera meddelanden på samma gång
flowchart TD
    A[Queue Growing] --> B{Consumer<br/>Saturated?}
    B -->|Yes| C[Add Consumers]
    B -->|No| D{Downstream<br/>Bottleneck?}
    D -->|Yes| E[Fix/Scale Downstream]
    D -->|No| F{Can Batch<br/>Process?}
    F -->|Yes| G[Implement Batching]
    F -->|No| H[Accept Higher Latency<br/>or Reduce Load]

    style C stroke:#10b981,stroke-width:2px
    style E stroke:#f59e0b,stroke-width:2px
    style G stroke:#0ea5e9,stroke-width:2px

övervakning och varning

Använd RabbitMQ Management- Plugin eller HTTP API för komplext övervakning. Installera varningar för:

Du vill veta om baktryck före det blir en kris, inte när ditt system redan har gått under,

Slutsatsen

"Backpressure" är inte bara "rate limiting", det är en överlevnadstaktik. . Genom att hantera det som ett samtal mellan producent och konsument, skapar man system som är motståndskraftiga under tryck.

De viktigaste insikterna:

  1. Backpressure är feedback - producenter och konsumenter samarbetar för att hitta ett hållbart genomskott
  2. Monitora ködjupet - du kan ' kan inte sköta vad du kan
  3. Använd förläggare bekräftar - vet när brokern kämpar
  4. Implementera exponentiella backoff - inte hammera ett system som ' redan är under tryck
  5. Skala konsumenter, inte bara producenter - fixa flaskhalvan , inte symptomet

När ditt system säger "" ", jag, ', jag är full "", ",, ge mig en minut" och "" ,", "" är det rätta svaret.

Stanna lugna när köerna växer lite. , och kom ihåg att ett system med elegant baktryck är oändligt bättre än ett som

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.