Backend Development 24 min read

Understanding RPC: Principles, Implementation Details, and Code Walkthrough

This article explains the fundamentals of Remote Procedure Call (RPC), covering its definition, core challenges, service registration and discovery with Zookeeper, client proxy generation, network transmission using Netty, serialization and compression, server-side request handling via reflection or Javassist, and performance comparisons between proxy strategies.

Top Architect
Top Architect
Top Architect
Understanding RPC: Principles, Implementation Details, and Code Walkthrough

RPC Definition

Remote Procedure Call (RPC) enables invoking remote methods as if they were local, providing a simple abstraction for distributed systems.

Core Problems of RPC

To achieve this, an RPC framework must solve four key issues: service discovery, data serialization, network transmission, and method invocation on the server side.

Service Registration and Discovery

The implementation uses Zookeeper as a registration center. Services register their nodes as persistent paths, while each instance creates an ephemeral node for its address.

public void exportService(Service serviceResource) {
  String name = serviceResource.getName();
  String uri = GSON.toJson(serviceResource);
  String servicePath = "rpc/" + name + "/service";
  if (!zkClient.exists(servicePath)) {
    zkClient.createPersistent(servicePath, true);
  }
  String uriPath = servicePath + "/" + uri;
  if (zkClient.exists(uriPath)) {
    zkClient.delete(uriPath);
  }
  zkClient.createEphemeral(uriPath);
}

A Zookeeper child listener clears the local cache when service nodes change.

public void handleChildChange(String parentPath, List
childList) throws Exception {
  String[] arr = parentPath.split("/");
  SERVER_MAP.remove(arr[2]);
}

Client Proxy Generation

The client creates dynamic proxies for remote interfaces, builds request objects, and sends them over the network.

public
T getProxy(Class
clazz, String group, String version, boolean async) {
  if (async) {
    return (T) asyncObjectCache.computeIfAbsent(clazz.getName() + group + version,
      clz -> Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
        new ClientInvocationHandler(clazz, group, version, async)));
  } else {
    return (T) objectCache.computeIfAbsent(clazz.getName() + group + version,
      clz -> Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
        new ClientInvocationHandler(clazz, group, version, async)));
  }
}

The ClientInvocationHandler obtains service nodes, selects one, constructs an RpcRequest , serializes and compresses it, and sends it via Netty.

Network Transmission, Serialization, and Compression

Requests are marshalled to byte arrays using a selected protocol (e.g., Protobuf, Kryo) and then compressed with Gzip before being transmitted over TCP.

public interface MessageProtocol {
  byte[] marshallingRequest(RpcRequest request) throws Exception;
  RpcRequest unmarshallingRequest(byte[] data) throws Exception;
  byte[] marshallingResponse(RpcResponse response) throws Exception;
  RpcResponse unmarshallingResponse(byte[] data) throws Exception;
}

Server-Side Request Handling

On the server, the request is decompressed, deserialized, and the target method is invoked either via Java reflection or a Javassist‑generated proxy.

public RpcResponse invoke(ServiceObject serviceObject, RpcRequest request) throws Exception {
  Method method = serviceObject.getClazz().getMethod(request.getMethod(), request.getParametersTypes());
  Object value = method.invoke(serviceObject.getObj(), request.getParameters());
  RpcResponse response = new RpcResponse(RpcStatusEnum.SUCCESS);
  response.setReturnValue(value);
  return response;
}

When using Javassist, the proxy implements InvokeProxy and delegates the call to the generated invoke method.

Performance Comparison

Benchmarks on a MacBook Pro M1 show that reflection and Javassist proxy modes have similar latency, with Javassist being marginally faster in some cases.

Conclusion

The article provides a comprehensive walkthrough of building a simple RPC framework in Java, covering service registration, client proxy creation, network communication, serialization, compression, and server-side method invocation, along with practical code examples.

distributed systemsJavaRPCSerializationZookeeperNettyJavassist
Top Architect
Written by

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.

0 followers
Reader feedback

How this landed with the community

login 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.