Understanding gRPC Architecture in Simple Terms
Most tutorials explain how to write a .proto file and call a gRPC method. But very few explain what actually happens after that call.
I recently implemented gRPC in one of my projects and while learning it, I tried to understand its architecture from first principles — not just how to use it, but why it is designed this way and why it is fast.
This post is a simple breakdown of that.
RPC: Calling Remote Code like It’s Local
Let’s first just remove ‘g’ from gRPC and understand what RPC means. You have a scenario where you have to execute some code that is not on your machine but is on some other remote machine. What would you do? Write an HTTP API for this.
But here is the catch. You can absolutely build a remote API with HTTP yourself, but you then have to define the wire format, client/server contracts, serialization, error handling, and other conventions. gRPC provides many of these pieces as a framework, and its use of Protocol Buffers and HTTP/2 can be efficient for many service-to-service workloads.
What if there is a way to execute code on a remote server as if it were on your local machine (just like we call functions)? This is what RPC is all about. RPC abstracts much of the networking complexity for you.
But how does RPC actually achieve this?
Here is a simple example:
1result := Add(2, 3)
This function, Add, may seem local, but it will actually be executed on a remote server by the following components.
- Client: Software that initiates the RPC by calling the remote server.
- Server: Software that will receive the request from the client and execute the code.
- Stub: It is the local implementation of a remote server. Client will interact with it.
- Skeleton: It is the server interface to the client. It is responsible for receiving requests from clients.
- Protocol: An underlying communication protocol. It can be TCP or anything else.
The client will call Add function using the stub. Stub will prepare an RPC request including information about the function to execute and parameters that are required. Data will then be marshaled to send over the network. The server skeleton will receive the request and execute the code. Then the server marshals the response and returns it to the client.
What Is gRPC?
RPC is just a concept, not a specific technology. RPC tells us how to “Call a function on another machine as if it were local.” And the implementation is our responsibility; we can do it as we want.
gRPC is a specific implementation of RPC, developed by Google. It’s a modern, high-performance RPC framework with strong conventions. There are several key features of gRPC that make it unique and useful.
- Uses HTTP/2
- Uses Protocol Buffers
- Supports streaming, deadlines, cancellation, metadata, authentication, and load balancing (at least in some libraries)
- Supports many languages
- Automatic code generation
- And it can be very fast for suitable service-to-service workloads
gRPC Architecture
At a high level, a gRPC call looks like this:
Client → Stub → Protobuf → HTTP/2 → Server Stub → Service
Everything else in gRPC is built around this pipeline.
HTTP/2
HTTP/1.0 and HTTP/1.1 evolved the original HTTP model, while HTTP/2 introduced binary framing and multiplexing over a single connection. gRPC uses HTTP/2 as its transport. HTTP/1.1 can keep connections persistent and can pipeline requests, but HTTP/2 makes concurrent streams a first-class part of the protocol, avoiding the need to rely on multiple TCP connections simply to achieve concurrency.
HTTP/2 introduced request/response multiplexing, allowing multiple streams to share a connection. This removes HTTP/1.1 application-level head-of-line blocking between independent streams, although TCP-level head-of-line blocking can still occur when packets are lost.
Header Optimization
HTTP/2 uses HPACK to compress header fields. HPACK maintains static and dynamic tables so repeated header names and values can be represented compactly, reducing header overhead.
Protocol Buffers
Many RPC systems can use formats such as JSON or XML. gRPC uses Protocol Buffers by default, which provides a compact binary representation and generated schemas. This can be efficient for service-to-service communication, although actual performance depends on the workload and implementation.
JSON is a text format, so JSON implementations must parse text and interpret strings, numbers, and other syntax. Protobuf uses a binary wire format with a schema, which can reduce serialization overhead for many structured workloads.
This is what a simple .proto file can look like:
1syntax = "proto3";
2
3service UserService {
4 rpc GetUser (GetUserRequest) returns (GetUserResponse);
5}
6
7message GetUserRequest {
8 int32 id = 1;
9}
10
11message GetUserResponse {
12 int32 id = 1;
13 string name = 2;
14 string email = 3;
15}
The client and server need compatible service and message definitions; they do not have to use the same physical .proto file as long as the generated interfaces remain compatible. Client and server code is generated from these definitions.
Streaming
gRPC supports four kinds of RPCs: unary, server streaming, client streaming, and bidirectional streaming. HTTP/2 provides the transport streams that make long-lived streaming calls practical.
- Server Streaming: The client sends a single request, and the server responds with a stream of messages.
- Client Streaming: The client sends a stream of messages, and the server returns a single response.
- Bidirectional Streaming: Both client and server send streams of messages independently.
Load Balancing
gRPC supports load balancing to distribute client requests across multiple backend servers, crucial for high-performance microservices.
Request Cancellation
gRPC clients have the ability to cancel a request if they no longer care about the response. Cancellation lets the client tell the server that it no longer needs the result. Whether server-side work stops immediately depends on whether the server propagates and handles the cancellation signal.