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.
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 channelRPC 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 informationMessage 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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
