How to Build Your Own Distributed RPC Framework from Scratch

This article walks through the motivation, core components, technology choices, architecture, and implementation details—including service registration, provider and consumer modules, custom protocol design, serialization, load balancing, and Netty-based I/O—of a self‑written distributed RPC framework, and presents performance test results.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
How to Build Your Own Distributed RPC Framework from Scratch

Preface

Why write your own RPC framework? From a personal growth perspective, understanding the essential elements of an RPC framework—service registration and discovery, load balancing, serialization protocol, RPC communication protocol, socket communication, asynchronous calls, circuit breaking, etc.—helps a developer comprehensively improve core skills. Reading source code alone can be superficial; building it yourself is the optimal path to mastery.

What is RPC

Remote Procedure Call (RPC) allows calling remote services as if they were local methods. Common implementations include gRPC, Dubbo, and Spring Cloud.

Distributed RPC Framework Elements

A distributed RPC framework relies on three basic components:

Service Provider

Service Consumer

Registry

These can be further extended to include service routing, load balancing, circuit breaking, serialization, and communication protocols.

1. Registry

The registry handles service registration and discovery. In dynamic cluster deployments, service addresses cannot be predetermined, so a unified registry is needed to discover services.

2. Service Provider (RPC Server)

The provider publishes service interfaces, registers metadata with the registry at startup, supports service deregistration, and starts a socket server to listen for client requests.

3. Service Consumer (RPC Client)

The consumer obtains service metadata from the registry, creates proxy objects, caches service addresses, selects a target address via a load‑balancing strategy, serializes request data, and communicates over sockets.

Technology Selection

Registry

Mature registries include Zookeeper, Nacos, Consul, and Eureka. This implementation supports both Nacos and Zookeeper, selectable via configuration.

IO Communication Framework

Netty is used as the underlying high‑performance, event‑driven, non‑blocking I/O framework.

Communication Protocol

TCP can cause packet fragmentation and aggregation (sticky packets). Three common solutions exist; this implementation adopts the third: a header containing magic number, version, type, and length, followed by the payload.

+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+
|  BYTE  |        |        |        |        |        ........
+--------------------------------------------+--------+-----------------+--------+--------+--------+--------+--------+--------+-----------------+
|  magic | version|  type  | content length | content byte[] |
+--------+-----------------------------------------------------------------------------------------+--------------------------------------------+

The first byte is a magic number, e.g., 0x35.

The second byte denotes the protocol version.

The third byte indicates request type (0 for request, 1 for response).

The fourth byte specifies the length of the following content.

Serialization Protocol

The framework supports JavaSerializer, Protobuf, and Hessian. Protobuf is recommended for its compact binary format and high performance.

Load Balancing

Two main strategies are provided: random and round‑robin, each supporting weighted variants.

Overall Architecture

Implementation Details

Project Structure

Service Registration & Discovery

Zookeeper

Zookeeper uses a hierarchical node model similar to a file system. Persistent nodes store service names; temporary nodes under them hold IP, port, and serialization info.

Nacos

Nacos, an Alibaba open‑source microservice management middleware, provides registration and discovery via the NamingService interface (registerInstance, getAllInstances, subscribe).

Service Provider

OrcRpcAutoConfiguration initializes the registry and RPC boot starter. The server startup flow includes loading service metadata, starting Netty listeners, and handling Spring container events.

Service Consumer

The consumer creates proxy objects before the application fully starts, using either a Spring Context initialization listener or a BeanFactoryPostProcessor. This implementation uses the latter.

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    this.beanFactory = beanFactory;
    postProcessRpcConsumerBeanFactory(beanFactory, (BeanDefinitionRegistry) beanFactory);
}

private void postProcessRpcConsumerBeanFactory(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry beanDefinitionRegistry) {
    String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
    int len = beanDefinitionNames.length;
    for (int i = 0; i < len; i++) {
        String beanDefinitionName = beanDefinitionNames[i];
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
        String beanClassName = beanDefinition.getBeanClassName();
        if (beanClassName != null) {
            Class<?> clazz = ClassUtils.resolveClassName(beanClassName, classLoader);
            ReflectionUtils.doWithFields(clazz, new FieldCallback() {
                @Override
                public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                    parseField(field);
                }
            });
        }
    }
    // registration logic omitted for brevity
}

private void parseField(Field field) {
    OrcRpcConsumer orcRpcConsumer = field.getAnnotation(OrcRpcConsumer.class);
    if (orcRpcConsumer != null) {
        OrcRpcConsumerBeanDefinitionBuilder beanDefinitionBuilder = new OrcRpcConsumerBeanDefinitionBuilder(field.getType(), orcRpcConsumer);
        BeanDefinition beanDefinition = beanDefinitionBuilder.build();
        beanDefinitions.put(field.getName(), beanDefinition);
    }
}

Proxy Factory

public class JdkProxyFactory implements ProxyFactory {
    @Override
    public Object getProxy(ServiceMetadata serviceMetadata) {
        return Proxy.newProxyInstance(serviceMetadata.getClazz().getClassLoader(), new Class[] {serviceMetadata.getClazz()},
                new ClientInvocationHandler(serviceMetadata));
    }

    private class ClientInvocationHandler implements InvocationHandler {
        private ServiceMetadata serviceMetadata;
        public ClientInvocationHandler(ServiceMetadata serviceMetadata) {
            this.serviceMetadata = serviceMetadata;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String serviceId = ServiceUtils.getServiceId(serviceMetadata);
            ServiceURL service = InvocationServiceSelector.select(serviceMetadata);
            OrcRpcRequest request = new OrcRpcRequest();
            request.setMethod(method.getName());
            request.setParameterTypes(method.getParameterTypes());
            request.setParameters(args);
            request.setRequestId(UUID.randomUUID().toString());
            request.setServiceId(serviceId);
            OrcRpcResponse response = InvocationClientContainer.getInvocationClient(service.getServerNet()).invoke(request, service);
            if (response.getStatus() == RpcStatusEnum.SUCCESS) {
                return response.getData();
            } else if (response.getException() != null) {
                throw new OrcRpcException(response.getException().getMessage());
            } else {
                throw new OrcRpcException(response.getStatus().name());
            }
        }
    }
}

IO Module (Netty)

The Netty IO service module handles client and server communication. Key classes include NettyNetClient, NettyNetServer, NettyClientChannelRequestHandler, and NettyServerChannelRequestHandler.

Encoder

@Override
protected void encode(ChannelHandlerContext ctx, ProtocolMsg msg, ByteBuf out) throws Exception {
    // write magic number
    out.writeByte(ProtocolConstant.MAGIC);
    // write version
    out.writeByte(ProtocolConstant.DEFAULT_VERSION);
    // write message type
    out.writeByte(msg.getMsgType());
    // write content length
    out.writeInt(msg.getContent().length);
    // write content bytes
    out.writeBytes(msg.getContent());
}

Decoder

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    if (in.readableBytes() < BASE_LENGTH) {
        return;
    }
    int beginIndex;
    while (true) {
        beginIndex = in.readerIndex();
        in.markReaderIndex();
        if (in.readByte() == ProtocolConstant.MAGIC) {
            break;
        }
        in.resetReaderIndex();
        in.readByte();
        if (in.readableBytes() < BASE_LENGTH) {
            return;
        }
    }
    byte version = in.readByte();
    byte type = in.readByte();
    int length = in.readInt();
    if (in.readableBytes() < length) {
        in.readerIndex(beginIndex);
        return;
    }
    byte[] data = new byte[length];
    in.readBytes(data);
    ProtocolMsg msg = new ProtocolMsg();
    msg.setMsgType(type);
    msg.setContent(data);
    out.add(msg);
}

Testing

On a MacBook Pro 13" (4‑core i5, 16 GB RAM) using Nacos as the registry, a single server and client with round‑robin load balancing were benchmarked using Apache ab.

8 threads, 10 000 requests: 18 s total, QPS ≈ 550.

100 threads, 10 000 requests: 13.8 s total, QPS ≈ 724.

Conclusion

Building this RPC framework reinforced knowledge of communication protocols, I/O frameworks, and exposed me to modern solutions like gRPC. Future work includes adding circuit‑breaking and other resilience mechanisms to continue learning and improving the project.

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.

Distributed SystemsJavaRPCNetty
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

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.