AWS Open Source Blog
The versatility of gRPC, an open source high-performance RPC framework
During the birth of the computer age, the first computers were the size of a room and all computations had to be run on the same computer. Once the first network was switched on, creating protocols for those computers to communicate became necessary. A protocol is defined as the set of rules governing the exchange or transmission of data between devices. With the evolution of technology, more protocols have been created that enable multiple computers to communicate in various formats.
The HTTP protocol was ratified by the IETF in 1996 and rapidly became the standard to use for internet-enabled applications. HTTP is built upon the TCP protocol (except for HTTP/3 currently in development) by providing a standard that can be used for most web servers available today, whether packaged or custom-built. HTTP has been revised over the years to add features such as compression, caching, and authentication.
gRPC is an emerging open source protocol and a successor to HTTP designed to focus around the contract between applications and to let other protocols handle traffic routing. gRPC improves upon its underlying protocols by:
- Using HTTP/2, enabling more features such as compression and stream prioritization.
- Using protobuf IDL, which (versus JSON) is binary-encoded and thus takes advantage of HTTP/2 binary framing.
- Supporting bi-directional streaming (separate streams for client-initiated and server-initiated traffic).
- Spanning or multiplexing connections as necessary—many gRPC packets can binpack into an HTTP/2 packet, or a single gRPC packet can span multiple HTTP/2 packets.
Implementing TCP vs. HTTP vs. gRPC
Networking professionals often refer to various protocols by their layer on the Open Systems Interconnect Model (OSI Model). This conceptual model is often represented as a pyramid, with each layer stacking on top of the previous layers. Each layer is able to relegate some responsibility to the lower layers and focus on its own advantages.
Consider that if each layer builds on the previous layers, we can also assume that the code required for our application decreases as we adopt higher-layer protocols into our application.
TCP application
Suppose we want to connect to another TCP back end. In most programming languages (Golang and port 50051 were arbitrarily selected for these examples), this process is trivial:
We can add some context inside our data to make it easier for the server to understand the response. Let’s use JSON to encode/decode our data. We’ll create two structs to hold our payloads:
HTTP application
Suppose we want to retrieve text from a web page on the internet using this same application. We would need to format the text we send to work with the protocol and provide metadata about that connection. Using TCP for negotiation gives us the features of HTTP but also requires us to determine how to translate this output. Was our request successful? How do we determine the difference between the metadata and the data?
Fortunately, we can use HTTP protocol handlers to accomplish this same goal:
HTTP simplified the network connection for us, but does our application need to be aware of the network connection at all? Let’s assume that our HTTP call was part of a SayHello
function, such as:
gRPC application
We can simplify this standardized function by allowing gRPC to implement the function call. gRPC includes code generators for many common programming languages (including Golang for our examples) that start with a protobuf definition file, such as:
In short, the protocol we select determines how much programming is required to get to the same feature set, with TCP taking the most programming and gRPC taking the least.
- For our code to communicate over TCP, the code must tell the server how to establish the TCP connection and send/receive the data.
- For our code to communicate over HTTP, the code must establish the HTTP connection and know how to send/receive the data.
- For our code to communicate over gRPC, the code calls the server function as an extension of its own code. The connection for gRPC is managed outside of the protocol and not necessary to implement the function.
From this example, the greeter server implements the SayHello
function but does not call it, and the greeter client calls SayHello
without implementing it, all using gRPC.
Using gRPC across different programming languages
gRPC’s generator allows creation of the boilerplate code that implemented our SayHello
function in Golang, but gRPC’s generator also includes support for many different languages (see Supported languages and platforms). The above examples show a Golang implementation of a client. The generator also creates the boilerplate code for implementing the greeter server. The full example is in the grpc-examples repository, but the concept is to create a struct/class that implements our SayHello
function as if that function would run locally. This is the greeter server implementation in Go:
To test, from the grpc-examples folder, start the greeter server (written in Go) by running:
The greeter client will output:
Using gRPC across different compute types
Because gRPC abstracts the network implementation and focuses on function calls, we can also apply this same pattern across different types of compute. All of the above code works on a local workstation, but it can also be run on instances using Amazon Elastic Compute Cloud (Amazon EC2), containers using Amazon Elastic Container Service (Amazon ECS)/Amazon Elastic Kubernetes Service (Amazon EKS), or functions using AWS Lambda.
The grpc-examples repository includes an AWS Cloud Development Kit (AWS CDK) construct that creates an Amazon ECS service for the greeter server (written in Go) and an AWS Lambda function for the greeter client (written in Python). For demo purposes, the Amazon ECS service and Lambda function are both Amazon Virtual Private Cloud (Amazon VPC)-enabled and contained within the same VPC, with a security group allowing access.
Navigate to the walkthroughs/compute-options folder and run npm run build && cdk deploy --require-approval never
(ensuring that npm
and cdk
are installed as prerequisites). Once the AWS CloudFormation stack is deployed, AWS CDK will specify the test command to run, highlighted below.
Run the command (use the one from the above output):
to get the logs from our function. Note the response from the greeter:
Check the greeter server logs using Amazon CloudWatch.
The logs show that our greeter server received the message.
Conclusion
gRPC makes it easy to build functions in code that can be separated by networks, programming languages, and even different types of compute resources. Instead of thinking about how to connect different services, we can build the services with a defined contract and write code to call/implement those functions. Using gRPC means services only need to be called by name—how to get to those services can be handled by transporting them over gRPC and coupling them into a network or service mesh.