HTTP For Inter-Service Communication?
After the evolution of distributed systems, microservices based application attracted interest of nearly every organisation those willing to grow with time and survive the market competition. Microservices allows us to scale and manage system easily. Development time reduced due to distributed effort among many teams and time-to-market new feature reduced significantly.
Due to distributed nature, communication among different components is over the network. And there are so many factors which can affect communication, either it can be security, added latency or abrupt termination of ongoing communication, leading to increased infrastructure cost. Hence either we can fix the network, which exists with numerous problem or we can architect our system to be resilient and reliable over the time.
Services communicate with each other in distributed environment using network protocols. Our solutions to make our system resilient, reliable and faster lies with correct protocol usage too. We have various protocols for different needs and for different network layers (i.e. OSI network model). When we talk about service to service communication or browser to service communication, HTTP usually adopted as a De facto standard. All REST based services adopts this as a standard.
But whether our too much relying on HTTP is correct? HTTP has been created for different purpose, they are created for browsers to back end server communication to retrieve some data using request-response model. And in current world, it is being used for even inter-services communications, reducing our system’s real power.
Here, I’ll cover issues with HTTP and possible available solutions for common use cases. And later I’ll provide some details about RSocket, which can alleviate many of these existing issues and can make our application fully reactive.
HTTP is an application layer protocol in OSI network model. Over the time HTTP has evolved and provided different versions to adopt. Let’s go through them and visualize the issues lies with them.
If a client service wanted to retrieve some data from another service, it will first open a connection with it and then send a request over it to the server. Server will send response and closes the connection. For each request, opening a new connection is must. Hence lots of additional overhead for each request-response cycle and result will be, slow communication.
Fig.1 shows the working of HTTP/1.0 between client and server machines. It also shows the multiple connection required for each request-response cycle.
HTTP/1.1 improves over HTTP/1.0 and provided solution in the form of persistent connection and introduced feature of ‘Pipelining’. Due to this a client can send multiple requests over a single connection, which can remains alive for a configured time only.
Though, situation relatively improved, but this still has a problem, popularly known as ‘Head of Line Blocking’. Due to this issue, if there are multiple requests on a single connection, then those will be queued to server and will be responded in same order only. And, if your client is fast in generating requests or server is slow in responding then that will block other requests to be processed. Hence, congestion in network causing unnecessary delay.
Fig2. Show the HTTP/1.1 working where Request#2 is facing the ‘Head Of Line Blocking’ due to the Request#1. Until the server process the request and respond it with Response#1, Request#2 will wait for processing at Server end.
HTTP/2 improves over HTTP/1.1 and introduced new feature of ‘Multiplexing’. This allowed sending multiple requests as separate streams to server over a single connection and server will send response over the streams back to client. This way inter-service communication is now relatively faster.
As shown in Fig.3, HTTP/2 uses multiplexed channel over a single connection. Over the same channel processed responses are sent which can be interleaved between other response frames. Any delayed or blocked response won’t affect other response.
HTTP/1.x uses text formats JSON, XML etc. for communication as these formats are intended for browsers. For obvious reason, text formats make server response human readable. But when services communicate with each other they doesn’t need response in texts format to interpret. Why would they need it either? If a service is working on an object then it resides in a binary format on that machine, optimized for its processing, and converting that to text before serializing over the network and then at receiver end again deserializing text and converting back to binary structure for further work, is an overhead which slows down the processing speed and increase the processing cost too.
HTTP/2 provides improvement over such issues too. It uses binary protocol instead, which is more efficient to parse and more compact, less error-prone compared to textual protocols. Recently developed gRPC, built on top of HTTP/2 only, uses further improved binary protocol Protobuf, as a mechanism to serialize binary data and resulted data is much simpler, smaller and improves processing speed. As this is an RPC, so it is just like calling remote method as a local method in client service and removes lot of boilerplate code for application level semantics for HTTP usage.
Note: gRPC Protobuf are currently for inter-service communication only and messages are not human readable. gRPC is not for browsers, but support for HTTP/2 is available from many browsers.
By now, it seems HTTP/2 solves the major inter-service communication issue.
Message Flow Control
Let’s consider a use case, where client is continuously bombarding requests to server at a higher rate compared to what server can process. This will overload the server and make it difficult to respond. We need some kind of flow control at application level.
gRPC built on top of HTTP/2 and HTTP/2 uses TCP as transport layer protocol which provides byte level flow control only. This type of flow control won’t be able to throttle requests at application layer. We still need application level flow control or need to implement circuit breakers to keep our system responsive. And sometime need to implement retry logic too to make system more resilient. This is an overhead to build and then manage, which still lies with gRPC and HTTP/2.
Real Time Updates
For another use cases where client always wanted to have the latest data with itself. In such scenario, one of the option is to use HTTP and keep sending polling requests to server to get the updates when available. This way lots of unnecessary requests will be generated resulting in unnecessary traffic and resource usage.
As HTTP provides single interaction model i.e. Request-Response, hence using HTTP is not the right option in such cases.
SSE(Server-Sent Events) is a way to stream real time updates from server to client. But this is a textual protocol.
Another option is to use Websockets (binary protocol) which can also provide real time updates to client over a single connection using bi-directional communication.
As Websockets are connection oriented, any intermittent disconnection in an ongoing communication and response data resumption after connection reestablished within some time, is difficult to manage.
Also, issues arising due to relatively faster producer or faster consumer are existing with Websockets too. Hence need of message level flow control is also required.
Adding to the list, is the complications associated with application level semantics need to be managed while working with Websockets.
We have solution for some issues but those are still a trade with some other issue. A need to have a protocol to hide all issues and complexity within it and provide a simple interface for inter-service communication with capability to support all the use cases with ease.
According to Reactive Manifesto, for optimum resource utilization, distributed systems need to be fully reactive. Such systems are more robust, more responsive, more resilient, more flexible and better positioned to meet modern demands.
Application developed using Reactive Programming only makes service reactive and helps service to better utilize available resources like CPU, memory etc. But being a fully reactive system means all associated IO needs to be reactive too. Commonly IO are associated with DB interaction and service to service communication. R2DBC (Reactive Relational Database Connectivity) and reactive NoSql drivers take care of DB interaction and RSocket is to take care of other area which deals with service to service communication in reactive style. For RSocket there is nothing like client and server. Once client establishes connection with server, both becomes equal and any one can initiate request i.e. communication is bi-directional.
RSocket (or Reactive Socket) is a binary protocol and it breaks down message into frames, which are stream of bytes. But, this also allows to employ any of the serialization/deserialization mechanism like Protobuf, AVRO or even JSON serialization.
It uses single physical connection for multiple logical streams to send data between client and server, which resolved ‘Head of Line Blocking’ issue.
Multiple Interaction Model
RSocket provides a simple communication interface, available with multiple interaction models:
- Request-Response (stream of 1)
- Fire-and-Forget (no response)
- Request-Stream (finite stream of many)
- Channel (bi-directional streams)
All these models represents asynchronous communications and returns a deferred result in the form of Mono/Flux in reactive terminology.
Transport Agnostic Communication
RSocket is an application layer protocol with an option to switch between different underlying transport layer protocol like TCP, Websockets and Aeron. TCP is the typical choice for the server-server variant, Websockets will be more useful for server-browser variant and Aeron (UDP-based) can be used where throughput is really critical.
Compared with HTTP, lots of unnecessary application semantics has been limited. Also compared with TCP and Websockets which are difficult to consume at application layer directly due to unavailability of easy application semantics, RSocket comes with easy to use semantics.
Build-In Flow Control
Available protocols uses transport level flow control which is just controlling the number of bytes, but RSocket provides the application level message flow-control, which is the need now. RSocket provides flow control for both client and server:
- Reactive Streams which is controlled by Requester.
- Lease Semantics which is controlled by responder.
This feature allow resuming long-lived streams, which is useful for mobile to server communication when network connections drop, switch, and reconnect frequently.
Faster than HTTP
Being binary protocol and use of asynchronous communication makes RSocket up to 10x faster than HTTP (source: https://www.netifi.com/rsocket).
RSocket perform better compared with gRPC too, which provides performance improvement over HTTP/2 itself.
Opportunities with RSocket
These features clearly shows the importance of looking beyond traditional protocols like HTTP for at least inter-service communication, which usually support the back bone of any distributed system.
Microservices built on reactive programming tech stack can improve the performance and infrastructure utilization using reactive communication protocols i.e. RSocket.
There are many opportunity for RSocket protocol in current environment which is majorly dominated by HTTP having many limitations. Some examples could be:
- Although advantages not limited to specific industry, but better streaming feature increase the scope of RSocket in real time chat applications, GPS based application, Online education with multimedia chats and collaborative drawings.
- Any Cloud based application where lot of data exchanged via inter-service communication.
- In distributed systems, wanted to reduce latency and make system’s faster.
- In distributed Systems. wanted to reduce the operational cost by better CPU utilization and increasing the memory efficiency.
- In applications, where server wanted to query specific set of clients to debug some issue at run-time. Due to bi-directional behavior it is possible to send requests from server to client on existing connection.
- Fire-and-Forget feature can be used for non critical tracing purpose.
Fig.4 show a possible usage of RSocket protocol, where multiple microservices implemented using various language can communicate with each other on different transport layer of choice. RSocket which gives application layer semantics make interaction easier. Microservices are now not tightly coupled with protocols semantics and can use simple and consistent RSocket interface.