Inter-Service Communication Overheads and gRPC
In today’s digital landscape, virtually every digital application aspires to enhance its performance in order to establish dominance in the market. Achieving this improved performance is not a one-size-fits-all endeavour. Many of us are actively exploring different avenues, such as optimising algorithms, scaling our applications, and leveraging the latest technologies. One promising approach involves enhancing response times by boosting the performance of inter-service communication.
Presently, in the context of system interactions, such as a client making requests to a server, the typical approach involves the serialisation of requests into a byte stream. Subsequently, on the receiving end, the server performs deserialization to transform the received request back into a format that it can interpret.
For a concrete example, let’s consider a scenario where a Java-based client intends to persist a data object through a server. Both the client and server have the capability to communicate via a REST API using Http/1.1 protocol. In this scenario, the client initially converts the object into JSON text representation and proceeds to serialise it into a byte stream for transmission over the network. On the server side, the received byte stream is deserialized, converting it back into JSON text, which is then further transformed into the required object format that can be understood by the Java compiler.
In this context, we encounter several significant challenges:
1. The client-side burden of converting language-specific data objects, initially in a compressed binary format, into JSON text format for transmission over the network, and subsequently converting them back into data objects on the server side. It’s worth noting that each programming language maintains objects in memory in a format unique to itself.
2. Additionally, JSON text is notably larger in size compared to the data objects used within the system, leading to increased network overhead due to its bulkier nature.
3. The use of HTTP/1.1 introduced the capability for multiple requests to share a single network connection, but it also brings along the Head Of Line blocking problem. Consequently, this issue hinders the optimal utilisation of the network connection by preventing multiple requests from fully leveraging the available bandwidth.
Now, let’s explore the potential solutions to address these challenges:
1. One approach is to seek a shared data model that can be universally understood across all programming languages, eliminating the need for additional conversions during transportation. A prime example of this is ProtoBuf, which boasts language and platform neutrality.
2. Furthermore, ProtoBuf utilizes a compiled binary format, rendering it exceptionally lightweight in terms of data size.
3. The adoption of HTTP/2 presents an opportunity for request multiplexing, effectively resolving the Head of Line Blockingissue associated with HTTP/1.1. This enhancement allows for more efficient utilization of network connections. Please refer here, how HTTP/2 works and solves this.
You can employ gRPC to ensure that all of the previously discussed requirements are fulfilled. It’s akin to:
gRPC = ProtoBuf + Http/2 + RPC
gRPC relies on ProtoBuf for specifying the agreement between the client and server. If you’re familiar with the concept of WSDL(Web Services Description Language) used in SOAP services, these gRPC contracts or service definition share similarities with WSDL. These contracts encompass the definition of accessible RPC methods and the specification of request and response models. To illustrate, here’s a straightforward example of a gRPC contract written in a ‘.proto’ file, which is an integral component of ProtoBuf:
The ‘.proto’ file can undergo compilation using the ‘protoc’ compiler, resulting in the creation of language-specific implementations for the underlying objects and service methods. This code facilitates the flow control between clients and servers, directing requests to the actual implementations. Clients can effortlessly consume server methods by invoking their locally generated stub methods, while the generated stub method implementations handle the server method calls seamlessly. This process can be illustrated with the following diagram:
Advantages of gRPC:
• It employs lightweight data for requests and responses, leading to reduced network bandwidth requirements.
• There’s no need for converting language-specific data objects into text, which enhances CPU performance.
• It promotes a contract-first approach, minimizing integration challenges between client and server components.
• By default, it utilizes HTTP/2, resulting in faster data transfers without being hindered by time-consuming requests from other sources.
• It supports four types of methods: unary, client streaming, server streaming, and bi-directional streaming.
Drawbacks of gRPC:
• It has a steep learning curve, requiring a significant learning investment.
• Unlike JSON/XML, it isn’t human-readable, making debugging with different payloads more challenging.
• It is primarily suitable for service-to-service communication, with limited browser support for HTTP/2.
Integration with Web Applications:
While gRPC was originally designed primarily for inter-service communication, its capabilities have been extended to work with web browsers as well. However, this integration requires some additional adjustments. Here are two main approaches commonly used to integrate a gRPC server with web browsers:
- gRPC-Gateway: One approach is to establish a reverse proxy between the web client and the backend server. This proxy, known as the Gateway, takes on the responsibility of converting REST/JSON requests to gRPC/ProtoBuf and vice versa. As a result, web clients can continue to operate as usual without being concerned about the backends’ technology stack.
However, this method introduces an intermediary step that adds overhead by performing conversions. It is most suitable for legacy applications where updating the web client is not feasible or requires significant effort.
2. gRPC-Web: Another option is to use the gRPC-Web, which is a JavaScript client library. This library allows your web application to communicate with a gRPC server using ProtoBuf, enabling end-to-end gRPC functionality.
You might wonder why an Envoy proxy is employed when gRPC-Web already makes the client compatible with gRPCservers. The reason is that gRPC-Web applications can produce ProtoBuf instead of JSON/XML, which can be consumed by a gRPC server. However, there’s still room for making the client calls more gRPC-friendly. An important point to note is that while browsers may support HTTP/2 or may not, gRPC is built upon HTTP/2. To bridge this gap, the Envoy proxy is used, as it excels in supporting HTTP/* to HTTP/2 conversion. Additionally, Envoy offers first-class support for gRPC and provides other essential capabilities required from a reverse proxy in a backend system, such as load balancing, rate limiting, circuit breaking, and more.
Conclusion
To enhance inter-service communication and mitigate the burden of converting between text and binary formats, it’s advisable to harness the potential of gRPC whenever feasible. Furthermore, gRPC employs small size data models for communication, resulting in reduced network bandwidth consumption and expedited communication compared to conventional REST-based integrations.
Certainly, there are challenges associated with gRPC, but our decision to embrace it should be driven by the advantages we derive from its implementation.