Message queue architecture: Kafka / RabbitMQ / RocketMQ topology and ordering guarantees

puml.online

Message queues are the glue of distributed systems — Kafka for throughput, RabbitMQ for flexibility, RocketMQ for transactions. This is the architecture topology of three mainstream MQs, consumer groups, partition ordering, exactly-once semantics, message backlog troubleshooting, dead-letter practice.

Three MQ comparison

Dimension Kafka RabbitMQ RocketMQ
Origin LinkedIn Erlang/finance Alibaba
Model distributed log queue + exchange queue + topic
Throughput millions/sec 10k/sec 100k/sec
Latency 10-100ms 1-10ms 5-50ms
Ordering within partition within queue (single consumer) within queue
Transaction weak (0.11+ supports) not supported supported
Best for logs, stream compute enterprise messaging, flexible routing e-commerce transactions

Kafka cluster architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@startuml
skinparam componentStyle rectangle
skinparam defaultTextAlignment center

title "Kafka Cluster Architecture"

node "Kafka Broker 1" {
queue "Topic: orders (Partition 0)" as p1_0
queue "Topic: orders (Partition 1)" as p1_1
queue "Topic: orders (Partition 2)" as p1_2
}

node "Kafka Broker 2" {
queue "Topic: orders (Partition 0 replica)" as p2_0
queue "Topic: orders (Partition 1 replica)" as p2_1
queue "Topic: orders (Partition 2 replica)" as p2_2
}

node "Kafka Broker 3" {
queue "Topic: payments (Partition 0)" as p3_0
queue "Topic: payments (Partition 1)" as p3_1
}

node "ZooKeeper / KRaft" as ZK {
component "Cluster Metadata" as cm
}

participant "Producer\n(Order Service)" as prod
participant "Consumer Group A\n(Inventory Service)" as cg_a
participant "Consumer Group B\n(Analytics Service)" as cg_b

prod --> p1_0 : "key=user_id"
prod --> p1_1 : "key=user_id"
prod --> p1_2 : "key=user_id"

cg_a --> p1_0 : "consume"
cg_a --> p1_1 : "consume"
cg_a --> p1_2 : "consume"

cg_b --> p1_0 : "consume (independent offset)"
cg_b --> p1_1 : "consume"
cg_b --> p1_2 : "consume"

prod --> p3_0
cg_a --> p3_0
cg_b --> p3_0
cg_b --> p3_1

ZK <-- p1_0 : "metadata"
ZK <-- p1_1 : "metadata"
ZK <-- p3_0 : "metadata"

@enduml

Key concepts:

  • Topic — message category (orders payments)
  • Partition — topic physical shard, ordered within partition
  • Replica — multiple copies of partition, leader + followers
  • Consumer Group — group of consumers, each partition consumed by only one consumer in the group
  • Offset — consumption position, independently maintained per group

Kafka ordering guarantee

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@startuml
title "Kafka Ordering Guarantee"

skinparam componentStyle rectangle

partition "Topic: orders" {
queue "Partition 0" as p0
queue "Partition 1" as p1
queue "Partition 2" as p2
}

participant "Producer" as Prod
participant "Consumer A" as A
participant "Consumer B" as B

note over Prod
key = user_id
same user routes to same partition
end note

Prod -> p0 : order_1 (user=alice)
Prod -> p1 : order_1 (user=bob)
Prod -> p0 : order_2 (user=alice)
Prod -> p2 : order_1 (user=charlie)
Prod -> p0 : order_3 (user=alice)

note right of p0
p0 internal order:
① order_1 (alice)
② order_2 (alice)
③ order_3 (alice)
✅ same user ordered
end note

p0 --> A : consumer A reads
p1 --> B : consumer B reads
p2 --> A

note over A, B
No order across partitions
Order within partition
end note

@enduml

Key:

  • same key → same partition → same consumer (within group)
  • different keys → different partitions → possibly parallel
  • global order impossible (unless single partition + single consumer)

Kafka Exactly-Once Semantics (EOS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@startuml
title "Kafka Exactly-Once with Transactional API"

participant "Producer" as P
participant "Kafka Broker" as K
participant "Consumer" as C
database "DB" as DB

P -> P : ① initTransactions()
P -> P : ② beginTransaction()

loop process N messages
P -> DB : ③ business write (local tx)
P -> K : ④ send offset + record (same tx)
end

P -> P : ⑤ commitTransaction()

note right of P
Kafka transaction guarantees:
- atomic: consume offset + business write either both succeed or both fail
- idempotent: ProducerId + SequenceNumber dedup
- non-duplicate: consumer only reads committed
end note

C -> K : ⑥ read_committed (only committed)
C -> C : ⑦ process message
C -> DB : ⑧ write result
C -> K : ⑨ sendToNext (forward)

@enduml

Three EOS implementations:

  1. Idempotent Producer — ProducerId dedup, cannot cross producer
  2. Transactional API — atomic write across partition + offset
  3. Read-process-write — Consumer writes to new topic, downstream idempotent

RabbitMQ Exchange topology

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@startuml
title "RabbitMQ Exchange Types"

skinparam componentStyle rectangle

participant "Producer" as P

rectangle "Exchange Types" {
component "Direct Exchange\n(routing_key exact match)" as direct
component "Topic Exchange\n(routing_key pattern match *.order.*)" as topic
component "Fanout Exchange\n(broadcast all queues)" as fanout
component "Headers Exchange\n(header-based routing)" as headers
}

queue "Queue: order.new" as q1
queue "Queue: order.paid" as q2
queue "Queue: order.cancelled" as q3
queue "Queue: analytics" as qa
queue "Queue: audit" as qau

P -> direct : "routing_key='order.new'"
direct --> q1 : "routing_key='order.new'"
direct --> q2 : "routing_key='order.paid'"
direct --> q3 : "routing_key='order.cancelled'"

P -> topic : "routing_key='cn.order.new'"
topic --> q1 : "match *.order.new"
topic --> q2 : "match *.order.paid"
topic --> qa : "match *.order.*"

P -> fanout : "broadcast"
fanout --> q1
fanout --> q2
fanout --> q3
fanout --> qau

@enduml

Exchange type selection:

  • Direct — one-to-one exact routing (order status dispatch)
  • Topic — pattern routing (multi business domain)
  • Fanout — broadcast (config change notification)
  • Headers — metadata routing (complex scenarios)

RabbitMQ ack + Dead Letter Queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@startuml
title "RabbitMQ Dead Letter Queue (DLQ) Flow"

participant "Producer" as P
queue "Main Queue" as MQ
queue "Retry Queue (TTL=30s)" as RQ
queue "Dead Letter Queue" as DLQ
participant "Consumer" as C

P -> MQ : ① publish
MQ -> C : ② deliver

alt Consumer ACK
C -> MQ : ③ ack (success)
else Consumer NACK
C -> MQ : ④ nack (requeue=false)
MQ -> DLQ : ⑤ dead-letter (failed beyond max retries)
end

note right of MQ
x-dead-letter-exchange config:
failed messages auto-forward to DLX
end note

note right of RQ
delay retry queue:
- TTL expires
- auto back to main queue
end note

DLQ -> C : ⑥ manual / monitor consume

@enduml

DLQ use cases:

  • business retry useless — order duplicated creation exceeds 3 attempts
  • message format error — JSON parse fails
  • downstream service failure — persists 1 hour

RocketMQ transactional message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@startuml
title "RocketMQ Transactional Message"

participant "Producer" as P
participant "Broker" as B
participant "Local DB" as DB
participant "Consumer" as C

P -> DB : ① local tx (create order, status=pending)
DB --> P : ② OK

P -> B : ③ send half message (consumer invisible)
B --> P : ④ OK

P -> DB : ⑤ execute local tx commit
DB --> P : ⑥ OK
P -> B : ⑦ commit (send commit msg)
B --> P : ⑧ OK

B -> C : ⑨ deliver (consumer visible)
C -> C : ⑩ process (reserve inventory etc)
C -> B : ⑪ ACK

note right of B
Half message mechanism:
- message persisted
- invisible to consumer
- waits for producer confirm
end note

note over P, B : reverse check mechanism
B -> P : ⑫ reverse query (producer down)
P -> B : ⑬ commit / rollback

@enduml

RocketMQ transaction guarantees local tx + message send eventual consistency — order success → message always delivered.

Message backlog troubleshooting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@startuml
title "Message Backlog Investigation"

start

:Kafka consumer lag suddenly spikes;

:check consumer group lag;
note right
kafka-consumer-groups.sh
--describe --group <name>
end note

if (lag high on single partition)
:check consumer instance for that partition;
:check consumer GC / CPU;
:check downstream DB slow query;
elseif (lag high on all partitions)
:consumer overall slow;
:check broker disk IO;
:check network bandwidth;
else
:check producer rate;
:real burst or fake;
end

if (consumer instances insufficient)
:add consumer instances (≤ partition count);
elseif (consumer processing slow)
:optimize code / add batch;
:async write DB;
:parallel process;
elseif (message format changed)
:consumer parse fail retry infinite loop;
:add DLQ;
end

:verify lag drops;

stop

@enduml

Lag check command (Kafka):

1
2
3
4
5
6
7
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--describe --group inventory-group

# output:
# GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
# inventory-group orders 0 1000 1500 500
# inventory-group orders 1 800 850 50

Message ordering in practice

Scenario: same order’s “create → pay → ship” must process in order

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@startuml
title "Order Event Ordering by Order ID"

participant "Order Service" as OS
participant "Kafka" as K
participant "Order Processor" as OP
participant "Inventory Service" as INV
participant "Shipping Service" as SHIP

OS -> K : ① publish OrderCreated {order_id=123}
OS -> K : ② publish OrderPaid {order_id=123}
OS -> K : ③ publish OrderShipped {order_id=123}

note over OS, K : key=order_id, all events route to same partition

K -> OP : ④ OrderCreated (offset 0)
OP -> OP : ⑤ process create
K -> OP : ⑥ OrderPaid (offset 1)
OP -> OP : ⑦ process payment
OP -> INV : ⑧ reserve stock
K -> OP : ⑨ OrderShipped (offset 2)
OP -> OP : ⑩ process ship
OP -> SHIP : ⑪ create shipment

note right of OP
single partition + single consumer
guarantees ordered processing
but cannot parallelize (same order)
end note

@enduml

Key: key = order_id routes all same-order events to same partition. Single consumer per partition = ordered.

DLQ design pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@startuml
title "DLQ Replay Pattern"

queue "Main Queue" as MQ
queue "DLQ" as DLQ
participant "Consumer" as C
participant "DLQ Replayer" as Replay

MQ -> C : ① consume
C -> C : ② business fail (3 retries)
C -> MQ : ③ NACK (requeue=false)
MQ -> DLQ : ④ forward to DLQ (with reason header)

note right of DLQ
DLQ messages:
- x-death header records fail reason
- original-routing-key
- timestamp
end note

Replay -> DLQ : ⑤ periodic scan DLQ
Replay -> MQ : ⑥ replay back to main (after fix)

@enduml

DLQ consumption strategy:

  • alert — DLQ has messages immediately alert
  • analyze — manual analyze failure reason
  • fix + replay — replay after fix
  • permanent archive — long-term S3 storage

Field foot-guns

  • Kafka partition count fixed, cannot decrease — only increase. Estimate future scale before production.
  • RabbitMQ single queue order — multiple consumers compete same queue, order broken. Use consistent hash exchange.
  • Consumer group rebalance — adding instance pauses briefly (seconds). Use CooperativeStickyAssignor to reduce rebalance.
  • Message body too large — Kafka default 1MB, exceeding rejects. Store large message in S3, Kafka stores reference URL.
  • Consumer didn’t commit offset — restart causes duplicate consumption. Business must be idempotent.
  • DLQ nobody watches — DLQ messages accumulate, data loss. Monitor DLQ depth + alert.
  • Kafka disk full — retention.ms only deletes on expiration. Set log.retention.bytes limit.
  • RocketMQ master-slave sync delay — async replication loses messages. Use SYNC_MASTER + sync flush.
  • Message timestamp inaccurate — Kafka message timestamp is producer write time. Store event timestamp in separate field.

Decision tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
What do you need?
├─ Log streaming / big data pipeline → Kafka
├─ Complex routing / RPC style → RabbitMQ
├─ E-commerce transaction consistency → RocketMQ
├─ Simple async tasks → Redis Streams / Postgres LISTEN
└─ IoT / lots of small messages → MQTT / NATS

Ordering requirement?
├─ Global order → single partition + single consumer (low throughput)
├─ Same key order → key hash partition
└─ No order → multiple partitions parallel

Message volume?
├─ < 1k msg/s → any MQ
├─ 1k-100k msg/s → Kafka / RocketMQ
└─ > 100k msg/s → Kafka + multiple partitions + sharding

Transaction requirement?
├─ Weak (loss acceptable) → any MQ
├─ No loss no dup → Kafka EOS / RocketMQ transaction
└─ Business idempotent + at-least-once → any MQ + idempotent consumer

Minimum start: Redis Streams (simple) or RabbitMQ (single Docker). Mid-scale: Kafka single broker start, production uses 3-broker cluster. High-scale: Kafka + partitions + monitoring + DLQ + auto-scaling.

Remember: message queue is an async decoupling tool, not universal glue. Simple tasks use synchronous calls; cross-service async / burst absorption / event stream uses MQ. Wrong scenario introduces complexity.

  • Title: Message queue architecture: Kafka / RabbitMQ / RocketMQ topology and ordering guarantees
  • Author: puml.online
  • Created at : 2026-07-30 18:00:00
  • Updated at : 2026-08-14 21:34:29
  • Link: https://puml.online/blog/plantuml-message-queue-kafka-en/
  • License: This work is licensed under CC BY-NC-SA 4.0.