Designing a Robust RPC Framework: Core Components and Implementation

This article provides a comprehensive deep‑dive into RPC functional goals, call classifications, component breakdown, Java‑based implementation details, protocol encoding, transport considerations, execution control, and exception handling, offering practical guidance for building a reliable distributed RPC framework.

ITFLY8 Architecture Home
ITFLY8 Architecture Home
ITFLY8 Architecture Home
Designing a Robust RPC Framework: Core Components and Implementation

In this deep dive we explore RPC functional goals and implementation considerations, outlining what a basic RPC framework should provide and how to achieve it.

RPC Functional Goals

The main goal of RPC is to make building distributed applications easier by offering powerful remote call capabilities without losing the simplicity of local call semantics. To achieve this, an RPC framework should provide a transparent invocation mechanism so users do not need to distinguish between local and remote calls.

RPC Call Types

1. Synchronous call
   The client waits for the call to complete and returns the result.
2. Asynchronous call
   The client does not wait for the result but can receive it via callbacks.
   If the client does not care about the result, it becomes a one‑way async call.

The distinction lies in whether the client waits for the server to finish execution and return a result.

RPC Structure Decomposition

The previous shallow overview presented a coarse‑grained RPC structure; here we further detail the components, as illustrated below.

The service side uses RpcServer to export remote interface methods, while the client uses RpcClient to import them. Calls are made through a proxy RpcProxy, which packages the invocation information and delegates it to RpcInvoker. On the client, RpcInvoker uses a RpcConnector to maintain a RpcChannel and employs RpcProtocol to encode the request before sending it.

On the server side, RpcAcceptor receives the request, decodes it with RpcProtocol, passes the information to RpcProcessor for processing, and finally invokes the actual method via RpcInvoker to return the result.

RPC Component Responsibilities

1. RpcServer – export remote interfaces
2. RpcClient – import remote interface proxy implementations
3. RpcProxy – proxy implementation of remote interfaces
4. RpcInvoker – client side: encode and send request; server side: execute method and return result
5. RpcProtocol – handle protocol encoding/decoding
6. RpcConnector – maintain connection channel and send data
7. RpcAcceptor – receive client requests and return responses
8. RpcProcessor – control call processing (thread pool, timeout, etc.)
9. RpcChannel – data transmission channel

RPC Implementation Analysis

Exporting Remote Interfaces

Only exported interfaces are callable remotely. In Java, exporting may look like:

DemoService demo = new ...;
RpcServer server = new ...;
server.export(DemoService.class, demo, options);

Selective method export is also possible:

// Export only the method hi(String s)
server.export(DemoService.class, demo, "hi", new Class<?>[] { String.class }, options);

For polymorphic services, each implementation must be uniquely identified (e.g., "demo2") during export so that the client can specify which implementation to invoke.

Importing Remote Interfaces and Client Proxy

Clients import interfaces to invoke them. Many cross‑language RPC frameworks generate stubs from IDL at compile time. In a single‑language scenario like Java, dynamic generation can be used at runtime via JDK dynamic proxies or bytecode generation. Example:

RpcClient client = new ...;
DemoService demo = client.refer(DemoService.class);
demo.hi("how are you?");

Dynamic proxies are easier to use but slower than bytecode generation; the trade‑off between performance and readability should be considered.

Protocol Encoding/Decoding

Before sending a request, the client must encode the following information:

-- Call Encoding --
1. Interface method (name, class)
2. Method parameters (types, values)
3. Call attributes (attachments, timeout, etc.)

-- Return Encoding --
1. Return result
2. Return code
3. Exception information

Message design typically separates a header (metadata) from a body (payload). An example header includes magic number, version, serialization type, heartbeat flag, one‑way flag, response flag, status code, message ID, and body size. The body can be serialized as XML, JSON, or binary formats (e.g., Thrift, Hessian, Kryo).

Serialization should aim for high efficiency, compact size, and compatibility across versions.

Transport Service

After encoding, the request is transmitted over a long‑living TCP connection, similar to HTTP request‑response but with a unique message ID for easier connection reuse. Whether to use a single or multiple connections depends on data volume; a single connection suffices for low traffic, while multiple connections can improve throughput for large data streams.

Connections are established by the client. In environments with intermediate load balancers, idle connections may be dropped, so periodic heartbeat messages (indicated by a dedicated flag in the protocol header) are required to keep the channel alive.

Executing Calls

On the server side, the stub receives the request, decodes it, and passes it to RpcProcessor for control (thread pool management, timeout handling, resource isolation). RpcInvoker performs the actual method invocation, typically via reflection or high‑performance reflection libraries.

Key considerations for RpcProcessor include:

1. Efficiency – use a thread pool instead of creating a new thread per request.
2. Resource isolation – prevent a single heavy interface from monopolizing threads.
3. Timeout control – abort server‑side execution if the client has already timed out.

RPC Exception Handling

Remote calls differ from local calls: network failures may prevent the request from reaching the server, and RPC frameworks can throw runtime exceptions beyond those declared by the interface. Users must distinguish between business exceptions (indicating server‑side execution) and framework/runtime exceptions (indicating transport or execution failures).

Because RPC adds millisecond‑level overhead compared to nanosecond‑level local calls, it is only worthwhile for tasks whose execution time far exceeds the RPC overhead.

Conclusion

We have presented a conceptual RPC framework, dissected its components, and discussed implementation details such as interface export/import, protocol design, transport, execution control, and exception handling. Understanding these fundamentals is essential for effectively applying RPC in distributed systems.

Source: https://blog.csdn.net/mindfloating/article/details/39474123

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RPCframework designnetwork protocol
ITFLY8 Architecture Home
Written by

ITFLY8 Architecture Home

ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.