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

Dubbo/DubboX 原生支援 Thrift 與 Avro 協議:極簡擴展實現與效能實測對比

     編輯:Java 綜合教學

背景動機

Thrift (Facebook)、Avro (Hadoop)、Protobuf/gRPC (Google) 是高效序列化/RPC 三大主流選擇。Dubbo 內建 Thrift 支援但鎖定 0.8.0 版本且協議非原生;社區擴展代碼冗長。本文展示單類實現原生協議接入,並給出實測效能數據。

核心原理:繼承 AbstractProxyProtocol

Dubbo 協議擴展點為 com.alibaba.dubbo.rpc.Protocol,AbstractProxyProxyProtocol 已封裝 Proxy/Invoker 轉換,僅需實現兩個模板方法:

  • doExport(T impl, Class<T> type, URL url):Provider 啟動時啟動原生 Server。
  • doRefer(Class<T> type, URL url):Consumer 參考服務時建立原生 Client。

Thrift 2 協議實現 (Thrift2Protocol.java)

package com.alibaba.dubbo.rpc.protocol.thrift2;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.protocol.AbstractProxyProtocol;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import java.lang.reflect.Constructor;

public class Thrift2Protocol extends AbstractProxyProtocol {
    public static final int DEFAULT_PORT = 33208;
    private static final Logger logger = LoggerFactory.getLogger(Thrift2Protocol.class);

    @Override public int getDefaultPort() { return DEFAULT_PORT; }

    @Override
    protected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException {
        String typeName = type.getName();
        if (!typeName.endsWith("$Iface")) throw new RpcException("Thrift service must be $Iface interface");

        String processorClass = typeName.substring(0, typeName.indexOf("$Iface")) + "$Processor";
        try {
            Class<?> clazz = Class.forName(processorClass);
            Constructor<?> ctor = clazz.getConstructor(type);
            TProcessor processor = (TProcessor) ctor.newInstance(impl);

            TNonblockingServerSocket socket = new TNonblockingServerSocket(url.getPort());
            TNonblockingServer.Args args = new TNonblockingServer.Args(socket)
                .processor(processor)
                .transportFactory(new TFramedTransport.Factory())
                .protocolFactory(new TCompactProtocol.Factory());

            TServer server = new TNonblockingServer(args);
            new Thread(server::serve, "Thrift-Server-" + url.getPort()).start();

            return () -> { try { server.stop(); } catch (Throwable ignored) {} };
        } catch (Exception e) {
            throw new RpcException("Failed to start Thrift server", e);
        }
    }

    @Override
    protected <T> T doRefer(Class<T> type, URL url) throws RpcException {
        String typeName = type.getName();
        if (!typeName.endsWith("$Iface")) throw new RpcException("Thrift service must be $Iface interface");

        String clientClass = typeName.substring(0, typeName.indexOf("$Iface")) + "$Client";
        try {
            Class<?> clazz = Class.forName(clientClass);
            Constructor<?> ctor = clazz.getConstructor(org.apache.thrift.protocol.TProtocol.class);

            TSocket socket = new TSocket(url.getHost(), url.getPort());
            TTransport transport = new TFramedTransport(socket);
            org.apache.thrift.protocol.TProtocol protocol = new TCompactProtocol(transport);
            transport.open();

            @SuppressWarnings("unchecked") T client = (T) ctor.newInstance(protocol);
            logger.info("Thrift client connected to {}", url);
            return client;
        } catch (Exception e) {
            throw new RpcException("Failed to create Thrift client", e);
        }
    }
}

Avro 協議實現 (AvroProtocol.java)

package com.alibaba.dubbo.rpc.protocol.avro;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.protocol.AbstractProxyProtocol;
import org.apache.avro.ipc.NettyServer;
import org.apache.avro.ipc.NettyTransceiver;
import org.apache.avro.ipc.Server;
import org.apache.avro.ipc.reflect.ReflectRequestor;
import org.apache.avro.ipc.reflect.ReflectResponder;
import java.net.InetSocketAddress;

public class AvroProtocol extends AbstractProxyProtocol {
    public static final int DEFAULT_PORT = 40881;
    private static final Logger logger = LoggerFactory.getLogger(AvroProtocol.class);

    @Override public int getDefaultPort() { return DEFAULT_PORT; }

    @Override
    protected <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException {
        Server server = new NettyServer(
            new ReflectResponder(type, impl),
            new InetSocketAddress(url.getHost(), url.getPort())
        );
        server.start();
        return () -> { try { server.close(); } catch (Throwable ignored) {} };
    }

    @Override
    protected <T> T doRefer(Class<T> type, URL url) throws RpcException {
        try {
            NettyTransceiver client = new NettyTransceiver(
                new InetSocketAddress(url.getHost(), url.getPort())
            );
            @SuppressWarnings("unchecked") T proxy = (T) ReflectRequestor.getClient(type, client);
            logger.info("Avro client connected to {}", url);
            return proxy;
        } catch (Exception e) {
            throw new RpcException("Failed to create Avro client", e);
        }
    }
}

SPI 註冊與打包

  1. META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol 新增兩行:
thrift2=com.alibaba.dubbo.rpc.protocol.thrift2.Thrift2Protocol
avro=com.alibaba.dubbo.rpc.protocol.avro.AvroProtocol
  1. dubbo-rpc/pom.xml 加入模組:
<module>dubbo-rpc-thrift2</module>
<module>dubbo-rpc-avro</module>
  1. dubbo/pom.xml 依賴聚合(排除傳遞依賴避免衝突):
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>dubbo-rpc-thrift2</artifactId>
  <version>${project.parent.version}</version>
  <exclusions><exclusion><groupId>org.apache.thrift</groupId><artifactId>libthrift</artifactId></exclusion></exclusions>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>dubbo-rpc-avro</artifactId>
  <version>${project.parent.version}</version>
  <exclusions>
    <exclusion><groupId>org.apache.avro</groupId><artifactId>avro</artifactId></exclusion>
    <exclusion><groupId>org.apache.avro</groupId><artifactId>avro-ipc</artifactId></exclusion>
  </exclusions>
</dependency>

效能實測 (MacBook Pro, 5萬次 RPC 呼叫 ping() -> "pong")

協議總耗時 (ms)QPS特點
Dubbo (預設)14,7783,383Hessian2 序列化、Netty 傳輸
Avro10,7074,670Netty 傳輸、二進制編碼更緊湊
Thrift (TCompactProtocol + FramedTransport)4,66710,714全鏈路 Facebook 自研、零拷貝、極致效能
REST (HTTP/JSON)112,699444文本協議、解析開銷大、適合異構/防火牆穿透

架構建議:雙協議共存

  • 內部微服務:統一使用 Thrift,跨語言、高效能、強契約 (IDL)。
  • 對外/邊緣/瀏覽器:暴露 REST/HTTP,通用性最強、易除錯、穿透防火牆。
  • Dubbo 支援同一服務接口多協議導出:<dubbo:protocol name="thrift2" port="33208"/> <dubbo:protocol name="rest" port="8080"/>。

代碼倉庫

完整實現已提交 GitHub:yjmyzz/dubbox (版本 2.8.4a),歡迎 Star 與 PR。

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