Dubbo原始碼學習--Rmi協議(八)

歸田發表於2018-04-14

RMI 協議採用 JDK 標準的 java.rmi.* 實現,採用阻塞式短連線和 JDK 標準序列化方式。

注意:如果正在使用 RMI 提供服務給外部訪問 1,同時應用裡依賴了老的 common-collections 包 2 的情況下,存在反序列化安全風險 3

特性

  • 連線個數:多連線
  • 連線方式:短連線
  • 傳輸協議:TCP
  • 傳輸方式:同步傳輸
  • 序列化:Java 標準二進位制序列化
  • 適用範圍:傳入傳出引數資料包大小混合,消費者與提供者個數差不多,可傳檔案。
  • 適用場景:常規遠端服務方法呼叫,與原生RMI服務互操作

約束

  • 引數及返回值需實現 Serializable 介面
  • dubbo 配置中的超時時間對 RMI 無效,需使用 java 啟動引數設定:-Dsun.rmi.transport.tcp.responseTimeout=3000,參見下面的 RMI 配置

dubbo.properties 配置

dubbo.service.protocol=rmi

RMI配置

java -Dsun.rmi.transport.tcp.responseTimeout=3000

更多 RMI 優化引數請檢視 JDK 文件

介面

如果服務介面繼承了 java.rmi.Remote 介面,可以和原生 RMI 互操作,即:

  • 提供者用 Dubbo 的 RMI 協議暴露服務,消費者直接用標準 RMI 介面呼叫,
  • 或者提供方用標準 RMI 暴露服務,消費方用 Dubbo 的 RMI 協議呼叫。

如果服務介面沒有繼承 java.rmi.Remote 介面:

  • 預設 Dubbo 將自動生成一個 com.xxx.XxxService$Remote 的介面,並繼承 java.rmi.Remote 介面,並以此介面暴露服務,
  • 但如果設定了 <dubbo:protocol name="rmi" codec="spring" />,將不生成 $Remote 介面,而使用 Spring 的 RmiInvocationHandler 介面暴露服務,和 Spring 相容。

配置

定義 RMI 協議:

<dubbo:protocol name="rmi" port="1099" />

設定預設協議:

<dubbo:provider protocol="rmi" />

設定服務協議:

<dubbo:service protocol="rmi" />

多埠:

<dubbo:protocol id="rmi1" name="rmi" port="1099" />
<dubbo:protocol id="rmi2" name="rmi" port="2099" />

<dubbo:service protocol="rmi1" />

Spring 相容性:

<dubbo:protocol name="rmi" codec="spring" />
1. 公司內網環境應該不會有攻擊風險 ↩
2. dubbo 不會依賴這個包,請排查自己的應用有沒有使用 ↩
3. 請檢查應用:將 commons-collections3 請升級到 3.2.2;將 commons-collections4 請升級到 4.1。新版本的 commons-collections 解決了該問題 ↩

Rmi協議完全使用了spring框架中提供的相關實現,同樣提供了doExport和doRefer兩個方法。

(1)doExport:通過rmi協議對外暴露服務

 protected <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException {
        final RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();
		//暴露服務的埠
        rmiServiceExporter.setRegistryPort(url.getPort());
		//服務名稱
        rmiServiceExporter.setServiceName(url.getPath());
		//服務介面
        rmiServiceExporter.setServiceInterface(type);
		//服務實現類
        rmiServiceExporter.setService(impl);
        try {
            rmiServiceExporter.afterPropertiesSet();
        } catch (RemoteException e) {
            throw new RpcException(e.getMessage(), e);
        }
		//返回執行緒,用於銷燬服務使用
        return new Runnable() {
            public void run() {
                try {
                    rmiServiceExporter.destroy();
                } catch (Throwable e) {
                    logger.warn(e.getMessage(), e);
                }
            }
        };
    }

(2)doRefer:引用服務,根據rmi協議呼叫服務

//建立rmi服務代理類
    @SuppressWarnings("unchecked")
    protected <T> T doRefer(final Class<T> serviceType, final URL url) throws RpcException {
        final RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
        // RMI needs extra parameter since it uses customized remote invocation object
        if (url.getParameter(Constants.DUBBO_VERSION_KEY, Version.getVersion()).equals(Version.getVersion())) {
            // Check dubbo version on provider, this feature only support
            rmiProxyFactoryBean.setRemoteInvocationFactory(new RemoteInvocationFactory() {
                public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
                    return new RmiRemoteInvocation(methodInvocation);
                }
            });
        }
		//設定服務地址
        rmiProxyFactoryBean.setServiceUrl(url.toIdentityString());
		//介面資訊
        rmiProxyFactoryBean.setServiceInterface(serviceType);
        rmiProxyFactoryBean.setCacheStub(true);
        rmiProxyFactoryBean.setLookupStubOnStartup(true);
        rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);
        rmiProxyFactoryBean.afterPropertiesSet();
        return (T) rmiProxyFactoryBean.getObject();

相關文章