The labs on this site need JavaScript to run. The same content is written out below.
Learn › Architecture and operations › Messaging and asynchronous processing
Message queue basics
Leave a note instead of calling directly
When an order comes in, you must send a confirmation email. The order server can call the email server directly and wait until it finishes, or it can put a message that says "send the email" in a queue and respond right away.
Conceptual model · A conceptual model (not checked against a real message queue)
Concepts in this lab
- Message queue
- An intermediate store that sits between the sending side (the producer) and the receiving side (the consumer), holds messages, and then delivers them. The producer puts a message in and moves straight on to its next work, and the consumer takes messages out and processes them at its own pace. The two systems do not call each other directly, so coupling is lower, and a sudden burst of requests can be piled up and processed later.
- Synchronous and asynchronous
- Synchronous means that the side that requests a task waits until it receives the result and only then moves on. Asynchronous means that the requester starts the task and moves on to the next work without waiting for the result, and the result is delivered later through a callback, a Future, or a similar means.
- Pub/Sub
- A messaging approach in which a publisher sends a message to a specific topic (channel) and every subscriber that is subscribed to that topic receives the message. Publishers and subscribers do not need to know each other. The Redis version does not store messages, so a subscriber that is not connected at that moment does not receive the message.
- Message delivery guarantees
- The level of guarantee about how many times a message is delivered. With at-most-once, a message is delivered at most one time, so there are no duplicates but messages can be lost. With at-least-once, a message is delivered at least one time, so nothing is lost but duplicates can occur. With exactly-once, a message is delivered exactly one time. In practice, systems often use at-least-once and make the consumer handle duplicates (idempotent).
- Eventual consistency
- The guarantee that, although some nodes may show the old value right after data changes, all nodes end up with the same value over time if no further changes are made. It gives up immediate consistency and gains availability and scalability in return. It is commonly adopted in distributed databases and for synchronizing data between microservices.
Try these
- Default settings — Direct call. The order response is delayed by the time it takes to send the email.
- With a queue — The order responds right away, and the emails go out one after another in the background.
- Direct call while the email server is down — Because of the email, even the order fails.
- Queue while the email server is down — The order succeeds, and the messages pile up and are handled after the recovery.
- When email is very slow — A 3s job. Compare the order response time of the two approaches.
Running with the default settings
- 3 orders arrive one after another. Saving an order to the database takes 0.05s, and sending one confirmation email takes 1s.
- Order 1: After saving (0.05s), the order server calls the email server and waits until it finishes (1s). The user gets a response only after 1.05s.
- Order 2: After saving (0.05s), the order server calls the email server and waits until it finishes (1s). The user gets a response only after 1.05s.
- Order 3: After saving (0.05s), the order server calls the email server and waits until it finishes (1s). The user gets a response only after 1.05s.
Result of this run
- With a direct call, the other side's processing time is added to your response time as it is. If email gets slow, orders get slow too. In return, when the response arrives, you can be sure the email is already sent.
- A queue suits work that does not need a result in the response and can be a little late. Email, notifications, and aggregating statistics are typical examples. Work where you need the result before you can go on, such as payment approval, is done with a direct call.
Code
@PostMapping("/orders")
OrderDto order(@RequestBody OrderRequest req) {
Order order = orderService.save(req); // 0.05s
mailClient.sendConfirm(order); // calls the email server directly and waits
return OrderDto.of(order); // responds only after the email is done
}
Assumptions of this model
This is a model to explain the concept, and it is not the result of running a real message queue. The time values (0.05s to save, 0.01s to put in the queue, 3s timeout) are examples. Even with a direct call, you can add a timeout and exception handling to keep the order from failing, and the cases where the queue itself goes down, message loss, duplicate delivery, and ordering problems are not covered here. @RabbitListener in the code is an example for when you use RabbitMQ.
Previous lab: Rate limiting · Next lab: Kafka partitions and consumers