Understanding RPC Implementation Principles and a Basic Java RPC Framework Example
This article explains the fundamentals of Remote Procedure Call (RPC), detailing the end‑to‑end workflow from defining service interfaces and implementations, registering services in a Zookeeper registry, handling network communication with Netty, and generating client‑side proxies using Spring and JDK dynamic proxies, all illustrated with complete Java code examples.
Introduction
The article discusses the principles of Remote Procedure Call (RPC), defining RPC as a protocol that allows a program on one machine to invoke a sub‑program on another machine without extra programming effort.
What a Basic RPC Call Involves
Popular RPC frameworks such as Dubbo rely on interface‑based remote method invocation. The client only needs the interface definition, and Java generates a dynamic proxy via Proxy and InvocationHandler. The proxy’s #invoke method performs the remote call and obtains the result.
RPC fundamentally requires network communication, serialization/deserialization, and codec handling. In clustered deployments, services register themselves with a registry (e.g., Zookeeper) to enable service discovery.
Basic Implementation
Service Side (Producer)
Service Interface
/**
* @author Sun Hao
* @Description Service interface
*/
public interface HelloService {
String sayHello(String somebody);
}Service Implementation
/**
* @author Sun Hao
* @Description Service implementation
*/
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String somebody) {
return "hello " + somebody + "!";
}
}Service Registration
Using Spring, a custom XML namespace registers the service implementation and publishes it to the registry. Example XSD fragment:
<xsd:element name="service">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="interface" type="xsd:string" use="required"/>
<xsd:attribute name="timeout" type="xsd:int" use="required"/>
<xsd:attribute name="serverPort" type="xsd:int" use="required"/>
<xsd:attribute name="ref" type="xsd:string" use="required"/>
<xsd:attribute name="weight" type="xsd:int" use="optional"/>
<xsd:attribute name="workerThreads" type="xsd:int" use="optional"/>
<xsd:attribute name="appKey" type="xsd:string" use="required"/>
<xsd:attribute name="groupName" type="xsd:string" use="optional"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>The Spring configuration registers the service bean and the custom storm:service element with attributes such as interface, ref, groupName, weight, appKey, workerThreads, serverPort, and timeout.
Network Communication
The producer uses Netty as a high‑performance NIO framework. The Netty server bootstrap configures boss and worker groups, channel options, and adds handlers for decoding, encoding, and business logic.
public void start(final int port) {
synchronized (NettyServer.class) {
if (bossGroup != null || workerGroup != null) {
return;
}
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.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 = serverBootstrap.bind(port).sync().channel();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}The core server handler NettyServerInvokeHandler extends SimpleChannelInboundHandler and processes incoming StormRequest objects, performs service lookup, reflection‑based method invocation, and writes back a StormResponse.
@Override
protected void channelRead0(ChannelHandlerContext ctx, StormRequest request) throws Exception {
if (ctx.channel().isWritable()) {
ProviderService metaDataModel = request.getProviderService();
long consumeTimeOut = request.getInvokeTimeout();
String methodName = request.getInvokedMethodName();
// Service lookup and semaphore‑based rate limiting omitted for brevity
Method method = localProviderCache.getServiceMethod();
Object result = method.invoke(serviceObject, request.getArgs());
StormResponse response = new StormResponse();
response.setInvokeTimeout(consumeTimeOut);
response.setUniqueKey(request.getUniqueKey());
response.setResult(result);
ctx.writeAndFlush(response);
} else {
logger.error("------------channel closed!---------------");
}
}Custom encoders/decoders inherit from Netty’s MessageToByteEncoder and ByteToMessageDecoder, allowing plug‑in of serialization frameworks such as Hessian or Protobuf.
Request and Response Wrappers
public class StormRequest implements Serializable {
private static final long serialVersionUID = -5196465012408804755L;
private String uniqueKey;
private ProviderService providerService;
private String invokedMethodName;
private Object[] args;
private String appName;
private long invokeTimeout;
// getters and setters omitted
} public class StormResponse implements Serializable {
private static final long serialVersionUID = 5785265307118147202L;
private String uniqueKey;
private long invokeTimeout;
private Object result;
// getters and setters omitted
}Client Side (Consumer)
The client generates a dynamic proxy for the service interface, discovers service instances from the registry, selects one using a load‑balancing strategy, and sends the request via Netty.
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{targetInterface}, this);
} String serviceKey = targetInterface.getName();
List<ProviderService> providerServices = registerCenter4Consumer.getServiceMetaDataMap4Consume().get(serviceKey);
ClusterStrategy clusterStrategyService = ClusterEngine.queryClusterStrategy(clusterStrategy);
ProviderService providerService = clusterStrategyService.select(providerServices);The invocation handler builds a StormRequest, submits it to a fixed‑size thread pool, and waits synchronously for the StormResponse returned by Netty.
Future<StormResponse> responseFuture = fixedThreadPool.submit(RevokerServiceCallable.of(inetSocketAddress, request));
StormResponse response = responseFuture.get(request.getInvokeTimeout(), TimeUnit.MILLISECONDS);
if (response != null) {
return response.getResult();
}Netty’s client handler stores asynchronous responses into a blocking queue so the invoking thread can retrieve them synchronously.
@Override
protected void channelRead0(ChannelHandlerContext ctx, StormResponse response) throws Exception {
RevokerResponseHolder.putResultValue(response);
}Running Example
The MainServer class loads storm-server.xml to publish the service, while the Client class loads storm-client.xml, obtains the HelloService bean, and calls sayHello("World").
public class MainServer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("storm-server.xml");
System.out.println(" Service published");
}
} public class Client {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("storm-client.xml");
HelloService helloService = (HelloService) context.getBean("helloService");
String result = helloService.sayHello("World");
System.out.println(result);
}
}Conclusion
The article provides a concise end‑to‑end walkthrough of RPC, covering service interface definition, registration, network transport with Netty, server‑side handling, client‑side proxy generation, service discovery, and load balancing, enabling readers to build a simple yet functional RPC framework in Java.
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.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.
