Framework
Snailclimb/guide-rpc-framework avatar
Snailclimb/guide-rpc-framework

guide-rpc-framework: a hand-written Java RPC stack you can read end to end

A custom RPC framework implemented with Netty, Kyro and Zookeeper, with a detailed implementation walkthrough and related tutorials.

4,446 stars2,142 forksJavaNOASSERTION

At a glance

What is it?
Snailclimb/guide-rpc-framework rebuilds a Java RPC framework from Netty, Kryo and Zookeeper as a teaching project, with a public checklist of what its author finished and what he left undone. The code is readable and unfinished in the same breath, which is exactly the point.
Who is it for?
Adopt the code, not the artifact: this repository earns a place in a study curriculum or a portfolio, not in a service mesh or a production client. Skip it if you need a monitoring console or per-service configuration, because its own roadmap leaves those boxes empty.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • Snailclimb/guide-rpc-framework4,446 ★ · +18
Star history
Repository2026-09-232026-11-14
Snailclimb/guide-rpc-framework4.4k4.4k

18 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

A call that looks local, and the six pieces that have to hide

Calling a method on an object and having a socket appear underneath it is the whole trick, and this repository exists to make the trick legible. The intended reader is a Java developer who has used Dubbo or gRPC from the outside and has never seen the request leave the JVM. The author frames the scope narrowly: this is an RPC framework, not an HTTP framework, and he names Feign as the category he is not building, because parsing and wrapping HTTP requests and responses is a different job.

The design notes break the work into six numbered slots, five named and the sixth left as an ellipsis. Registration means a directory service that maps a service name to an ip and port pair, and the chosen answer is Zookeeper. Transport means an NIO stack, and the chosen answer is Netty. Serialization means anything faster and safer than the JDK built-in, with hessian2, kryo and protostuff named as candidates. Dynamic proxy hides the network call behind an ordinary method invocation. Load balancing picks which of several registered addresses answers. Fault tolerance is named just before the list, alongside load balancing, as something a qualified RPC framework must offer, but the sixth slot itself is only an ellipsis.

BIO sockets and JDK serialization were the first version, and both were thrown out

The optimisation checklist is the most honest document in the repository. Its first two checked items are deletions in disguise: Netty on NIO replaced blocking Socket transport, and Kryo replaced the JDK serialization mechanism. The author states the reason for the second swap in plain terms, saying that JDK serialization is inefficient and carries security holes, and that nobody should ship it across a network. He writes that the original version used traditional BIO sockets and the JDK mechanism, and that later work replaced both.

The interesting entry is the fifth. Receiving a reply used to hang state off the connection itself through Netty's AttributeMap; the current code wraps the result in CompletableFuture and returns it from the async proxy as CompletableFuture<T>, while a synchronous proxy keeps the familiar local-call shape. Storing per-request state on a shared Channel is the kind of shortcut that works until two requests overlap, so moving to a future is a correctness argument, not a performance one.

Two more items are worth reading closely. A Netty heartbeat mechanism is checked off to keep connections from being dropped and forcing a reconnect. A second checked item notes that Netty reuses the Channel, which is the change that removes a TCP handshake per call.

The frame grew a magic number, a serializer id and a body length

The wire format is the part most homemade RPC code gets wrong, and this one was redesigned. The checked item says the client and server packet structure was rebuilt so that the request and the response become message bodies inside a richer envelope, with a reference to the Netty in-action book and to how Dubbo designs the same region.

The envelope carries three named fields. A magic number, usually four bytes, sits at the front so the server can compare the first four bytes and reject a packet that does not follow the custom protocol, closing the connection immediately rather than spending resources on garbage. A serializer id tells the receiver how to decode the body, so Java serialization, json and kryo can coexist on one port. A body length is computed at runtime, which is what lets the receiver frame the stream at all.

Here is the detail a skim misses. That checked item names the message body twice as RpcRequest and RpcRequest. One of them has to mean RpcResponse, and the checklist has never been corrected. It is a small slip, but it is the same slip a reader makes when they assume the response path reuses the request type, and the typed RpcStatusCode error model described elsewhere in the feature list says otherwise.

Zookeeper holds the addresses, and the default picker is a consistent hash

Service discovery here is Zookeeper, and the client resolves a service name to registered ip and port pairs through it. Two load balancing strategies are checked off, random and consistent hash, and the feature list adds that service discovery defaults to the consistent hash implementation. Random is the obvious choice for a flat list; consistent hash is what you reach for when a cached mapping has to keep pointing at the same node as the address list grows and shrinks, and the author picked that as the default without spelling out the reasoning.

The two annotation-shaped problems the framework handles are both solved with an extra string on the annotation. One interface with several implementations is handled by grouping, since a group parameter is added when the service is published. Backward compatibility across releases is handled with a version, with the recommendation that versions be two-digit numbers such as 1.0 and that the number only move when the interface changes in a breaking way, for instance when a method is removed, a field is removed, or an enum gains a value. Both are visible in the example service, which is annotated with group "test1" and version "version1".

Timeout handling is configured rather than hardcoded, and the feature list says pending requests are cleaned up after a timeout fires.

Building it means JDK 25, Maven 3.9 and a Zookeeper on port 2181

The README lists JDK 25+, Maven 3.9+ and Zookeeper 3.9.5+ as the environment. Start the registry first, pulling the image and mapping the client port:

bash
# 拉取 zookeeper 镜像
docker pull zookeeper:3.9.5
# 使用 Docker 启动 Zookeeper
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.9.5

Then clone and build, which installs the modules into the local Maven repository:

bash
git clone https://github.com/Snailclimb/guide-rpc-framework.git
cd guide-rpc-framework
mvn clean install

The repository is a multi-module Maven build: rpc-framework-common holds shared utilities and constants, rpc-framework-simple holds the framework core, hello-service-api defines the demo contract, and example-server and example-client are the provider and consumer. The contract lives in hello-service-api and pairs an interface with a serialisable DTO:

java
// HelloService.java - 服务接口
public interface HelloService {
    String hello(Hello hello);
}

// Hello.java - 数据传输对象
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@ToString
public class Hello implements Serializable {
    private String message;
    private String description;
}

On the provider side, an implementation is marked for export with the annotation, and the group and version become the lookup coordinates:

java
@Slf4j
@RpcService(group = "test1", version = "version1")
public class HelloServiceImpl implements HelloService {

A server main class annotated with @RpcScan(basePackage = {"github.javaguide"}) starts the Netty server, and the consumer injects the remote bean with @RpcReference. Which classloader, interface and package the scan must cover is the first thing to get right, because the proxy is built from the interface the consumer compiled against.

The unchecked boxes are the failure modes: concurrency, disconnect retry and illegal frames

Read the checklist for what is still missing rather than what is present. The automated tests item is checked, and it names what they cover: configuration, protocol codec, proxy, exception response, and registration and discovery. The very next item is unchecked and names the gaps: concurrent requests, disconnect retry, illegal frames and end to end fault tests. Those are exactly the four cases that break a homemade transport under load, so the passing tests are the happy path and the failure path is unproven.

Two more boxes stay open. Further support for the registry, load balancing and per-service or per-method configuration is unchecked, meaning every client currently applies one serialization choice, one compression choice and one timeout across all services. A monitoring console in the shape of dubbo-admin is unchecked too, so an operator has no dashboard for call volume, latency or failures and must read application logs.

The author's own framing matches: the framework currently implements only the most basic RPC functionality, and the listed optimisation points are there as an invitation to fork. There is no retry policy, no circuit breaker and no failover in that list at all, so a consumer that loses one Channel has no documented path to another address beyond what the heartbeat and the consistent hash picker already do.

Against Dubbo and gRPC the difference is contract shape, not feature count

The README compares itself to Dubbo and gRPC directly, placing its own server, registry and client diagram next to Dubbo's and calling them broadly similar. That is the honest framing: the difference is not that this code is better, it is that Dubbo is a product and this is a lesson. Dubbo's own configuration surface, governance and monitoring exist because teams run it in production; the unchecked dubbo-admin box here says plainly that this repository does not.

gRPC is the sharper contrast. gRPC defines a language-neutral contract, .proto files, and generates the client and server stubs, so the interface cannot drift between the two sides. This framework takes a plain Java interface, discovers the implementations with @RpcScan, and builds the proxy with a dynamic proxy at runtime, which is why the consumer's jar and the provider's jar have to agree on the same interface at the same version. That flexibility is what lets you change a return type or add a method by editing one interface and redeploying, and it is also why the version parameter exists at all.

The message format follows the same split. A gRPC call is an HTTP/2 request carrying a length-prefixed protobuf body, so any HTTP client can reach a server. The custom frame here begins with a magic number, so anything that has not read this protocol specification is disconnected on sight.

Editorial conclusion

Adopt the code, not the artifact: this repository earns a place in a study curriculum or a portfolio, not in a service mesh or a production client. Skip it if you need a monitoring console or per-service configuration, because its own roadmap leaves those boxes empty. Before you fork it, check whether the transport still behaves on your JDK: the README now asks for JDK 25 with Maven 3.9.

Frequently asked questions

What is an RPC framework?

It is a framework that lets a client call a server method as simply as a local method. The README defines the category as letting a client invoke a server side method directly, as with Dubbo, Motan and gRPC, and excludes frameworks that have to parse and wrap HTTP requests and responses.

Is RPC still relevant today?

This repository treats it as a subject worth implementing by hand rather than only consuming. The author writes that RPC principles are not hard in themselves but that he hit many problems building his own, which is the reason the code is published step by step.

Can you explain RPC in a simple way?

The client looks up the server address in the registry, then sends a request over the network that names the class, the method and its arguments. In this implementation the lookup goes through Zookeeper and the transport runs over Netty, with the payload serialized by Kryo or another pluggable serializer.

Why RPC instead of REST?

An RPC call reaches a typed method directly through a generated or dynamic proxy, so the caller works with a Java interface rather than building and parsing HTTP requests and responses. The README draws that boundary explicitly, putting Feign on the HTTP side rather than the RPC side.

Official sources

  1. Issues
  2. Project website
  3. README
  4. Snailclimb/guide-rpc-framework on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/snailclimb-guide-rpc-framework.svg)](https://hysenlabs.com/projects/snailclimb-guide-rpc-framework)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/snailclimb-guide-rpc-framework.svg)](https://hysenlabs.com/projects/snailclimb-guide-rpc-framework)

Was this useful?