A lightweight, high-performance, and extensible RPC framework based on Netty, Nacos, and ByteBuddy. Supports multiple protocols including HTTP/2, HTTP/1.1, and custom Netty protocols.
This project is a high-performance, pluggable RPC framework designed to demonstrate the convergence of standard protocols and dynamic invocation. Unlike traditional RPC frameworks that bind tightly to a single protocol or require strict code generation for every service, XiaoYu RPC features a unique "Universal gRPC Adapter". It implements the standard gRPC protocol (HTTP/2 + Protobuf) but routes requests dynamically to Java service implementations. This allows you to:
- Use standard gRPC clients (like Python, Go, Node.js) to call your Java services directly.
- Retain Java's dynamic flexibility (Reflection/ByteBuddy) without generating separate
.protoservice stubs for every business class.
The project is organized into the following modules to ensure separation of concerns and maintainability:
| Module | Description |
|---|---|
rpc-api |
Defines service interfaces. Shared between Provider and Consumer. |
rpc-common |
Common utilities, Value Objects (RpcRequest, RpcResponse), and Protobuf definitions (rpc_meta.proto). |
rpc-core |
The core framework implementation. Contains SPI interfaces, Dynamic Proxy, and Registry logic. Netty-free. |
rpc-transport-netty |
The default transport implementation based on Netty. |
rpc-provider |
Example provider application that implements and exports services. |
rpc-consumer |
Example consumer application that imports and invokes services. |
rpc-spring-boot-starter |
Spring Boot auto-configuration starter for provider/consumer integration. |
rpc-benchmark |
Performance benchmarking module using JMH (Java Microbenchmark Harness). |
python_client |
Python client implementation demonstrating cross-language gRPC interoperability. |
go_client |
Go client implementation demonstrating cross-language gRPC interoperability. |
flowchart LR
subgraph External["External Systems"]
E1["python_client (grpc-python)"]
E2["go_client (grpc-go)"]
E3["Nacos Registry"]
end
subgraph Internal["Internal Components"]
subgraph Clients["Java Clients"]
C1["rpc-consumer"]
C2["Spring Boot App"]
end
subgraph Core["rpc-core (Microkernel)"]
P1["ProxyFactory (JDK/ByteBuddy)"]
P2["RpcClient / RpcServer"]
P3["ExtensionLoader (SPI)"]
end
subgraph Plugins["SPI Plugins"]
S1["Protocol: Netty / HTTP / HTTP2 / gRPC"]
S2["Serializer: Protobuf / Kryo / JSON / Java"]
S3["LoadBalancer: RoundRobin / Random"]
S4["Registry: Nacos / Local"]
S5["Transport: rpc-transport-netty"]
end
subgraph Provider["Service Provider"]
M1["rpc-provider"]
M2["HelloServiceImpl"]
end
end
A1["rpc-api (service interfaces)"]
A2["rpc-common (RpcRequest/RpcResponse + proto)"]
C1 --> P1
C2 --> P1
E1 -->|gRPC / HTTP2 + Protobuf| S1
E2 -->|gRPC / HTTP2 + Protobuf| S1
P1 --> P2
P2 --> S3
P2 --> S4
P2 --> S5
P2 --> S2
S5 --> S1
S1 --> M1
M1 --> M2
S4 <-->|service register/discover| E3
A1 -.shared API.-> C1
A1 -.shared API.-> M1
A2 -.shared model.-> P2
P3 -.loads.-> S1
P3 -.loads.-> S2
P3 -.loads.-> S3
P3 -.loads.-> S4
P3 -.loads.-> S5
- 🔌 Plugin-based Architecture: Leverages a custom SPI mechanism for maximum flexibility.
- 🤝 Universal gRPC Compatibility: A custom-implemented
GrpcProtocollayer that runs standard gRPC on HTTP/2, proven to interoperate with officialgrpc-pythonclients. - ⚡ Dynamic-Static Hybrid: Combines the performance of Protobuf serialization (with custom Type Wrappers) and the flexibility of Java dynamic proxies.
- 🚀 Pluggable Transport: Fully decoupled transport layer. Default implementation is
rpc-transport-netty, but can be swapped for Tomcat/Socket. - 📡 Multi-Protocol Support: Choice of
Netty(Custom),HTTP/1.1, orgRPC(HTTP/2) for communication. - ⚡ High-Performance Proxy: Uses ByteBuddy for dynamic proxy generation, optimized for Java 17+.
- ⚖️ Intelligent Load Balancing: Includes
RoundRobinandRandomstrategies. - 📦 Diverse Serialization: Supports
Protobuf(Enhanced with Scalar Wrappers), Kryo (Optimized),JSON, and standardJavaserialization. - 🔄 Request Multiplexing: True asynchronous request/response correlation using
request_id, enabling a single connection to handle thousands of concurrent streams (especially for HTTP/2). - 🔍 Service Discovery: Integrated with Nacos for robust service registry and discovery.
Start Nacos using Docker:
docker run --name nacos-standalone \
-e MODE=standalone \
-p 8848:8848 \
-p 9848:9848 \
-d nacos/nacos-server:v2.3.1-slimExecute the following commands to start the Java RPC Provider. This will build the project and register the HelloService to your local Nacos instance.
The default rpc.protocol is netty for Java-to-Java Provider/Consumer calls.
If you need Python/Go interoperability, switch to grpc in rpc-core/src/main/resources/rpc-config.yaml.
# 1. Build Project
mvn clean package -DskipTests
# 2. Start Provider
java -cp rpc-provider/target/rpc-provider-1.0-SNAPSHOT.jar:rpc-transport-netty/target/rpc-transport-netty-1.0-SNAPSHOT.jar:rpc-core/target/rpc-core-1.0-SNAPSHOT.jar:rpc-common/target/rpc-common-1.0-SNAPSHOT.jar:rpc-api/target/rpc-api-1.0-SNAPSHOT.jar:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout -pl rpc-provider -am) com.xiaoyu.rpc.provider.ProviderAppExecute the ConsumerApp in the rpc-consumer module to make calls to the provider.
java -cp rpc-consumer/target/rpc-consumer-1.0-SNAPSHOT.jar:rpc-transport-netty/target/rpc-transport-netty-1.0-SNAPSHOT.jar:rpc-core/target/rpc-core-1.0-SNAPSHOT.jar:rpc-common/target/rpc-common-1.0-SNAPSHOT.jar:rpc-api/target/rpc-api-1.0-SNAPSHOT.jar:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout -pl rpc-consumer -am) com.xiaoyu.rpc.consumer.ConsumerAppUnit Tests: Run the comprehensive unit test suite covering SPI, serializers, load balancers, and protocols:
mvn test -pl rpc-core,rpc-transport-nettyIntegration Tests: Run the full integration test suite:
mvn test -pl rpc-consumer -am -Dtest=FullIntegrationTestXiaoYu RPC is designed for high performance. Below are the verified results from our JMH benchmark suite.
Tested with 8 concurrent threads on local loopback (127.0.0.1).
| Protocol | Throughput (ops/ms) | Latency (ms/op) | Characteristics |
|---|---|---|---|
| Netty (Custom) | 84.245 | 0.093 | Champion. Pure binary, minimal overhead. |
| HTTP/1.1 | 76.853 | 0.104 | Robust, but limited by serial processing per connection. |
| HTTP/2 | 58.847 | 0.137 | Multiplexing Power. Higher overhead but stable under load. |
Tip
Why HTTP/2? While HTTP/1.1 is slightly faster in zero-latency local loopback tests due to its simplicity, HTTP/2's Multiplexing allows it to handle massive concurrent requests over a single connection without Head-of-Line (HoL) blocking, which is critical for real-world distributed systems.
Comparison of processing a standard POJO (RpcRequest).
| Serializer | Throughput (ops/us) | Latency (us/op) | Payload Size |
|---|---|---|---|
| Protobuf | 34.429 | 0.029 | 65 bytes |
| Kryo (Optimized) | 11.932 | 0.066 | 68 bytes |
| JSON | 2.050 | 0.497 | 231 bytes |
| Java | 1.102 | 0.895 | 652 bytes |
Key Insights:
- Protobuf vs. Java: Protobuf is 77x faster and 10x smaller than standard Java serialization.
- Binary vs. Text: Kryo (Binary) provides 6x higher throughput than JSON (Text) for complex objects due to Varint compression and omission of field names.
Use LoadTestApp to simulate microservice-style calls with configurable concurrency, duration, payload size, and output file. It reports QPS, P50/P95/P99 latency, error rate, and basic GC/heap stats.
1) Build
mvn -pl rpc-consumer -am -DskipTests package2) Start Provider
./run_server.sh3) Run Load Test Client
java -cp rpc-consumer/target/rpc-consumer-1.0-SNAPSHOT.jar:rpc-transport-netty/target/rpc-transport-netty-1.0-SNAPSHOT.jar:rpc-core/target/rpc-core-1.0-SNAPSHOT.jar:rpc-common/target/rpc-common-1.0-SNAPSHOT.jar:rpc-api/target/rpc-api-1.0-SNAPSHOT.jar:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout -pl rpc-consumer -am) \
com.xiaoyu.rpc.consumer.LoadTestApp \
--threads=200 --warmup=5 --duration=30 --payload=128 --output=loadtest-results.txtParameters
--threads=NUMWorker threads (default 200)--warmup=SECWarmup seconds (default 5)--duration=SECMeasurement seconds (default 30)--payload=BYTESPayload size in bytes (default 128)--sample-size=NUMLatency sample size (default 1,000,000)--output=PATHOutput file path (defaultloadtest-results.txt)--appendAppend to output file
Output format (one line per run)
time=2026-04-29T12:34:56Z threads=200 warmupSec=5 durationSec=30 payloadBytes=128 total=123456 success=123000 error=456 qps=4115.20 successQps=4100.00 errorRatePct=0.37 avgLatencyMs=0.410 minMs=0.120 p50Ms=0.300 p95Ms=0.900 p99Ms=1.500 maxMs=5.000 samples=1000000 heapUsedBytes=12345678 heapTotalBytes=268435456 gcCount=2 gcTimeMs=15
Configure the framework via rpc-core/src/main/resources/rpc-config.yaml.
rpc:
transport: "netty" # Transport: netty
protocol: "netty" # Protocol: netty, http, http2
server-host: "127.0.0.1"
server-port: 8080
registry: "nacos" # Registry: nacos, local
registry-address: "127.0.0.1:8848"
serializer: "protobuf" # Serializer: protobuf, kryo, java, json
proxy: "bytebuddy" # Proxy: jdk, bytebuddy
load-balancer: roundrobin # Load Balancer: roundrobin, random
max-message-size: 8388608 # 8MBImportant
Default is netty for a faster local Java-to-Java path.
grpc now also works with rpc-consumer (RpcClientProxy) and can be used for both Java and Python/Go interoperability.
XiaoYu RPC adheres to the Microkernel Architecture, where the core (rpc-core) only provides the lifecycle management and SPI (Service Provider Interface) definitions, while all specific functionalities are implemented as plugins. This design ensures the framework is highly extensible, lightweight, and follows the Open-Closed Principle.
We strictly define interfaces to decouple every major component:
| Interface | Description | Default Impl | Purpose |
|---|---|---|---|
Transport |
Abstraction of network communication. Decouples the underlying I/O framework. | NettyTransport |
Allow switching between Netty, Tomcat, or Socket without changing core logic. |
Protocol |
Message protocol definition. Controls how bytes are framed and processed. | NettyProtocol |
Support multiple protocols (Custom RPC, gRPC, HTTP) on the same port. |
Serializer |
Object serialization strategy. | ProtoBuf |
Balance performance (Protobuf/Kryo) vs Compatibility (JSON/Java). |
LoadBalancer |
Client-side load balancing strategy. | RoundRobin |
Distribute traffic evenly or randomly to providers. |
ServiceRegistry |
Service registration and discovery. | Nacos, Local |
Decouple from specific registry backend (swap Nacos for Zookeeper/Consul easily). |
ProxyFactory |
Dynamic proxy generation strategy. | ByteBuddy |
Optimization for different JDK versions (ByteBuddy works best on Java 17+). |
We implemented a powerful loading mechanism similar to Dubbo's ExtensionLoader. It scans META-INF/rpc/ for configuration files and loads implementation classes lazily by name.
- Implement the Interface: Create a class that implements the target SPI interface (e.g.,
Serializer). - Create SPI Configuration File:
- Create a file in
src/main/resources/META-INF/rpc/ - Filename must match the fully qualified interface name (e.g.,
com.xiaoyu.rpc.common.serialization.Serializer).
- Create a file in
- Register the Implementation: Add a key-value pair to the file:
my-serializer=com.example.MyCustomSerializer - Use It: update
rpc-config.yaml:rpc: serializer: my-serializer
Q: Why ByteBuddy? A: CGLIB is problematic on Java 17+ due to deep reflection restrictions. ByteBuddy is the modern industry standard for bytecode manipulation.
Q: Connection Timeout/Refusal?
A: Ensure Nacos is running and the ports 8848 and 9848 are accessible. Check your rpc-config.yaml for correct host/port settings.
Q: How to switch to Local Registry for testing?
A: Set registry: "local" in rpc-config.yaml. This bypasses Nacos and uses an in-memory map, useful for unit tests or offline development.
Q: Encountering "No Transport Found" error?
A: Make sure you have included rpc-transport-netty (or custom transport module) in your runtime dependencies. rpc-core does not include a transport implementation by default to ensure modularity.
A dedicated Spring Boot Starter is available: rpc-spring-boot-starter.
<dependency>
<groupId>com.xiaoyu.rpc</groupId>
<artifactId>rpc-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>@RpcService
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
}@RestController
public class HelloController {
@RpcReference
private HelloService helloService;
@GetMapping("/hello")
public String hello(@RequestParam String name) {
return helloService.sayHello(name);
}
}rpc:
server-port: 8080
registry: nacos
registry-address: 127.0.0.1:8848
serializer: kryo
server-enabled: true # Set to false for consumer-only appsThis framework supports interoperability with standard gRPC clients (e.g., Python, Go), allowing non-Java clients to invoke services hosted by the RPC framework.
- Standard gRPC Protocol: Implements standard HTTP/2 transport compatible with widespread gRPC libraries (via
grpc-io). - Protobuf Serialization: Supports standard Protobuf
Empty,StringValue,Int32Value, etc., via wrapper types for seamless data exchange. - Nacos Integration: Services registered in Nacos can be discovered and invoked.
-
Configure Java Server (gRPC Mode): Update
rpc-core/src/main/resources/rpc-config.yamlas follows (copy-paste ready):rpc: transport: "netty" protocol: "grpc" server-host: "127.0.0.1" server-port: 8080 registry: "nacos" registry-address: "127.0.0.1:8848" serializer: "protobuf" proxy: "bytebuddy" load-balancer: "roundrobin" max-message-size: 8388608
After multi-language tests, switch
protocolback tonetty(orhttp/http2) for Java-to-Java calls. -
Start the Java Provider (gRPC Mode): Run the following commands to build the project and start the server:
# Build the project (skip tests to speed up) mvn clean package -DskipTests # Run the Provider java -cp rpc-provider/target/rpc-provider-1.0-SNAPSHOT.jar:rpc-transport-netty/target/rpc-transport-netty-1.0-SNAPSHOT.jar:rpc-core/target/rpc-core-1.0-SNAPSHOT.jar:rpc-common/target/rpc-common-1.0-SNAPSHOT.jar:rpc-api/target/rpc-api-1.0-SNAPSHOT.jar:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout -pl rpc-provider -am) com.xiaoyu.rpc.provider.ProviderApp
-
Run the Python Client: Refer to
python_client/client.pyfor details.cd python_client # ... (existing steps) python3 client.py
-
Run the Go Client: Navigate to the
go_clientdirectory and run:cd go_client go run main.goExpected Output:
Sending RpcRequest: interface=com.xiaoyu.rpc.api.HelloService, method=sayHello, param=World RpcResponse received: Data: Hello, World! Message: Success
To ensure system reliability and code quality, this project integrates a robust CI/CD pipeline using GitHub Actions. This pipeline automatically validates the build process and runs integration tests upon every push and pull request.
Key Workflows:
- Automated Testing: Runs unit and integration tests to verify RPC functionality.
- Service Verification: Launches Nacos, the Java Provider, and Python Client in a containerized environment to test cross-language interoperability.
- Build Status: Provides immediate feedback on code health via GitHub Actions.
The core of XiaoYu RPC's interoperability lies in its custom implementation of the gRPC wire protocol over Netty's HTTP/2 stack.
Every gRPC message is prefixed with a 5-byte header, handled directly in GrpcServerHandler:
- Compression Flag (1 Byte):
0(Uncompressed) or1(Compressed). - Message Length (4 Bytes): Big-endian integer specifying the length of the following Protobuf payload.
- Payload: Standard Protobuf binary data, deserialized via
NativeProtobufSerializer.
Strict adherence to gRPC HTTP/2 headers ensures compatibility:
- :status:
200(HTTP level success) - content-type:
application/grpc(Crucial for client recognition) - te:
trailers
gRPC uses HTTP/2 Trailers to convey the final RPC status, distinct from the HTTP status code.
- HEADERS Frame (EndStream=true): Sent after the data payload.
- grpc-status:
0for OK, non-zero for errors. - grpc-message: Descriptive error message.
Contributions are welcome! Feel free to open issues or submit pull requests to improve the framework.

