轉發-常用的RPC框架

常用的RPC框架
原創Simple_Yang92 最後發佈於2018-02-05 15:32:11 閱讀數 24036  收藏
展開
1. 爲什麼要使用RPC?

RPC(remote procedure call)是指遠程過程調用,比如兩臺服務器A和B,A服務器上部署一個應用,B服務器上部署一個應用,A服務器上的應用想調用B服務器上的應用提供的接口,由於不在一個內存空間,不能直接調用,所以需要通過網絡來表達調用的語義和傳達調用的數據。


RPC(remote procedure call,遠程過程調用):

首先,要解決通訊的問題,主要是通過客戶端和服務器端之間建立TCP連接,遠程過程調用的所有交換的數據都在這個連接裏傳輸。連接可以是按需連接,調用結束後就斷掉,也可以是長連接,多個遠程過程調用共享一個連接。
第二,要解決尋址的問題,A服務器上的應用要調用B服務器上的應用,A服務器上的應用需要通過底層RPC框架得知:如何連接到B服務器(主機或IP地址)以及特定的端口,方法的名稱等信息,這樣才能完成調用。
第三,A服務器上的應用發起遠程調用時,方法的參數需要通過底層的網絡協議如TCP傳遞到B服務器,由於網絡協議是基於二進制的,內存中的參數需要序列化成二進制形式,然後再通過尋址和傳輸將序列化的二進制發送給B服務器。
第四:B服務器收到請求後,需要進行反序列化,恢復爲內存中的表達方式,然後找到對應的方法進行本地調用並返回,序列化返回值併發送給A服務器。
第五:A服務器收到B服務器的返回值後,進行反序列化,恢復爲內存中的表達方式,然後交給A服務器上的應用進行處理。

以上參考: 誰能用通俗的語言解釋一下什麼是 RPC 框架?

RPC的協議有很多,比如Java RMI、WebService的RPC風格、Hession、Thrift、REST API


2. RPC、RMI、SOAP、REST的區別

RMI(remote method invocation,面向對象的遠程方法調用)
RPC(remote procedure call,遠程過程調用)
SOAP(simple object access protoal,簡單對象訪問協議)
REST(representational state transfer,表達性狀態轉移)

可以都理解爲調用遠程方法的一些通信技術“風格”:

· RMI就好比它是本地工作,採用tcp/ip協議,客戶端直接調用服務端上的一些方法。優點是強類型,編譯期可檢查錯誤,缺點是隻能基於JAVA語言,客戶機與服務器緊耦合。

· RPC是一個泛化的概念,嚴格來說一切遠程過程調用手段都屬於rpc範疇,包括rmi、hessian、soap、thrift、protobuf等等。

· SOAP是在XML-RPC基礎上,使用標準的XML描述了RPC的請求信息(URI/類/方法/參數/返回值)。因爲XML-RPC只能使用有限的數據類型種類和一些簡單的數據結構,SOAP能支持更多的類型和數據結構。優點是跨語言,非常適合異步通信和針對松耦合的C/S,缺點是必須做很多運行時檢查。

· REST一般用來和SOAP做比較,它採用簡單的URL方式來代替一個對象,優點是輕量,可讀性較好,不需要其他類庫支持,缺點是URL可能會很長,不容易解析。


3. Java RMI

Java RMI(Romote Method Invocation)是一種基於Java的遠程方法調用技術,是Java特有的一種RPC實現。它能夠部署在不同主機上的Java對象之間進行透明的通信與方法調用。


RMI工作原理:


首先,在一個JVM中啓動rmiregistry服務,啓動時可以指定服務監聽的端口,也可以使用默認的端口。

其次,RMIServer在本地先實例化一個提供服務的實現類,然後通過RMI提供的Naming,Context,Registry等類的bind或rebind方法將剛纔實例化好的實現類註冊到RMIService上並對外暴露一個名稱。

最後,RMIClient通過本地的接口和一個已知的名稱(即RMIServer暴露出的名稱)再使用RMI提供的Naming,Context,Registry等類的lookup方法從RMIService那拿到實現類。這樣雖然本地沒有這個類的實現類,但所有的方法都在接口裏了,想怎麼調就怎麼調。

RMI 採用stubs 和 skeletons 來進行遠程對象(remote object)的通訊。stub 充當遠程對象的客戶端代理,有着和遠程對象相同的遠程接口,遠程對象的調用實際是通過調用該對象的客戶端代理對象stub來完成的,通過該機制RMI就好比它是本地工作,採用tcp/ip協議,客戶端直接調用服務端上的一些方法。優點是強類型,編譯期可檢查錯誤,缺點是隻能基於Java語言,客戶機與服務器緊耦合。

具體RMI的原理可參考:java RMI原理詳解

深入理解rmi原理

RMI使用Demo:
定義RMI對外服務接口:RMI接口方法定義必須顯示聲明拋出RemoteException異常。

package com.yyy.RMIDemo.java.server;
 
import java.rmi.Remote;
import java.rmi.RemoteException;
 
/**
 * RMI對外服務接口
 * @author 
 *
 */
public interface HelloService extends Remote{
    public String sayHello(String someone) throws RemoteException;
}
服務端接口實現:服務端方法實現必須繼承UnicastRemoteObject類,該類定義了服務調用方與服務提供方對象實例,並建立一對一的連接。
package com.yyy.RMIDemo.java.server;
 
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
 
/**
 * 服務器端接口實現
 * @author 
 *
 */
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
 
    private static final long serialVersionUID = 4176511759435216154L;
 
    protected HelloServiceImpl() throws RemoteException {
        super();
        // TODO Auto-generated constructor stub
    }
 
    @Override
    public String sayHello(String someone) throws RemoteException {
        // TODO Auto- generated method stub
        return "Hello" + someone;
    }
 
}
RMI的通信端口是隨機產生的,因此有可能被防火牆攔截,爲了防止被防火牆攔截,需要強制指定RMI的通信端口。一般通過自定義一個RMISocketFactory類來實現,代碼如下:

package com.yyy.RMIDemo.java.server;
 
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.server.RMISocketFactory;
 
public class CustomerSocketFactory extends RMISocketFactory{
 
    
    @Override
    public ServerSocket createServerSocket(int port) throws IOException {
        // TODO Auto-generated method stub
        if (port == 0) {
            port = 8051;
        }
        System.out.println("rmi notify port : " + port);
        return new ServerSocket(port);
    }
 
    //指定通信端口,防止被防火牆攔截
    @Override
    public Socket createSocket(String host, int port) throws IOException {
        // TODO Auto-generated method stub
        return new Socket(host, port);
    }
 
}


服務端RMI服務啓動:
package com.yyy.RMIDemo.java.server;
 
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.RMISocketFactory;
 
public class ServerMain {
    public static void main(String[] args) throws Exception {
        //創建服務
        HelloService helloService = new HelloServiceImpl();
        //註冊服務
        LocateRegistry.createRegistry(8801);
        //指定通信端口,防止被防火牆攔截
        RMISocketFactory.setSocketFactory(new CustomerSocketFactory());
        
        Naming.bind("rmi://localhost:8801/helloService", helloService);
        
        System.out.println("ServceMain provide service now");
    }
}

客戶端遠程調用RMI服務代碼:

package com.yyy.RMIDemo.java.client;
 
import java.rmi.Naming;
 
import com.yyy.RMIDemo.java.server.HelloService;
 
public class ClientMain {
    public static void main(String[] args) throws Exception {
        HelloService helloService = 
                (HelloService)Naming.lookup("rmi://localhost:8801/helloService");
        System.out.println("RMI 客戶端接收到的結果是:" + helloService.sayHello("RMI"));
    }
}
先運行服務器端程序ServerMain,然後運行ClinetMain,運行結果如下:
RMI 客戶端接收到的結果是:HelloRMI


4. Hessian 
Hessian 是由 caucho 提供的一個基於 binary-RPC 實現的遠程通訊 library 。Hessian基於Http協議進行傳輸。


Binary-RPC 是一種和 RMI 類似的遠程調用的協議,它和 RMI 的不同之處在於它以標準的二進制格式來定義請求的信息 ( 請求的對象、方法、參數等 ) ,這樣的好處是什麼呢,就是在跨語言通訊的時候也可以使用。傳輸協議基於TCP。


Hessian可通過Servlet提供遠程服務,需要將匹配某個模式的請求映射到Hessian服務。也可Spring框架整合,通過它的DispatcherServlet可以完成該功能,DispatcherServlet可將匹配模式的請求轉發到Hessian服務。Hessian的server端提供一個servlet基類, 用來處理髮送的請求,而Hessian的這個遠程過程調用,完全使用動態代理來實現的,,建議採用面向接口編程,Hessian服務通過接口暴露。

Hessian處理過程示意圖:

客戶端——>序列化寫到輸出流——>遠程方法(服務器端)——>序列化寫到輸出流 ——>客戶端讀取輸入流——>輸出結果

Spring + Hessian實現服務器端的demo:
在工程中導入hessian的jar包

在web.xml中,我們配置SpringMVC的DispatcherServlet:


    <!-- Spring hessian servlet -->
    <servlet>  
        <servlet-name>hessian</servlet-name>  
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
        <init-param>  
            <param-name>contextConfigLocation</param-name>  
            <param-value>classpath:spring-hessian.xml</param-value>  
        </init-param>  
        <load-on-startup>1</load-on-startup>  
        <async-supported>true</async-supported>  
    </servlet>  
    <servlet-mapping>  
        <servlet-name>hessian</servlet-name>  
        <url-pattern>/pubservice/*</url-pattern>  
    </servlet-mapping>
spring-hessian.xml的配置:

    <bean id="/testService" class="com.example.platform.hession.MyHessianServiceExporter">
        <property name="service" ref="testServiceImpl" />
        <property name="serviceInterface" value="com.xinwei.platform.pubservice.TestService" />    
    </bean>

使用了org.springframework.remoting.caucho.HessianServiceExporter來發布服務。將程序部署在tomcat中。如果我們想從HessianServiceExporter的handleRequest方法中可以獲得客戶端request,那我們就會有很多種方法得到此request;Aop方式或重寫方法,我們採用重寫的方式:

/**
 * 
 */
package com.example.platform.hession;
 
import javax.servlet.ServletRequest;
 
/**
 * Hession service線程上下文,用以線程安全地保存客戶端request
 * @author yangyunyun
 *
 */
public class HessionContext {
    private ServletRequest request;
    private static final ThreadLocal<HessionContext> localContext
                            = new ThreadLocal<HessionContext>(){
        @Override
        public HessionContext initialValue(){
            return new HessionContext();
        }    
    };
    
    private HessionContext(){
        
    }
 
 
    public static ServletRequest getRequest() {
        return localContext.get().request;
    }
 
 
    public static void setRequest(ServletRequest request) {
        localContext.get().request = request;
    }
    
    public static void clear() {
        localContext.get().request = null;
    }
    
    
    
    
}
自定義類:

/**
 * 
 */
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.remoting.caucho.HessianExporter;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.util.NestedServletException;
 
/**
 * @author 
 *
 */
public class MyHessianServiceExporter extends HessianExporter implements HttpRequestHandler {
 
    private static final Logger logger = LoggerFactory.getLogger(InmpServiceExporter.class);
        
 
    @Override
    public void handleRequest(HttpServletRequest request, 
                                HttpServletResponse response)
                                throws ServletException, IOException {
        // TODO Auto-generated method stub
        if (!"POST".equals(request.getMethod())){
            throw new HttpRequestMethodNotSupportedException(
                    request.getMethod(), new String[]{"POST"},
                    "HessionSeriviceExporter only supports POST requests");
        }
        response.setContentType(CONTENT_TYPE_HESSIAN);
        try {
            //保存request到Hession線程上下文
            HessionContext.setRequest(request);
            invoke(request.getInputStream(), response.getOutputStream());
        } catch (Throwable ex) {
            // TODO: handle exception
            logger.error("Hession skeleton invocation failed");
            throw new NestedServletException("Hession skeleton invocation failed", ex);
        } finally{
            HessionContext.clear();
        }
        
        
    }
 
}
Service開發中就可以直接使用 HessianContext.getRequest(); 來獲得客戶端的request了,所有的客戶端信息隨你所用了,而且是線程安全的。
服務器端服務接口:


package com.example.platform.pubservice;
 
public interface TestService {
    public String sayHello(String datas);
}
接口實現類:


package com.example.platform.pubservice.impl;
 
import com.example.platform.pubservice.TestService;
 
public class TestServiceImpl extends InmpHessianHandle implements TestService{
 
    @Override
    public String sayHello(String datas) {
        // TODO Auto-generated method stub
        return "Hello " + datas;
    }
 
}
client調用:

public class TestClient {
    public static void main(String[] args) {
        try {
            String url = "http://localhost:8080/HessianDemo/pubservice/testService";
            HessianProxyFactory factory = new HessianProxyFactory();
            factory.setOverloadEnabled(true);
            TestService testService = (TestService) factory.create(TestService.class, url);
            System.out.println(basic.sayHello("SW"));
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
Hessian的原理以及使用請參考: Hessian的使用以及理解

5. WebService

WebService是一種跨平臺的RPC技術協議。WebService技術棧由SOAP(簡單對象訪問協議)、UDDI(統一描述、發現與集成)、WSDL(網絡服務描述語言:用來描述Service、以及如何訪問WebService)組成。SOAP是一種使用XML進行數據編碼的通信協議。獨立於平臺、獨立於語言,簡單可擴展。

WebService常用的兩種實現:CXF和Axis2


詳解見 從零開始寫分佈式服務框架 1.3小節


目前三種主流的web服務實現方法:

REST(新型):表象化狀態轉變 (軟件架構風格)RESTEasy、Wink、CXF、Axis2…….

SOAP(比較成熟):簡單對象訪問協議  Xfire、Axis2、CXF、Axis1

XML-RPC(淘汰):遠程過程調用協議(慢慢被soap 所取代)

1、Java開發WebService最重要的兩個規範:

JSR-224 (JAX-WS:Java API for XML-Based Web Services ) ,主要使用soap協議,使用wsdl來描述;

JSR-311 (JAX-RS:The Java API for RESTful Web Services),簡化了 web service 的設計,它不再需要 wsdl ,也不再需要 soap 協議,而是通過最簡單的 http 協議傳輸數據 ( 包括 xml 或 json) 。既簡化了設計,也減少了網絡傳輸量(因爲只傳輸代表數據的 xml 或 json ,沒有額外的 xml 包裝)。

JAX-WS是針對WebService。而JAX-RS是針對RESTful HTTP Service。

RESTful HTTP Service相關介紹可以參考:RESTful Service API 設計最佳工程實踐和常見問題解決方案

JAX-WS與JAX-RS兩者是不同風格的SOA架構。前者以動詞爲中心,指定的是每次執行函數。而後者以名詞爲中心,每次執行的時候指的是資源。

JAX-RS是JAVA EE6 引入的一個新技術。是一個Java 編程語言的應用程序接口,支持按照表述性狀態轉移(REST)架構風格創建Web服務。JAX-RS使用了Java SE5引入的Java標註來簡化Web服務的客戶端和服務端的開發和部署。
JAX-WS規範是一組XML web services的JAVA API,JAX-WS允許開發者可以選擇RPC-oriented或者message-oriented 來實現自己的web services。
支持JAX-WS服務規範的框架有:CXF,Axis,Xfire。結合java語言均可可實現JAX-WS

支持JAX-RS服務規範的框架有: 

1.CXF——XFire和Celtix的合併 

2.Jersey——Sun公司的JAX-RS參考實現。 

3.RESTEasy——JBoss的JAX-RS項目。

4.Restlet——也許是最早的REST框架了,它JAX-RS之前就有了。

在分佈式服務框架中,除了實現RPC的特性以外,還包括負載均衡策略以及實現,服務的註冊、發佈與引入。服務的高可用策略以及服務治理等特性。

RESTful架構風格參考:RESTful 架構風格概述

RESTful Web 服務:教程
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章