How to Prevent Message Loss and Ensure Reliable Delivery in Distributed Systems
This article explains practical techniques for detecting lost messages, guaranteeing reliable production, storage, and consumption stages, handling duplicate deliveries with idempotent designs, managing message backlogs, and implementing distributed transactions using transactional messages in modern message queue systems.
1. Detecting Message Loss
Use sequential IDs injected by the producer and verified by the consumer; most MQ clients support interceptors to add IDs before sending and check continuity on receipt. In distributed setups, assign IDs per partition and include a producer identifier when multiple producers are used.
2. Ensuring Reliable Delivery
Production Stage
The producer receives an acknowledgment from the broker; capture exceptions and retry if needed.
try {
producer.send(record).get();
System.out.println("Message sent successfully");
} catch (Exception e) {
System.out.println("Message sending failed");
e.printStackTrace();
}Asynchronous Send
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (metadata != null) {
System.out.println("Message sent successfully");
} else {
System.out.println("Message sending failed");
exception.printStackTrace();
}
}
});Storage Stage
Configure the broker to flush to disk synchronously (e.g., RocketMQ flushDiskType=SYNC_FLUSH) or replicate messages to multiple nodes so that a single broker failure does not cause loss.
Consumption Stage
Process the business logic first, then acknowledge the message manually to avoid premature ACKs.
@RabbitListener(bindings = @QueueBinding(...))
public void onMessage(@Payload Order order, @Headers Map<String,Object> headers, Channel channel) throws Exception {
// business logic
System.out.println("Consumer: " + order);
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
channel.basicAck(deliveryTag, false);
}3. Handling Duplicate Messages
Most MQs provide at‑least‑once delivery, so duplicates are inevitable. Implement idempotent consumption using unique database constraints, conditional updates, version checks, or a token table that records processed message IDs.
4. Dealing with Message Backlog
Optimize producer performance by adjusting batch size and concurrency; optimize consumer performance by scaling instances and matching the number of partitions. When a backlog spikes, determine whether the production rate increased or the consumption rate decreased, then scale or temporarily degrade non‑critical traffic.
5. Distributed Transactions with Transactional Messages
Use half‑messages: the producer sends a pending message, executes the local DB transaction, then commits or rolls back the message. RocketMQ adds a transaction‑check callback so the broker can query the producer for the final state if the commit response is lost.
Consumers only see the message after the transaction is committed, guaranteeing atomicity between the database write and the message publish.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITFLY8 Architecture Home
ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
