Skip to main content
FomiVIP
Tech Sharing

RabbitMQ: Splitting Order Processing From SMS and Notifications

An order endpoint averaged 1.2s, but only 100ms of that was writing the order. We moved SMS, WeCom push, and detailed logging behind RabbitMQ — one message per order, three independent consumers. Average dropped to 200ms. Along the way: persistence, idempotent consumers, and a dead-letter queue.

March 24, 2023RabbitMQArchitectureMessage Queue

An order-creation endpoint averaged 1.2 seconds under load, up past 2 seconds at times. Breaking it down, writing the order table took barely 100 milliseconds; the rest was waiting on three things that don't need an immediate result: sending the confirmation SMS, pushing a message to a WeCom group, and writing a detailed log.

How We Split It

The endpoint now does two things: write the order and publish one message to RabbitMQ. The payload is tiny — just an order_id and an event type. The remaining three actions each got their own consumer, fetching and retrying on their own.

The endpoint now averages a little over 200 milliseconds. Users hit the success page instantly, and an SMS arriving a second or two later goes unnoticed.

1. No Persistence: a Restart Lost 6 Messages

The first version took the defaults for queues and messages to save time. One night the service restarted and 6 pending messages vanished — 6 users never got their confirmation SMS. We only found out from the logs; the users didn't even complain.

The fix: durable queues, delivery_mode 2 on messages, and publisher confirms on the producer. All three together make it solid. The exchange got its persistent flag too.

2. No Idempotency: the SMS Sent Twice

One consumer acked only after finishing its business logic. A network blip meant the ack never landed, the message was redelivered, and the SMS went out a second time. One user received two identical texts.

Moving the ack before the business logic? No — that loses messages. Instead we added a dedup table keyed on order_id plus event_type: only insert-then-send, and duplicates get acked immediately. Same idea as the idempotency keys in our points system.

3. The Dead-Letter Queue

Messages that keep failing can't retry forever — they'd clog the queue. We capped retries at 3, after which messages go to a dead-letter queue, counted in monitoring with an alert past 5. The ops on-call sheet gained a line: "check dead-letter depth".

4. Ordering Isn't Guaranteed

Occasionally the WeCom notification arrives before the order-success log. It looks odd but breaks nothing. We don't force ordering — messages carry an occurred_at field, and display sorts by it. Trading throughput for a single-partition queue just to preserve order isn't worth it here.

Afterward

The three consumers can now be paused independently. Once, when the SMS provider had an outage, we stopped that consumer for two hours — orders kept flowing, and everything queued up and sent after recovery. Before, one SMS outage would have stalled ordering with it.

Those 6 lost messages were re-sent from the order table afterward, and we noted it in the docs: never trust default configurations.

Consulting · Free initial chat

Need advice for your scenario?

Tell us your business context and current systems, and we'll give an actionable approach.

Contact us

Mon–Fri 9:00–18:00 · We respond on business days within one business day.

WeChat (Business Only)
WeChat (Business Only)二维码

Scan and add our WeChat for consulting

Or fill out the requirements form