This article contains the basic principles and evolution of interservice interaction using specialized software - Message oriented middleware (MOM), in particular RabbitMQ.
Message oriented middleware (MOM) is а specialized software aimed at working in the environment of several services and for integrating these services by exchanging of asynchronous messages. This article will consider:
- Development of two-link systems and their problems.
- Development of three-link, N-link systems and their problems.
- Problems of synchronous service-to-service interaction.
- Description of asynchronous interaction as an alternative.
- Horizontal scaling of asynchronous systems using MOM.
To understand the pros and cons of MOM, we will compare some approaches in software development:
- The simplest and at the same time very difficult for development two-link architecture.
- More complex and more common three-link or N-link architecture.
- Using of Message oriented middleware for services connection.
Some features of two-link architecture:
- Initial ease of development.
- Well suited for low loads.
- Limited scalability.
- Changing of the API server disrupts client’s work.
- Synchronization of calls.
The two-link architecture is suitable for small applications with a small number of users, queries or data for the reason that its horizontal scaling is difficult.
If you need to change server logic work, you will need to change the client’s work.
- Client directly sent requests to create an record in one table only.
- After the further development of server, it became necessary to make another record in another table with a foreign key. Therefore, the client must be aware of changes on the server.
Some features of three-link architecture:
- Increasing of initial development complexity.
- Better scalability.
- The need to break up large modules (services) with logic.
- The complexity of N modules integration.
- Synchronization of calls.
To solve some problems of two-link applications, the transition to a three- and higher-link system helps.
In a three-link architecture, high-level logic is placed in a separate level and is located on the application server. The client works either indirectly with the database through application servers, or you can abstract from the database and simply state that different logics are in different applications and should not overlap.
In this architecture, it is possible to add server instances (clones) to increase and parallelize the load, i.e., to perform horizontal scaling. You also need to consider a little more complex development and testing - to design and develop so that there is the possibility of horizontal scaling.
Sooner or later, a monolithic application begins to become so complex that it needs to be divided into smaller logical modules/services, thereby facilitating the support and development of these small modules/services. In addition, the duration of work of different logic located on different servers can differ by several orders of magnitude (10, 100 and higher times). This is due to the logic that is in these calls.
After splitting into modules or adding modules/services, the question of the simple integration of these modules into one network arises, without refusing from the advantages of the N link system. In this case, you need to follow a unified approach in building the API.
Message oriented middleware (MOM)
Some properties of Message oriented middleware (MOM):
- Increasing of initial development complexity.
- Communication through messages, not HTTP.
- Messages storage.
- Simple horizontal scaling.
Message oriented middleware is software designed to integrate various modules/services into a single network.
The APIs of this middleware offer a set of functions that allow the integration of many services without going into the infrastructure and implementation of the network. That is, the modules communicate with each other through Middleware and nothing prevents each participant from both receiving and sending entities to another service.
Subsequent changes in the infrastructure and/or network do not entail changes in the applications. Perhaps you only have to change the network address of the Middleware server (if it, of course, moves).
Middleware provides very transparent, open access to a network service.
Middleware is independent of other network services. Middleware architecture is very scalable - it’s enough to just create the clone of the required service.
The main essence of this software is messages that are used instead of the usual HTTP. A message is byte information that should be suitable for the API server, usually in JSON format. In the RabbitMQ implementation, messages are transmitted using the AMQP protocol (Advanced Message Queuing Protocol). In our article, messages and AMQP will be discussed below.
This architecture is asynchronous, i.e., in comparison with three-link applications, there are no locks when requesting from service to service.
It is worth noting that RabbitMQ is a database that can store messages in itself. This feature allows to store the facts of a request from service to service, even if the final recipient (service) is missing, turned off, faulty, etc. Messages are also stored internally when RabbitMQ itself is turned off.
Our project uses such a scheme of communication between services for several reasons:
- There is a large number of services that massively call various APIs of other services, therefore, there are high requirements for response time from each API.
- High resiliency requirements. RabbitMQ keeps messages in itself even in case of accidents.
- High performance requirements. The increase in productivity is solved by including additional servers of our service to the common RabbitMQ, which contributes to horizontal scaling.
Features of communication via AMQP protocol:
- Message is body + headers.
- Exchange is an entry point for most messages.
- Queue is an exit point for most messages.
- Routing Key.
The work of Message Broker, which RabbitMQ represents, can be described as the work of a post office.
Sender client creates a message (letter) with a specific body and a routing key (recipient). Then the client sends this message to the exchange point (exchange), which can be presented in the form of a box for outgoing letters. After that, Message Broker routes this incoming message according to the routing key and places this message in a queue configured to receive messages with this routing key. A queue can be considered as a mailbox for incoming messages, from where the client receives incoming messages.
- Scaling is simplified compared to two-, three-link systems.
- It is necessary to raise several clones of one server.
- Clones must listen on one queue.
It is necessary to take into account the fact that one message that appears in the queue will be read once and only once. This is a plus towards simple scaling - it’s enough to just raise several clones of one service and configure them to read messages from the same queue. Since the same message will not be read by several servers at the same time, conflicts are eliminated.
Synchronous and asynchronous interaction
One of the main differences in the architecture using Message oriented middleware is the fact that the interaction between the services is asynchronous, in contrast to two- and three-link systems, where communication is usually synchronous and is carried out via HTTP. To compare the features of synchronous and asynchronous interaction, the following table is provided:
- The client waits a response from the server. This reduces performance within the service. The time that could be spent on something useful, the service spends waiting for synchronous responses. Especially if the service calls the second service, and the second service also calls the third service, etc. (by chain). Or if they are inserts into 100 tables on a loaded database.
- The client must know the addresses of all the servers he works with. If there is network reconfiguration, then the server moves to other addresses. If the client works with dozens of servers, then reconfiguration can take a lot of time.
- If the server is disconnected/faulty, then the client must handle this error, for example, repeat the request.
- The client does not know when the server will again be able to accept requests. The client must wait for the server to load.
- Possible server crash with a huge number of requests at a time.
- Services communication is sending of requests for possibly long-term work. In asynchronous interaction, sending of such requests depends only on the size of the message itself, usually sending is instant.
- The client does not need to know if there is a server at all. To receive a message, i.e. to be signed, is the prerogative of the recipient server.
- Processing messages “later”. This may happen in the future when the server is free. It means that if the server fails, there are basic settings which will limit the number of received messages.
- Save the fact of the request (message), even if the final server is disconnected. This is impossible with synchronous calls.
- When the service is turned on, it should subscribe to the necessary RabbitMQ queue and only then it will receive a message that was in waiting.
- RabbitMQ has the ability to not acknowledge messages (ack). For example, if the server could not process the message for various reasons - exception, incorrect message format, then the message can be deleted (ack) or returned (reject) to the queue for further processing. This remains the choice of the developer/service.
This article is not a call to use RabbitMQ everywhere, instead, the purpose of the article is to present the pros and cons of some approaches in software design and development to the reader. A balanced choice of an approach should be made individually for each of the developed products.