基於Spring的RPC通信模型.

1、概念和原理

    RPC(remote procedure call),遠程過程調用,是客戶端應用和服務端之間的會話。在客戶端,它所須要的一些功能並不在該應用的實現範圍以內,因此應用要向提供這些功能的其餘系統尋求幫助。而遠程應用經過遠程服務暴露這些功能。RPC 是同步操做,會阻塞調用代碼的執行,直到被調用的過程執行完畢。java

    Spring支持多種不一樣的RPC模型,包括RMI、Caucho的Hessian和Burlap以及Spring自帶的HTTP invoker:git

    客戶端:github

    在全部的模型中,服務都是做爲 Spring 所管理的 bean 配置到咱們的應用中。這是經過一個代理工廠 bean 實現的,這個bean可以把遠程服務像本地對象同樣裝配到其餘bean的屬性中。spring

    客戶端向代理髮起調用,就像代理提供了這些服務同樣。代理表明客戶端和遠程服務進行通訊,由它負責處理鏈接的細節並向遠程服務發起調用。網絡

    服務端:app

Spring 使用遠程導出器(remote exporter)將bean方法發佈爲遠程服務。框架

2、RMI

    RMI 最初在JDK 1.1被引入到Java平臺中,它爲Java開發者提供了一種強大的方法來實現Java程序間的交互。ide

    Spring 提供了簡單的方式來發布RMI服務,在服務端,RmiServiceExporter 能夠把任何 Spring 管理的bean發佈爲RMI服務 ,如圖所示,RmiServiceExporter 把bean包裝在一個適配器類中,而後適配器類被綁定到RMI註冊表中,而且代理到服務類的請求。 ui

    /**
     * 服務端:
     * <p>
     * 一、默認狀況下,RmiServiceExporter 會嘗試綁定到本地機器1099端口上的RMI註冊表。
     * 二、若是在這個端口沒有發現RMI註冊表,RmiServiceExporter 將會啓動一個註冊表。
     * 三、可重寫註冊表的路徑和端口,這個是個大坑,當你設置了registryHost屬性的時候,源碼中就不建立Registry,而是直接去獲取,但是咱們本身也沒有建立,因此就會報鏈接不上。
     *
     * @param userService
     * @return
     */
    @Bean(name = "rmiServiceExporter")
    public RmiExporter rmiServiceExporter(UserService userService, Environment environment) {
        String registryHost = environment.getProperty("registryHost");
        int registryPort = environment.getProperty("registryPort", Integer.class);
        RmiExporter rmiExporter = new RmiExporter();
        rmiExporter.setService(userService); //要把該bean(即rmiServiceImpl)發佈爲一個RMI服務
        rmiExporter.setServiceName("RmiService"); //命名RMI 服務
        rmiExporter.setServiceInterface(UserService.class); //指定服務所實現的接口
        rmiExporter.setRegistryHost(registryHost);
        rmiExporter.setRegistryPort(registryPort);
        return rmiExporter;
    }
/**
 * Created by XiuYin.Cui on 2018/5/14.
 * 
 * 解決設置 registryHost 後,報鏈接拒絕的問題。
 */
public class RmiExporter extends RmiServiceExporter {

    @Override
    protected Registry getRegistry(String registryHost, int registryPort, RMIClientSocketFactory clientSocketFactory,
                                   RMIServerSocketFactory serverSocketFactory) throws RemoteException {


        if (registryHost != null) {
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
                }
                //把spring源代碼中這裏try起來,報異常就建立一個
                Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
                testRegistry(reg);
                return reg;
            } catch (RemoteException ex) {
                LocateRegistry.createRegistry(registryPort);
                Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
                testRegistry(reg);
                return reg;
            }
        } else {
            return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
        }
    }
}
View Code

     接下來,來看看客戶端是怎麼使用這些遠程服務的吧!Spring的RmiProxyFactoryBean是一個工廠bean,該bean能夠爲RMI服務建立代理。該代理表明客戶端來負責與遠程的RMI服務進行通訊。客戶端經過服務的接口與代理進行交互,就如同遠程服務就是一個本地的POJO。url

 

    @Bean(name = "rmiUserServiceClient")
    public RmiProxyFactoryBean RmiUserServiceClient(){
        RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
        rmiProxyFactoryBean.setServiceUrl("rmi://127.0.0.1:9999/RmiService");
        rmiProxyFactoryBean.setServiceInterface(UserService.class);
        rmiProxyFactoryBean.setLookupStubOnStartup(false);//不在容器啓動後建立與Server端的鏈接
        rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);//鏈接出錯的時候自動重連
        rmiProxyFactoryBean.afterPropertiesSet();
        return rmiProxyFactoryBean;
    }
    @Resource(name="rmiUserServiceClient")
    private UserService userService;

     RMI 的缺陷:

一、RMI很難穿越防火牆,這是由於RMI使用任意端口來交互——這是防火牆一般所不容許的。
二、RMI是基於Java的。這意味着客戶端和服務端必須都是用java開發。由於RMI使用了Java的序列化機制,因此經過網絡傳輸的對象類型必需要保證在調用兩端的Java運行時中是徹底相同的版本。

    tips:最近發現 Dubbo 底層也是用 RMI 實現的,它把 zookeeper 看成註冊表。

3、Hessian 和 Burlap

    hessian 和 Burlap 是 Caucho Technology 的兩種基於HTTP的輕量級遠程服務解決方案。藉助於儘量簡單的API和通訊協議,它們都致力於簡化Web服務。

    hessian,像RMI同樣,使用二進制消息進行客戶端和服務端的交互。可是它與RMI不一樣的是,它的二進制消息能夠移植到其餘非Java的語言中。因爲它是基於二進制的,因此它在帶寬上更具優點。

    Burlap 是一種基於XML的遠程調用技術,這使得它能夠天然而然的移植到任何可以解析XML的語言上。正由於它基於XML,因此相比起Hessian的二進制格式而言,Burlap可讀性更強。可是和其餘基於XML的遠程技術(例如SOAP或XML-RPC)不一樣,Burlap的消息結構儘量的簡單。

    下面咱們會介紹 hessian 的使用。Spring 不推薦使用 Burlap,BurlapServiceExporter 在4.0後被廢棄,再也不提供支持。5.0 後直接從開發包丟棄了。

    服務端,相似於 RmiServiceExporter ,hessian 也有一個 HessianServiceExporter 將 Spring 管理的 bean 發佈爲 Hessian 服務,不一樣於RMI的是,HessianServiceExporter是一個Spring MVC控制器,它接收Hessian請求(HTTP協議的請求),並將這些請求轉換成對被導出POJO的方法調用。既然是HTTP請求,那咱們就必須配置Spring 的 DispatcherServlet ,並配置 HandlerMapping,將相應的URL映射給 HessianServiceExporter。

 

    /**
     * hessian沒有註冊表,不須要設置 serviceName
     */
    @Bean(name = "hessianServiceExporter")
    public HessianServiceExporter hessianServiceExporter(UserService userService) {
        HessianServiceExporter hessianServiceExporter = new HessianServiceExporter();
        hessianServiceExporter.setService(userService);
        hessianServiceExporter.setServiceInterface(UserService.class);
        return hessianServiceExporter;
    }
    /**
     * 須要配置一個URL映射來確保DispatcherServlet把請求轉給HessianServiceExporter
     */
    @Bean(name = "handlerMapping")
    public HandlerMapping handlerMapping() {
        SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
        Properties mappings = new Properties();
        mappings.setProperty("/user.service", "hessianServiceExporter");
        handlerMapping.setMappings(mappings);
        return handlerMapping;
    }

    客戶端,相似於 RmiProxyFactoryBean ,Hessian 也有一個代理工廠Bean——HessianProxyFactoryBean,來建立代理與遠程服務進行通訊:

    @Bean(name = "hessianUserServiceClient")
    public HessianProxyFactoryBean hessianUserServiceClient(){
        HessianProxyFactoryBean proxy = new HessianProxyFactoryBean();
        proxy.setServiceUrl("http://127.0.0.1:8080/user.service");
        proxy.setServiceInterface(UserService.class);
        return proxy;
    }
    @Resource(name="hessianUserServiceClient")
    private UserService userService; 

    Hessian 的缺陷:

    hessian 和 Burlap 都是基於HTTP的,它們都解決了RMI所頭疼的防火牆滲透問題。可是當傳遞過來的RPC消息中包含序列化對象時,RMI就完勝 Hessian 和 Burlap 了。由於 Hessian 和 Burlap 都採用了私有的序列化機制,而RMI使用的是Java自己的序列化機制。

4、HttpInvoker

    RMI 和 Hessian 各有本身的缺陷,一方面,RMI使用Java標準的對象序列化機制,可是很難穿透防火牆。另外一方面,Hessian和Burlap能很好地穿透防火牆,可是使用私有的對象序列化機制。就這樣,Spring的HTTP invoker應運而生了。HTTP invoker是一個新的遠程調用模型,做爲Spring框架的一部分,可以執行基於HTTP的遠程調用,並使用Java的序列化機制。

    HttpInvoker 的使用和 Hessian 很相似,HttpInvokerServiceExporter 也是一個Spring MVC 控制器,也是經過 DispatcherServlet 將請求分發給它...

    /*Http Invoker*/
    @Bean(name = "httpInvokerServiceExporter")
    public HttpInvokerServiceExporter httpInvokerServiceExporter(UserService userService){
        HttpInvokerServiceExporter httpInvokerServiceExporter = new HttpInvokerServiceExporter();
        httpInvokerServiceExporter.setService(userService);
        httpInvokerServiceExporter.setServiceInterface(UserService.class);
        return httpInvokerServiceExporter;
    }
    /**
     * 須要配置一個URL映射來確保DispatcherServlet把請求轉給HessianServiceExporter
     */
    @Bean(name = "handlerMapping")
    public HandlerMapping handlerMapping() {
        SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
        Properties mappings = new Properties();
        mappings.setProperty("/user.service", "hessianServiceExporter");
        mappings.setProperty("/userInvoker.service", "httpInvokerServiceExporter");
        handlerMapping.setMappings(mappings);
        return handlerMapping;
    }

    客戶端,像 RmiProxyFactoryBean 和 HessianProxyFactoryBean 同樣,HttpInvoker 也提供了一個代理工廠Bean——HttpInvokerProxyFactoryBean,用於建立HttpInvoker代理來與遠程服務通訊:

    @Bean(name = "httpInvokerUserServiceClient")
    public HttpInvokerProxyFactoryBean httpInvokerUserServiceClient(){
        HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean();
        proxy.setServiceUrl("http://127.0.0.1:8080//userInvoker.service");
        proxy.setServiceInterface(UserService.class);
        return proxy;
    }
    @Resource(name="httpInvokerUserServiceClient")
    private UserService userService;

 

 

參考資料:《Spring 實戰第四版》

演示源代碼連接:https://github.com/JMCuixy/SpringForRpc

相關文章
相關標籤/搜索