首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

輕量級分布式 RPC 框架 NettyRpc 核心原理與源碼深度解析

     編輯:Java 綜合教學

輕量級分布式 RPC 框架 NettyRpc 核心原理與源碼深度解析

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

整體架構

NettyRpc 架構圖

模塊技術選型解決問題
服務註冊發現ZooKeeper服務地址動態管理、健康檢查、負載均衡元數據
網絡通信Netty 4高性能 NIO、零拷貝、內存池、事件驅動
依賴管理SpringBean 生命周期、掃描註解、AOP 織入
透明調用JDK 動態代理客戶端感知本地調用,屏蔽網絡細節
序列化Protostuff高性能二進制序列化、Schema 緩存、跨語言潛力

服務端:發布流程

1. 註解標識服務

@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(); }
}

2. Spring 啟動掃描註冊

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); // 接口全限定名 -> 實現實例
        }
    }
}

3. ZooKeeper 註冊臨時有序節點

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);
        // 臨時節點:客戶端斷開自動刪除,實現服務下線感知
        // 有序節點:自動生成序號,便於客戶端負載均衡選擇
    }
}

客戶端:調用流程

1. 動態代理攔截調用

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();
            }
        );
    }
}

2. 服務發現與負載均衡

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; }
}

Protostuff 高性能序列化工具

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;
    }
}

TCP 拆包/粘包處理

// 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
服務治理缺失無法灰度發布、流量染色、就近路由引入服務網格或增強註冊中心元數據

參考資源

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved