3.3 Synchrony
Synchronous and asynchronous communications are two different forms of interaction that both require the support of a generic technology for distributed systems.
Synchronous communication is characterized by the immediate responses of the communication partners. The communication follows a request/reply pattern that enables the free flow of conversation, often based on the use of busy waits. Applications with user interaction specifically require this conversational mode of interaction. Synchronous communication requires that the client and server are always available and functioning.
Asynchronous communication is less stringent. Both communication partners are largely decoupled from each other, with no strict request/reply pattern. Typically, one party creates a message that is delivered to the recipient by some mediator, and no immediate response is needed. The sender can store context information and retrieve it when the recipient returns the call, but there is not necessarily a response. In contrast to a synchronous request-reply mechanism, asynchronous communication does not require the server to be always available, so this type can be used to facilitate high-performance message-based systems.
Typically, synchronous communication is implemented by RPC-style communication infrastructures, while asynchronous mechanisms are implemented by MOM. However, it is entirely possible to implement synchronous communication based on MOM, and it is also possible to build MOM-style interaction over RPC. Nevertheless, RPC is more suitable if immediate responses are required, and MOM is the technology of choice for decoupled, asynchronous communication.
Due to the manifold requirements of most real-world scenarios, typical enterprise systems embody both synchronous and asynchronous communication. For this purpose, a variety of different communication infrastructures is used, ranging from simple FTP (File Transfer Protocol) to more advanced middleware platforms, such as RPC and MOM. In addition, there are also communication infrastructures that support both communication modesfor example, pipelining RPC, which supports asynchronous communication in addition to the standard synchronous RPC communication.
To conclude this discussion on synchrony, we will provide an overview of the most common ways of implementing synchronous and asynchronous communication with both RPC/ORB and MOM technology. We will look at the following examples:
Simulated synchronous services with queues
Asynchronous one-way: fire-and-forget RPC
Callbacks and polling services
The first example, simulated synchronous communication, can often be found in mainframe environments, where a message queuing system has been chosen as the standard means for remote interaction with the host, such as OS/390 with MQSeries access. This is a common scenario in many large enterprises. Often, these companies have gone one step further, developing frameworks on top of this combination of OS/390 and MQSeries that enable service-like interfaces to the most widely used transactions on the mainframe. This is done by implementing client-service wrappers that shield the underlying MQ infrastructure from the client developer. These service wrappers often simulate synchronous interactions with the mainframe by combining two underlying queues, one with request semantics and the other with reply semantics, using correlation IDs to pair messages into request/reply tuples. Effectively, this relegates the message queuing system to playing a low-level transport function only, not generally leveraging any of the advanced features of the messaging system. Figure 3-9 provides an overview of this approach.
Figure 3-9 Simulated synchronous services with queues. A correlation ID maps a reply message to the corresponding request. On the client side, this is hidden by a service wrapper, which gives the caller the impression of synchrony.
The second example, fire-and-forget RPC, assumes an RPC or ORB implementation with asynchronous one-way semantics: The client fires off a request to the server without expecting an answer. This can be achieved either by defining an operation signature that does not include any return values or by using specific features of the middleware, such as a CORBA IDL operation using the keyword oneway. Figure 3-10 provides an overview of this approach.
Figure 3-10 A synchronous one-way call implies fire-and-forget semantics. The request is fired off by the client without a reply from the server.
There are two key issues with this approach: The first is that the client has no guarantee that the server will receive and process the request appropriately. This problem reduces the applicability of this method significantly. The second problem is that most RPCs/ORBs typically use a reliable communication protocol such as TCP. Sending a one-way request through TCP generally means that the client is blocked until delivery to the server on the TCP level has been completed. If a server is getting swamped with requests, it might become unable to process all incoming one-way requests on the TCP layer. Effectively, this means that the client is blocked until the server is at least able to read the request from the network. Therefore, it is not advisable to use this approach to implement large-scale event notification. Instead, an appropriate MOM should be chosen.
The third example, callbacks and polling services, is the most common way of decoupling clients and server in RPC-style applications, without having to move to a fully fledged MOM solution. The basic idea is similar to the conventional callback, as it is realized in functional programming languages with function pointers, or in OO languages using object references: A client sends a request to the server, and the server stores the request and returns control back to the client (possibly sending an acknowledgment that it received the request). After having processed the request, the server (now acting as a client) sends the result back to the client (now acting as a server), using the standard RPC/ORB invocation mechanism (see Figure 3-11). Sometimes, it is not possible for the client to act as a server (e.g., due to firewall restrictions). In these cases, the client can periodically poll the server for the availability of the result.
Figure 3-11 Callbacks and polling services: A client sends a request to a server ("trigger"). The server stores the requested activity in a database before replying with an acknowledgment to the client. The server has a thread that takes pending requests from the database, processes them, and sends back the result to the originating client using callback.
This approach can sometimes provide a good compromise for satisfying the need to decouple clients and servers without having to add technology such as a MOM. However, often the implementation of such a remote callback can be more difficult than it originally appears. This is especially true if the application requires a high degree of reliability. In these cases, it is necessary to introduce some kind of mechanism for ensuring reliability, such as through combining a server-side database with some kind of custom-built acknowledgment protocol. Also, the server-side logic can become quite complex: To ensure that all requests are eventually processed, the database must be constantly polled for pending requests, potentially adding a huge burden on database performance. For this reason, one should carefully weigh the use of database triggers. Here, it is important to ensure that the execution of the trigger is not part of the same transaction that puts the new request in the database. In this case, you could encounter a situation where the client is blocked because it has to wait not only until the server has stored the request in the database before returning an acknowledgment to the client, but also until the database trigger has been executed. This will effectively eliminate the decoupling effect of the callback implementation.
Figure 3-12 Callbacks and queues. Similar to the previous example, except that queues are introduced on the server side to ensure better decoupling on the server side.
As shown in Figure 3-12, the server-side implementation can alternatively use internal message queues to ensure an efficient means of storing incoming requests in a reliable and efficient manner, thus avoiding many of the issues with the pure-database approach described previously.