The Message Queue: Problem Statement
Asynchronous processing gives a client a confirmation at once and the result later, and it needs a list of pending orders that survives a failure.
A pizza shop keeps taking orders while pizzas are being made. The client gets a confirmation that the order has been placed, not the pizza. The shop keeps a list: order 1, order 2 and so on. A new order is added to the queue, and a finished pizza is removed from it, after which the client pays.
Two things follow from not making the client wait:
- The client does other tasks during that time.
- The pizza maker orders the tasks in the queue by priority.
Then the shop becomes a chain: pizza shop 1, 2 and 3. If pizza shop 3 goes down in a power outage, its takeaway orders are dumped and its delivery orders are sent to the other shops.
A list in memory does not allow this, because the computer shuts down with the shop. The list needs persistence, so it is stored in a database.
Asynchronous processing: benefits
A message queue takes tasks, persists them, assigns them to a server, and reassigns them when the server does not acknowledge in time.
The setup is four servers, S0 to S3, and a database that stores every order: the order ID, the contents and whether it is done. S3 is serving orders 9 and 11 when it crashes.
- A notifier checks each server for a heartbeat every 10 or 15 seconds, and assumes a server that does not respond is dead.
- It queries the database for the orders that are not done and distributes them to the remaining servers. The query also picks up order 3, which another server is already processing, so two servers make the same pizza.
- Load balancing with consistent hashing removes the duplicates. Each server handles a set of buckets, and on a crash the remaining servers only get new buckets added, so order 3 stays where it was.
A message queue, here a task queue, encapsulates the assignment, load balancing, heartbeat and persistence in one thing. RabbitMQ is an example.
Publisher Subscriber Models
In a publisher subscriber model a service publishes a message to a message broker, and the broker sends it to the subscribers.
With requests and responses, S1 calls S0 and S2, and S2 calls S3 and S4. If S4 fails, S2 waits for a timeout and passes it to S1 and the client. S1 has already changed its database, and a retry changes it again.
With a message broker, S1 publishes and sends a success to the client. The broker persists the message and replays it when S2 is back up.
The services are decoupled, delivery is guaranteed at least once, and a new service S6 only registers with the broker.
Two disadvantages:
- No atomicity. Account A has 1000. A transfer of 950 deducts a commission of 50 while the fund transfer service is down. A second transfer of 800 deducts another 50, leaving 900. When the service runs, the 950 fails and the 800 succeeds, leaving 100 where 0 was expected.
- No idempotency. A replayed message debits 50 again unless it carries a request ID.