NettyRpc 基於 Netty 4 + ZooKeeper + Spring 實現,是簡易版 Dubbo,麻雀雖小五臟俱全。本文系統拆解服務發布/發現、網絡通信、動態代理、序列化編碼、TCP 拆包粘包、線程模型等核心模塊,並指出現存性能瓶頸與演進方向。

| 模塊 | 技術選型 | 解決問題 |
|---|---|---|
| 服務註冊發現 | ZooKeeper | 服務地址動態管理、健康檢查、負載均衡元數據 |
| 網絡通信 | Netty 4 | 高性能 NIO、零拷貝、內存池、事件驅動 |
| 依賴管理 | Spring | Bean 生命周期、掃描註解、AOP 織入 |
| 透明調用 | JDK 動態代理 | 客戶端感知本地調用,屏蔽網絡細節 |
| 序列化 | Protostuff | 高性能二進制序列化、Schema 緩存、跨語言潛力 |
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
Class<?> value(); // 服務接口類
}
@RpcService(HelloService.class)
public class HelloServiceImpl implements HelloService {
public String hello(String name) { return "Hello! " + name; }
public String hello(Person person) { return "Hello! " + person.getFirstName() + " " + person.getLastName(); }
}
public class RpcServer implements ApplicationContextAware {
private Map<String, Object> handlerMap = new ConcurrentHashMap<>();
@Override
public void setApplicationContext(ApplicationContext ctx) {
Map<String, Object> serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class);
for (Object serviceBean : serviceBeanMap.values()) {
String interfaceName = serviceBean.getClass().getAnnotation(RpcService.class).value().getName();
handlerMap.put(interfaceName, serviceBean); // 接口全限定名 -> 實現實例
}
}
}
public class ServiceRegistry {
private final String registryAddress;
private final CountDownLatch latch = new CountDownLatch(1);
public void register(String data) { // data = "interfaceName:host:port"
ZooKeeper zk = connectServer();
if (zk != null) {
addRootNodeIfAbsent(zk); // 確保 /rpc 父節點存在
createNode(zk, data);
}
}
private void addRootNodeIfAbsent(ZooKeeper zk) {
Stat stat = zk.exists(Constant.ZK_REGISTRY_PATH, false);
if (stat == null) {
zk.create(Constant.ZK_REGISTRY_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
private void createNode(ZooKeeper zk, String data) {
String path = zk.create(Constant.ZK_DATA_PATH, data.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// 臨時節點:客戶端斷開自動刪除,實現服務下線感知
// 有序節點:自動生成序號,便於客戶端負載均衡選擇
}
}
public class RpcProxy {
private ServiceDiscovery serviceDiscovery;
@SuppressWarnings("unchecked")
public <T> T create(Class<?> interfaceClass) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
(proxy, method, args) -> {
RpcRequest request = new RpcRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
String serverAddress = serviceDiscovery.discover(); // 從 ZK 獲取可用地址
if (serverAddress == null) throw new RuntimeException("No server address found!");
String[] array = serverAddress.split(":");
RpcClient client = new RpcClient(array[0], Integer.parseInt(array[1]));
RpcResponse response = client.send(request); // 同步阻塞等待響應
if (response.isError()) throw new RuntimeException(response.getError());
return response.getResult();
}
);
}
}
public class ServiceDiscovery {
private volatile List<String> dataList = new ArrayList<>();
private final Watcher watcher = event -> {
if (event.getType() == Event.EventType.NodeChildrenChanged) {
watchNode(zk); // 節點變更重新監聽,保持列表最新
}
};
public String discover() {
int size = dataList.size();
if (size == 0) return null;
if (size == 1) return dataList.get(0);
return dataList.get(ThreadLocalRandom.current().nextInt(size)); // 隨機負載均衡
}
private void watchNode(ZooKeeper zk) {
List<String> children = zk.getChildren(Constant.ZK_REGISTRY_PATH, watcher);
List<String> newData = new ArrayList<>();
for (String child : children) {
byte[] bytes = zk.getData(Constant.ZK_REGISTRY_PATH + "/" + child, false, null);
newData.add(new String(bytes));
}
this.dataList = newData; // volatile 保證可見性
}
}
public class RpcRequest {
private String requestId; // 唯一標識,用於異步回調匹配
private String className; // 接口全限定名
private String methodName; // 方法名
private Class<?>[] parameterTypes;
private Object[] parameters;
}
public class RpcResponse {
private String requestId;
private String error; // 異常信息
private Object result; // 返回值
public boolean isError() { return error != null; }
}
public class SerializationUtil {
private static final Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>();
private static final Objenesis objenesis = new ObjenesisStd(true); // 無構造函數實例化
@SuppressWarnings("unchecked")
private static <T> Schema<T> getSchema(Class<T> cls) {
Schema<T> schema = (Schema<T>) cachedSchema.get(cls);
if (schema == null) {
schema = RuntimeSchema.createFrom(cls);
if (schema != null) cachedSchema.put(cls, schema);
}
return schema;
}
public static <T> byte[] serialize(T obj) {
Class<T> cls = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Schema<T> schema = getSchema(cls);
return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} finally {
buffer.clear();
}
}
public static <T> T deserialize(byte[] data, Class<T> cls) {
T message = objenesis.newInstance(cls);
Schema<T> schema = getSchema(cls);
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
}
}
// Netty Pipeline 配置
channel.pipeline().addLast(
new LengthFieldBasedFrameDecoder(65536, 0, 4, 0, 0), // 前 4 字節為消息長度
new RpcDecoder(RpcRequest.class), // 入站解碼
new RpcEncoder(RpcResponse.class), // 出站編碼
new RpcServerHandler() // 業務處理
);
編碼格式:[4字節長度][消息體字節數組],解碼器自動按長度拆分完整消息。
public class RpcServerHandler extends SimpleChannelInboundHandler<RpcRequest> {
private static final ExecutorService executor = Executors.newFixedThreadPool(16); // 業務線程池
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcRequest request) {
executor.submit(() -> {
RpcResponse response = new RpcResponse();
response.setRequestId(request.getRequestId());
try {
Object result = handle(request); // 反射調用目標方法
response.setResult(result);
} catch (Throwable t) {
response.setError(t.toString());
}
ctx.writeAndFlush(response)
.addListener(ChannelFutureListener.CLOSE)
.addListener(future -> log.debug("Response sent for {}", request.getRequestId()));
});
}
}
關鍵點:Netty EventLoop 不執行業務邏輯,避免阻塞 I/O 線程;業務線程池隔離 CPU 密集/IO 密集任務。
| 問題 | 影響 | 解決方案 |
|---|---|---|
| 客戶端短連接(每次調用新建連接) | 高並發下連接數耗盡、握手延遲大 | 長連接池 + 心跳保活 + 連接生命周期管理 |
| 同步阻塞調用 | 調用線程掛起,吞吐受限 | 異步非阻塞:返回 CompletableFuture/ListenableFuture + 回調 |
| 單一序列化協議 | 跨語言互操作困難、升級困難 | 多協議適配:Protobuf/Hessian/Kryo/JSON 可插拔 |
| 無熔斷/限流/超時重試 | 雪崩風險、資源耗盡 | 整合 Hystrix/Resilience4j/Sentinel |
| 服務治理缺失 | 無法灰度發布、流量染色、就近路由 | 引入服務網格或增強註冊中心元數據 |