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 text
Client: 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

ComponentRole
SerializationObject → bytes for transmission; affects performance and compatibility
Service discoveryResolve service name to list of available instances
Load balancingPick one instance (round-robin, random, least connections, etc.)
TransportTCP/HTTP2; connection pooling, multiplexing
Timeout/retryAvoid long blocks; retry on failure with defined policy
Circuit breaker/degradeFail 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.