Scaling Workloads Using Messaging
As our applications grow, we must find ways to keep manage software complexity while scaling our application.
One of the most common techniques is to offload work into the background that doesn’t have to be performed immediately. This frees applications to respond back to the mobile or web user immediately, while handling the work at a later time. It also allows other work to occur outside of the normal flow of the application.
A well-known solution to this problem is to use a distributed messaging system. A distributed messaging system enables the application to communicate by sending and receiving messages between various parts of the system.
Using Distributed Messaging to Scale Applications
While messages could be sent directly to other parts of the system, this prevents us from scaling effectively. Instead, we use message brokers to facilitate message communication. Therefore, communication is performed in a loosely coupled way, meaning that the message sender won’t know who will receive the message. The broker is responsible for routing the message to the proper destination.
Distributed messaging scales by increasing the subscribers as needed to handle more unprocessed messages while reducing them with fewer unprocessed messages. Our application doesn’t need to know how many subscribers exist, it only needs to know how to create the message and send it to the broker. This results in a more loosely-coupled application through the composition of multiple services that are deployed and scaled independently, without the rest of the system knowing it. Distributed messaging is the foundation for a microservices architecture.
Core Concepts in Distributed Messaging
There are some core concepts to understand when using distributed messaging:
Message Broker — Middleware that facilitates the interaction between publishers and subscribers via topics and queues. The broker is responsible for mediating the messages between publisher and subscriber, enforcing delivery rules, and handling errors and retries.
Publisher/Producer — The system that is sending a message to the message broker for routing. A publisher may publish to a single or multiple destinations, as desired
Subscriber/Consumer/Client — The system that receives a message from the message broker after it has been properly routed. A single process or service may subscribe to one or more destinations, as desired. Subscribers must use one of the supported network protocols, such as AMQP, to connect and communicate with the message broker
Message — Messages are published to a destination and may be received by one or more subscribers. Messages generally have headers such as the creation time, time-to-live, and contain an opaque body that may be in JSON, XML, or any other format as desired. Some brokers also offer a routing key, to allow namespacing or message typing for more sophisticated routing
Topics — A destination that allows for any number of subscribers, each of which will receive every message routed to the topic
Queues — A destination that allows for any number of subscribers, but only one will receive a message routes to the queue
Message Routing and Exchanges — Brokers may be configured to route messages to destinations based on one or more rules. Rules may also be established to send a message to multiple destinations. Some brokers utilize an exchange as a special destination that accepts incoming messages, prior to routing them to their destination topic(s) and/or queue(s)
Delivery Mode — Commonly, brokers will offer a variety of options for message delivery and persistence. These include persistent messaging (guaranteed delivery) and non-persistent messaging (best effort delivery). Messages that must always be processed and never lost should be persistent, though this will reduce the overall throughput capabilities of the message broker. Messages permitted to be lost due to system failure may use non-persistent delivery and will enable the message broker to operate at a higher throughput rate.
Client Acknowledgement Mode — When a subscriber subscribes to a queue or topic, it specifies how a message is considered acknowledged by the client. Options usually include:
Client acknowledgement — the message is acknowledged upon receiving it from the broker, resending the message to another subscriber if the message is not acknowledged by the specified time (most reliable, least performant)
No-acknowledgement — the client will never acknowledge the message (last reliable, most performant)
Durable Subscription — A method of subscription that identifies a client and requires the message broker to store undeliverable messages on the client’s behalf until they reconnect (store and forward)
Dead Letter Queue (DLQ) — a special queue that stores all messages that could not be delivered due to error
Clusters and Federations — Since a single broker instance would present a single point of failure (SPF), message brokers often offer clustering to enable multiple brokers to operate in a highly available mode. Federations may be used to share messages across regions, with each region hosting at least one cluster of brokers.
How Distributed Messaging Works
Most messaging solutions support two methods of distribution: queue-based and topic-based.
Point-to-Point Messaging Using Queues
Point-to-Point messaging allows a publisher to send a message to a single subscriber from a pool of subscribers. The broker is responsible for selecting the subscriber that will receive the published message for processing. Only one subscriber will receive a message published to the queue, unless the subscriber fails to process the message within a given timeout period.
Fanout (Pub/Sub) Messaging Using Topics
Fanout messaging allows every message published to a topic to be distributed to every subscriber currently registered. The broker doesn’t care if the message was processed by all subscribers, or just a subset of them.
A Basic Messaging Workflow
The following is an example flow of how two parts of an application communicate using a queue available within the message broker:
- Some code in your application defines an exchange E and a queue Q
- It then binds E and Q, so that every message sent to E will be stored in Q
- One service in your application (“Publisher”) sends messages to an exchange E
- The broker routes all incoming messages from E to Q. All messages are stored in Q
- Another service (“Subscriber”) reads and processes each message from Q
All interactions between the Publisher and Subscriber happen through the message broker, rather than directly interacting with each other. This leads to loose coupling and the ability to scale the number of publishers and subscribers independent of one another.