RPC Fundamentals - What Happens on a Call
RPC (Remote Procedure Call) makes a remote call look like a local method call. Under the hood, the call is serialized, sent over the network, executed on a remote service, and the result is returned. A typical RPC flow: serialize parameters → resolve address (service discovery) → load balance → send request → deserialize → execute → serialize result → return. This article explains the flow and components with examples and a reference table.
Overview
- Client: A proxy (stub) converts the method call into a message (interface, method name, parameter types and values), serializes it, and sends it over the transport (e.g. TCP). On response, it deserializes and returns the result to the caller.
- Server: Receives the message, deserializes it, routes to the implementation by interface and method name (reflection or generated code), executes, serializes the return value, and sends it back.
- Components: Serialization, service discovery, load balancing, transport (TCP/HTTP/gRPC), timeout, retry, circuit breaker. Frameworks like Dubbo, gRPC, and Thrift implement these differently.
Example
Example 1: Call flow (simplified)
Plain textClient: userService.getUser(1) → Serialize: {interface:"UserService", method:"getUser", args:[1]} → Service discovery: resolve "UserService" to provider list → Load balance: pick one provider → Send request (TCP/HTTP) Server: receive → deserialize → route to UserServiceImpl.getUser(1) → return User → Serialize User → send back Client: receive → deserialize → return User to caller
Example 2: Key components
| Component | Role |
|---|---|
| Serialization | Object → bytes for transmission; affects performance and compatibility |
| Service discovery | Resolve service name to list of available instances |
| Load balancing | Pick one instance (round-robin, random, least connections, etc.) |
| Transport | TCP/HTTP2; connection pooling, multiplexing |
| Timeout/retry | Avoid long blocks; retry on failure with defined policy |
| Circuit breaker/degrade | Fail fast or use fallback when downstream is unhealthy |
Example 3: RPC vs REST
- RPC: Method-oriented, strongly typed, compact protocol. Common for service-to-service calls. gRPC, Thrift, Dubbo.
- REST: Resource-oriented, HTTP semantics. Common for external APIs and cross-language. Can run over HTTP/2 (e.g. gRPC).
Example 4: Serialization choices
Java// JSON: human-readable, widely supported, but larger and slower {"userId":1,"name":"Alice"} // Protobuf/Hessian: binary, compact, faster // Schema or interface required; better for internal RPC
Core Mechanism / Behavior
- Proxy: Client stubs are often code-generated or created with dynamic proxies. They turn Java method calls into protocol messages. The server uses reflection or generated dispatchers to route to the implementation.
- Serialization: JSON is readable and compatible but larger and slower. Protobuf, Hessian, Kryo are binary and more efficient. Choose based on performance, readability, and versioning needs.
- Network: Long-lived connections, connection pools, and multiplexing reduce connection overhead. Timeout and retry must align with idempotency and retryable error types.
Key Rules
- Understand the end-to-end path: serialization, discovery, load balancing, transport, timeout, retry, circuit breaker. Each can affect the call; debug by walking the chain.
- Always set timeout; configure retry based on idempotency and retryable errors; use circuit breaker to limit failure spread.
- When choosing an RPC stack, consider performance (serialization, connection reuse), observability (tracing, logging), and compatibility (protocol versioning, schema evolution).
What's Next
See Dubbo Architecture, Serialization Tradeoffs, Timeout/Retry/Fallback, Load Balancing, and Service Versioning for each component.