Building a Custom RPC Spring Starter: Architecture, Principles, and Code Walkthrough
This article explains the fundamentals of remote procedure call (RPC), presents a complete system architecture diagram, and provides a step‑by‑step implementation of a custom rpc‑spring‑starter using Java, Spring, Zookeeper for service registration, Netty for network communication, and dynamic proxy techniques for client‑side invocation.
Remote Procedure Call (RPC) enables a program to invoke methods on a remote server as if they were local calls; the article reviews its history, goals, and the main challenges such as service discovery, data representation, transmission, and method invocation.
The overall system consists of a registration center (Zookeeper) where service nodes register themselves, a client that discovers available nodes, serializes and compresses requests, sends them over Netty, and a server that decompresses, deserializes, and invokes the target method.
Service registration and discovery – The author uses Zookeeper persistent nodes for service definitions and temporary nodes for live instances. Example registration code: public void exportService(Service serviceResource) { String name = serviceResource.getName(); String uri = GSON.toJson(serviceResource); String servicePath = "rpc/" + name + "/service"; zkClient.createPersistent(servicePath, true); String uriPath = servicePath + "/" + uri; zkClient.createEphemeral(uriPath); } Discovery retrieves child nodes, decodes the JSON, and caches the list.
Client implementation – A dynamic proxy generated by ClientProxyFactory.getProxy intercepts method calls, builds an RpcRequest , selects a service instance via load‑balancing, serializes the request, compresses it, and sends it through netClient.sendRequest . The invocation handler extracts service name, method name, parameters, and version before dispatch.
Network transmission – The request bytes are sent over TCP using Netty. The article discusses TCP’s sticky‑packet problem and Netty’s three decoder options: FixedLengthFrameDecoder, LineBased/DelimiterBasedFrameDecoder, and LengthFieldBasedFrameDecoder.
Serialization and compression – Various serializers (Java Serializable, Protobuf, Kryo) and compressors (Gzip) are described. The MessageProtocol interface defines byte[] marshallingRequest(RpcRequest request) and RpcRequest unmarshallingRequest(byte[] data) , while the Compresser interface defines byte[] compress(byte[] bytes) and byte[] decompress(byte[] bytes) .
Server implementation – The server receives the request, decompresses and deserializes it, looks up the service object from serverRegister , and invokes the target method via reflection or Javassist‑generated proxy classes. Example handler code: public RpcResponse handleRequest(RpcRequest request) throws Exception { ServiceObject serviceObject = serverRegister.getServiceObject(request.getServiceName() + request.getGroup() + request.getVersion()); return invoke(serviceObject, request); }
The article also compares two server‑side proxy generation strategies: DefaultRpcReflectProcessor registers the original bean as the service object, while DefaultRpcJavassistProcessor creates a new proxy class with Javassist to wrap the bean, showing the generated proxy source for a sample HelloService interface.
Overall, the tutorial provides a comprehensive, hands‑on guide to building an RPC framework from scratch, covering architecture, service registry, dynamic proxies, serialization, compression, network handling, and server dispatch.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.