Topic Overview
gRPC
Learn gRPC: high-performance RPC framework using HTTP/2 and Protocol Buffers. Understand service definition, streaming, and comparison with REST.
gRPC
gRPC is a high-performance, open-source RPC (Remote Procedure Call) framework developed by Google. It uses HTTP/2 for transport and Protocol Buffers for serialization, making it efficient for microservices communication.
What is gRPC?
gRPC provides:
- RPC framework: Call remote functions as if they were local
- HTTP/2: Multiplexing, header compression, server push
- Protocol Buffers: Efficient binary serialization
- Language agnostic: Works across multiple languages
- Streaming: Supports unary and streaming RPCs
Benefits:
- Performance: Faster than REST (binary protocol, HTTP/2)
- Type safety: Strong typing with Protocol Buffers
- Code generation: Auto-generate client/server code
- Streaming: Bidirectional streaming support
gRPC vs REST
REST (HTTP/JSON)
Client → Server: POST /api/users
Content-Type: application/json
Body: {"name": "John", "email": "john@example.com"}
Server → Client: 200 OK
Content-Type: application/json
Body: {"id": 1, "name": "John", "email": "john@example.com"}
Characteristics:
- Text-based (JSON)
- Human-readable
- Stateless
- Resource-oriented
gRPC (HTTP/2/Protobuf)
Client → Server: RPC call (binary)
Method: CreateUser
Request: (Protocol Buffer binary)
Server → Client: RPC response (binary)
Response: (Protocol Buffer binary)
Characteristics:
- Binary protocol (Protocol Buffers)
- More efficient
- Strong typing
- Streaming support
Protocol Buffers
Protocol Buffers (protobuf) is a language-neutral serialization format.
Example .proto File
syntax = "proto3";
package user;
// Service definition
service UserService {
// Unary RPC
rpc GetUser (GetUserRequest) returns (User);
// Server streaming
rpc ListUsers (ListUsersRequest) returns (stream User);
// Client streaming
rpc CreateUsers (stream CreateUserRequest) returns (CreateUsersResponse);
// Bidirectional streaming
rpc Chat (stream Message) returns (stream Message);
}
// Message definitions
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
int32 id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
}
Key points:
- Service: Defines RPC methods
- Message: Data structures
- Field numbers: Unique identifiers (1, 2, 3, ...)
- Streaming:
streamkeyword for streaming RPCs
gRPC RPC Types
1. Unary RPC
One request, one response:
rpc GetUser (GetUserRequest) returns (User);
Client → Server: Request
Server → Client: Response
2. Server Streaming
One request, multiple responses:
rpc ListUsers (ListUsersRequest) returns (stream User);
Client → Server: Request
Server → Client: Response 1
Server → Client: Response 2
Server → Client: Response 3
...
3. Client Streaming
Multiple requests, one response:
rpc CreateUsers (stream CreateUserRequest) returns (CreateUsersResponse);
Client → Server: Request 1
Client → Server: Request 2
Client → Server: Request 3
Server → Client: Response
4. Bidirectional Streaming
Multiple requests, multiple responses:
rpc Chat (stream Message) returns (stream Message);
Client → Server: Request 1
Server → Client: Response 1
Client → Server: Request 2
Server → Client: Response 2
...
Examples
gRPC Server (Node.js)
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
// Load proto file
const packageDefinition = protoLoader.loadSync('user.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const userProto = grpc.loadPackageDefinition(packageDefinition).user;
// Implement service
const server = new grpc.Server();
server.addService(userProto.UserService.service, {
// Unary RPC
GetUser: (call, callback) => {
const userId = call.request.id;
const user = {
id: userId,
name: 'John Doe',
email: 'john@example.com'
};
callback(null, user);
},
// Server streaming
ListUsers: (call) => {
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
{ id: 3, name: 'Charlie', email: 'charlie@example.com' }
];
users.forEach(user => {
call.write(user);
});
call.end();
},
// Client streaming
CreateUsers: (call, callback) => {
const users = [];
call.on('data', (request) => {
users.push({
id: Math.random(),
name: request.name,
email: request.email
});
});
call.on('end', () => {
callback(null, { count: users.length, users });
});
},
// Bidirectional streaming
Chat: (call) => {
call.on('data', (message) => {
// Echo message
call.write({
id: message.id,
text: `Echo: ${message.text}`,
timestamp: Date.now()
});
});
call.on('end', () => {
call.end();
});
}
});
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
server.start();
console.log('gRPC server running on port 50051');
});
gRPC Client (Node.js)
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
// Load proto file
const packageDefinition = protoLoader.loadSync('user.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
const userProto = grpc.loadPackageDefinition(packageDefinition).user;
// Create client
const client = new userProto.UserService(
'localhost:50051',
grpc.credentials.createInsecure()
);
// Unary RPC
client.GetUser({ id: 1 }, (error, user) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('User:', user);
});
// Server streaming
const call = client.ListUsers({ page: 1, page_size: 10 });
call.on('data', (user) => {
console.log('User:', user);
});
call.on('end', () => {
console.log('Stream ended');
});
// Client streaming
const createCall = client.CreateUsers((error, response) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('Created users:', response);
});
createCall.write({ name: 'Alice', email: 'alice@example.com' });
createCall.write({ name: 'Bob', email: 'bob@example.com' });
createCall.end();
// Bidirectional streaming
const chatCall = client.Chat();
chatCall.on('data', (message) => {
console.log('Received:', message);
});
chatCall.write({ id: 1, text: 'Hello' });
chatCall.write({ id: 2, text: 'World' });
chatCall.end();
gRPC with Authentication
// Server with authentication
const server = new grpc.Server();
server.addService(userProto.UserService.service, {
GetUser: (call, callback) => {
// Verify authentication
const metadata = call.metadata;
const token = metadata.get('authorization')[0];
if (!this.verifyToken(token)) {
callback({
code: grpc.status.UNAUTHENTICATED,
message: 'Invalid token'
});
return;
}
// Process request
const user = this.getUser(call.request.id);
callback(null, user);
}
});
// Client with authentication
const metadata = new grpc.Metadata();
metadata.add('authorization', 'Bearer token123');
const client = new userProto.UserService(
'localhost:50051',
grpc.credentials.createInsecure()
);
client.GetUser({ id: 1 }, metadata, (error, user) => {
// Handle response
});
Common Pitfalls
- Not handling errors: gRPC uses status codes, not HTTP status codes. Fix: Handle gRPC status codes (OK, NOT_FOUND, UNAUTHENTICATED, etc.)
- Large message sizes: Default limit is 4MB. Fix: Increase message size limit or use streaming
- Not using streaming: Using unary for large datasets. Fix: Use server streaming for large responses
- Version compatibility: Proto changes break clients. Fix: Use versioning, backward compatibility
- No timeout: Requests can hang indefinitely. Fix: Set deadlines/timeouts
- Not handling connection errors: Network issues not handled. Fix: Implement retry logic, connection pooling
Interview Questions
Beginner
Q: What is gRPC and how does it differ from REST?
A:
gRPC is a high-performance RPC framework using HTTP/2 and Protocol Buffers.
Differences from REST:
| Aspect | REST | gRPC |
|---|---|---|
| Protocol | HTTP/1.1 | HTTP/2 |
| Format | JSON (text) | Protocol Buffers (binary) |
| Performance | Slower | Faster |
| Type safety | Weak | Strong |
| Streaming | Limited | Full support |
| Code generation | Manual | Auto-generated |
gRPC Benefits:
- Performance: Binary protocol, HTTP/2 multiplexing
- Type safety: Strong typing with Protocol Buffers
- Code generation: Auto-generate client/server code
- Streaming: Bidirectional streaming support
Example:
REST:
POST /api/users
Content-Type: application/json
{"name": "John"}
gRPC:
RPC: CreateUser
Request: (Protocol Buffer binary)
Intermediate
Q: Explain the different types of gRPC RPCs. When would you use each?
A:
1. Unary RPC:
rpc GetUser (GetUserRequest) returns (User);
- One request, one response
- Use when: Simple request/response (like REST)
- Example: Get user by ID, create user
2. Server Streaming:
rpc ListUsers (ListUsersRequest) returns (stream User);
- One request, multiple responses
- Use when: Large dataset, real-time updates
- Example: List users, live notifications, log streaming
3. Client Streaming:
rpc CreateUsers (stream CreateUserRequest) returns (CreateUsersResponse);
- Multiple requests, one response
- Use when: Batch operations, uploading data
- Example: Batch create users, file upload
4. Bidirectional Streaming:
rpc Chat (stream Message) returns (stream Message);
- Multiple requests, multiple responses
- Use when: Real-time bidirectional communication
- Example: Chat, gaming, collaborative editing
When to use:
- Unary: Simple operations (most common)
- Server streaming: Large datasets, real-time data
- Client streaming: Batch operations
- Bidirectional: Real-time communication
Senior
Q: Design a microservices architecture using gRPC. How do you handle service discovery, load balancing, retries, and observability?
A:
class gRPCMicroservicesArchitecture {
private serviceRegistry: ServiceRegistry;
private loadBalancer: LoadBalancer;
private circuitBreaker: CircuitBreaker;
private observability: Observability;
constructor() {
this.serviceRegistry = new ServiceRegistry();
this.loadBalancer = new LoadBalancer();
this.circuitBreaker = new CircuitBreaker();
this.observability = new Observability();
}
// 1. Service Discovery
class ServiceRegistry {
private services: Map<string, ServiceInstance[]>;
async register(serviceName: string, instance: ServiceInstance): Promise<void> {
if (!this.services.has(serviceName)) {
this.services.set(serviceName, []);
}
this.services.get(serviceName).push(instance);
// Health check
this.startHealthCheck(serviceName, instance);
}
async discover(serviceName: string): Promise<ServiceInstance[]> {
const instances = this.services.get(serviceName) || [];
return instances.filter(instance => instance.healthy);
}
}
// 2. Load Balancing
class LoadBalancer {
async selectInstance(instances: ServiceInstance[]): Promise<ServiceInstance> {
// Round robin
const index = this.getNextIndex(instances.length);
return instances[index];
// Or: Least connections
// return instances.reduce((min, i) =>
// i.connectionCount < min.connectionCount ? i : min
// );
}
}
// 3. gRPC Client with Retry
class gRPCClient {
async call(serviceName: string, method: string, request: any): Promise<any> {
const instances = await this.serviceRegistry.discover(serviceName);
const instance = await this.loadBalancer.selectInstance(instances);
// Retry logic
return await this.retry(async () => {
return await this.makeRequest(instance, method, request);
}, {
maxRetries: 3,
backoff: 'exponential'
});
}
async makeRequest(instance: ServiceInstance, method: string, request: any): Promise<any> {
const client = await this.getClient(instance);
return new Promise((resolve, reject) => {
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 5); // 5 second timeout
client[method](request, { deadline }, (error, response) => {
if (error) {
reject(error);
} else {
resolve(response);
}
});
});
}
}
// 4. Circuit Breaker
class CircuitBreaker {
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
private failures: number = 0;
private lastFailureTime: number = 0;
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > 60000) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await fn();
if (this.state === 'HALF_OPEN') {
this.state = 'CLOSED';
this.failures = 0;
}
return result;
} catch (error) {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= 5) {
this.state = 'OPEN';
}
throw error;
}
}
}
// 5. Observability (Metrics, Tracing, Logging)
class Observability {
async recordMetric(service: string, method: string, duration: number, status: string): Promise<void> {
// Record to metrics system (Prometheus, etc.)
await this.metrics.record({
service,
method,
duration,
status
});
}
async trace(service: string, method: string, request: any): Promise<string> {
// Create trace span
const traceId = this.generateTraceId();
await this.tracer.startSpan(traceId, {
service,
method,
request
});
return traceId;
}
}
// 6. Interceptors (Middleware)
class gRPCInterceptor {
intercept(call: any, options: any, next: Function): any {
const traceId = this.observability.trace(call.service, call.method, call.request);
const startTime = Date.now();
return next(call, options).then(
(response) => {
const duration = Date.now() - startTime;
this.observability.recordMetric(call.service, call.method, duration, 'success');
return response;
},
(error) => {
const duration = Date.now() - startTime;
this.observability.recordMetric(call.service, call.method, duration, 'error');
throw error;
}
);
}
}
}
Features:
- Service discovery: Register and discover services
- Load balancing: Distribute requests across instances
- Retry logic: Automatic retries with exponential backoff
- Circuit breaker: Prevent cascading failures
- Observability: Metrics, tracing, logging
- Interceptors: Middleware for cross-cutting concerns
Key Takeaways
- gRPC: High-performance RPC framework using HTTP/2 and Protocol Buffers
- Protocol Buffers: Binary serialization format, more efficient than JSON
- RPC types: Unary, server streaming, client streaming, bidirectional streaming
- Benefits: Performance, type safety, code generation, streaming support
- vs REST: Binary vs text, HTTP/2 vs HTTP/1.1, stronger typing
- Service definition: Define services and messages in .proto files
- Use cases: Microservices communication, high-performance APIs, real-time streaming
- Best practices: Handle errors, set timeouts, use streaming for large data, implement retries and circuit breakers