Implementing RPC from Scratch: Service Interface, Registration, Netty Server, and Client Proxy
This article explains the fundamentals of Remote Procedure Call (RPC), detailing the definition, basic call flow, service interface design, server registration with Zookeeper, Netty-based network communication, dynamic proxy generation on the client side, and provides complete Java code examples for a working RPC framework.
Remote Procedure Call (RPC) is a communication protocol that allows a program on one machine to invoke methods on a program running on another machine as if it were a local call.
A typical RPC call involves a client obtaining a proxy for the service interface, discovering service providers through a registration center (Zookeeper in this example), selecting a provider using a load‑balancing strategy, sending a request over the network, and receiving a response.
Basic RPC Call Flow
The process includes serialization of the request, network transmission via Netty, server‑side handling that locates the target service implementation, invokes the method via reflection, and returns the result back to the client.
Service Interface Definition
/**
* @author 孙浩
* @Descrption 服务接口
*/
public interface HelloService {
String sayHello(String somebody);
}Service Implementation
/**
* @author 孙浩
* @Descrption 服务实现
*/
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String somebody) {
return "hello " + somebody + "!";
}
}Service Registration (Spring XML + Custom XSD)
The service bean is defined in Spring and registered to Zookeeper using a custom storm:service tag, which includes attributes such as interface, ref, appKey, serverPort, and others.
<bean id="helloService" class="com.hsunfkqm.storm.framework.test.HelloServiceImpl"/>
<storm:service id="helloServiceRegister"
interface="com.hsunfkqm.storm.framework.test.HelloService"
ref="helloService"
groupName="default"
weight="2"
appKey="ares"
workerThreads="100"
serverPort="8081"
timeout="600"/>Server‑Side Network Setup (Netty)
public void start(final int port) {
synchronized (NettyServer.class) {
if (bossGroup != null || workerGroup != null) return;
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyDecoderHandler(StormRequest.class, serializeType));
ch.pipeline().addLast(new NettyEncoderHandler(serializeType));
ch.pipeline().addLast(new NettyServerInvokeHandler());
}
});
try {
channel = sb.bind(port).sync().channel();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}The server handler extracts the target method, obtains the service implementation from the Spring container, invokes it via reflection, and writes a StormResponse back to the client.
Client‑Side Proxy Generation
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{targetInterface}, this);
}When a method is invoked on the proxy, the client builds a StormRequest containing a unique key, service metadata, method name, and arguments, then sends it through a Netty channel to the selected provider.
Future<StormResponse> future = fixedThreadPool.submit(
RevokerServiceCallable.of(inetSocketAddress, request));
StormResponse response = future.get(request.getInvokeTimeout(), TimeUnit.MILLISECONDS);
return response != null ? response.getResult() : null;As Netty responses are asynchronous, a blocking queue ( RevokerResponseHolder) is used to store the result until the calling thread retrieves it.
Overall Flow
Load and cache service interfaces on the provider side.
Register services and host information to Zookeeper.
Start a Netty server to listen for incoming RPC requests.
On the consumer side, generate a dynamic proxy, discover providers, select one, and perform remote method invocation via Netty.
The article also includes links to the full source code on GitHub and promotional messages for additional resources.
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.
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.
