Using Asynchronous Messaging to Improve Availability

    imageHi, habrozhiteli! We recently handed over a book by Chris Richardson to the printing house , the purpose of which is to teach how to successfully develop applications using the microservice architecture. The book discusses not only the advantages, but also the disadvantages of microservices. You will learn in what situations it makes sense to apply them, and when it is better to think about a monolithic approach.

    The book focuses on architecture and design. It is designed for anyone whose responsibilities include writing and delivering software, including developers, architects, technical directors and heads of development departments.

    The following is an excerpt from the book Using Asynchronous Messaging

    Using Asynchronous Messaging to Improve Availability


    As you have seen, the various IPC mechanisms push you to various compromises. One of them is related to how IPC affects accessibility. In this section, you will learn that synchronous interaction with other services as part of request processing reduces the availability of the application. In this regard, when designing your services, you should use asynchronous messaging whenever possible.

    First, let's see what problems synchronous interaction creates and how it affects accessibility.

    3.4.1. Synchronized interaction reduces availability


    REST is an extremely popular IPC engine. You may be tempted to use it for interservice communication. But the problem with REST is that it is a synchronous protocol: the HTTP client has to wait until the service returns a response. Each time the services communicate with each other via a synchronous protocol, this reduces the availability of the application.

    To understand why this happens, consider the scenario shown in Fig. 3.15. The Order service has a REST API for creating orders. To check the order, he turns to the Consumer and Restaurant services, which also have a REST API.

    image

    Creating an order consists of this sequence of steps.

    1. The client makes an HTTP POST / orders request to the Order service.
    2. The Order service retrieves customer information by making an HTTP GET / consumers / id request to the Consumer service.
    3. The Order service retrieves restaurant information by executing an HTTP GET / restaurant / id request to the Restaurant service.
    4. Order Taking checks the request using information about the customer and the restaurant.
    5. Order Taking creates an order.
    6. Order Taking sends an HTTP response to the client.

    Since these services use HTTP, all of them must be accessible for FTGO to process the CreateOrder request. It will not be able to create an order if at least one of the services is unavailable. From a mathematical point of view, the availability of a system operation is a product of the availability of services that are involved in it. If the Order service and the two services that it calls have an availability of 99.5%, then their overall availability will be 99.5% 3 = 98.5%, which is much lower. Each subsequent service participating in the request makes the operation less accessible.

    This issue is not unique to REST-based interactions. Availability decreases whenever a service needs to receive responses from other services to respond to a client. Even the transition to a request / response interaction style on top of asynchronous messages will not help here. For example, if the Order service sends a message to the Consumer service through a broker and begins to wait for a response, its availability will deteriorate.

    If you want to maximize accessibility, minimize the amount of synchronous interaction. Let's see how to do it.

    3.4.2. Get rid of synchronous interaction


    There are several ways to reduce the amount of synchronous interaction with other services when processing synchronous requests. Firstly, to completely avoid this problem, all services can be provided with exclusively asynchronous APIs. But this is not always possible. For example, public APIs generally adhere to the REST standard. Therefore, some services are required to have synchronous APIs.

    Fortunately, to process synchronous requests, it is not necessary to execute them yourself. Let's talk about such options.

    Using Asynchronous Interaction Styles

    Ideally, all interaction should occur in the asynchronous style described earlier in this chapter. Imagine, for example, that the FTGO application client uses an asynchronous request / asynchronous response interaction style to create orders. To create an order, he sends a request message to the Order service. Then this service asynchronously exchanges messages with other services and eventually returns a response to the client (Fig. 3.16).

    image

    The client and service communicate asynchronously, sending messages through channels. None of the participants in this interaction is blocked waiting for a response.

    Such an architecture would be extremely robust, because the broker buffers messages until their consumption is possible. But the problem is that services often have an external API that uses a synchronous protocol like REST and, as a result, must immediately respond to requests.

    If the service has a synchronous API, accessibility can be improved through data replication. Let's see how it works.

    Data replication

    One way to minimize synchronous interaction during query processing is to replicate data. The service stores a copy (replica) of the data that it needs to process requests. To keep the replica up to date, it subscribes to events published by the services to which this data belongs. For example, an Order service may store a copy of data belonging to the Consumer and Restaurant services. This will allow him to process requests for creating orders without resorting to these services. Such an architecture is shown in fig. 3.17.

    image

    The Consumer and Restaurant services publish events whenever their data changes. Order service subscribes to these events and updates its replica.

    In some cases, data replication is a good solution. For example, Chapter 5 describes how the Order service replicates Restaurant service data to be able to check menu items. One of the drawbacks of this approach is that sometimes it requires copying large amounts of data, which is inefficient. For example, if we have many customers, storing a replica of the data belonging to the Consumer service may be impractical. Another disadvantage of replication lies in the fact that it does not solve the problem of updating data belonging to other services.

    To solve this problem, a service can delay interaction with other services until it responds to its client. This will be discussed further.

    Completion of processing after the response is returned

    Another way to eliminate synchronous interaction during request processing is to perform this processing in the following steps.

    1. The service checks the request only with the help of data available locally.
    2. It updates its database, including adding messages to the OUTBOX table.
    3. Returns the response to its client.

    During the processing of the request, the service does not synchronously access any other services. Instead, he sends them asynchronous messages. This approach provides poor connectivity of services. As you will see in the next chapter, this process is often implemented as a narrative.

    Imagine that the Order service acts in this way. He creates an order with the status PENDING and then checks it by exchanging asynchronous messages with other services. In fig. Figure 3.18 shows what happens when the createOrder () operation is called. The chain of events looks like this.

    1. The Order service creates an order with the status PENDING.
    2. Order service returns a response with order ID to its client.
    3. The Order service sends a ValidateConsumerInfo message to the Consumer service.
    4. The Order service sends a ValidateOrderDetails message to the Restaurant service.
    5. The Consumer service receives a ValidateConsumerInfo message, checks to see if the customer can place an order, and sends a ConsumerValidated message to the Order service.
    6. The Restaurant service receives a ValidateOrderDetails message, checks the correctness of menu items and the restaurant's ability to deliver an order to a given address, and sends an OrderDetailsValidated message to the Order service.
    7. Order service receives ConsumerValidated and OrderDetailsValidated messages and changes the order status to VALIDATED.

    And so on ...

    The Order service can receive the ConsumerValidated and OrderDetailsValidated messages in any order. To know which one he received first, he changes the status of the order. If the first message was ConsumerValidated, the order status changes to CONSUMER_VALIDATED, and if OrderDetailsValidated changes to ORDER_DETAILS_VALIDATED. After receiving the second message, the Order service sets the order status to VALIDATED.

    After checking the order, the Order service performs the remaining steps to create it, which we will discuss in the next chapter. A great part of this approach is that the Order service can create an order and respond to the customer, even if the Consumer service is unavailable. Sooner or later, the Consumer service will recover and process all pending messages, which will complete the verification of orders.

    image

    The disadvantage of returning a response before the request is completely processed is that it makes the client more complex. For example, when the Order service returns a response, it gives minimal guarantees about the status of the order that has just been created. He answers immediately, even before checking the order and authorizing the client's bank card. Thus, in order to find out whether the order has been successfully created, the client must periodically request information or the Order service must send him a notification message. Despite the complexity of this approach, in many cases it is worth preferring it, especially because it takes into account the problems with distributed transaction management, which we will discuss in Chapter 4. In chapters 4 and 5, I will demonstrate this technique using the example of the Order service.

    Summary


    • Microservice architecture is distributed, so interprocess communication plays a key role in it.
    • The development of the API service must be approached carefully and carefully. It’s easiest to make backward compatible changes because they don’t affect the way customers work. When making breaking changes to the service API, you usually have to maintain both the old and the new version until the clients are updated.
    • There are many IPC technologies, each with its own strengths and weaknesses. The key decision at the design stage is the choice between synchronous remote procedure call and asynchronous messages. The easiest to use are synchronous protocols like REST, based on the call of remote procedures. But ideally, to increase accessibility, services should communicate using asynchronous messaging.
    • To prevent an avalanche-like accumulation of failures in the system, a client using a synchronous protocol must be able to cope with partial failures - the fact that the called service is either unavailable or exhibits high latency. In particular, when executing requests, it is necessary to count the waiting time, limit the number of overdue requests and apply the “Fuse” template in order to avoid calls to the faulty service.
    • An architecture using synchronous protocols must include a discovery mechanism so that clients can determine the network location of service instances. The easiest way is to focus on the discovery mechanism provided by the deployment platform: on the “Server-side discovery” and “Third-party registration” templates. An alternative approach is the implementation of service discovery at the application level: the Client Discovery and Self-Registration templates. This method requires more effort, but is suitable for situations where services run on multiple deployment platforms.
    • The message and channel model encapsulates the details of the implementation of the messaging system and becomes a good choice when designing this type of architecture. Later, you can tie your architecture to a specific messaging infrastructure, which typically uses a broker.
    • A key difficulty in messaging is the publication and updating of the database. A good solution is to use the Event Publishing template: the message is written to the database at the very beginning as part of the transaction. A separate process then retrieves the message from the database using the Interrogating Publisher or Transactional Log Tracking template, and passes it to the broker.

    »More information on the book can be found on the publisher’s website
    » Contents
    » Excerpt

    For Khabrozhiteley 30% discount on pre-order books on a coupon - Microservices

    Also popular now: