常用的RPC框架

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的協議有很多,比如Java RMI、WebService的RPC風格、Hession、Thrift、REST API

2. RPC、RMI、SOAP、REST的區別RMI(remote method invocation,面向對象的遠程方法調用)

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


常用的RPC框架


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,缺點是必須做很多運行時檢查。

比較,它採用簡單的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使用Demo:

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

<code>package com.yyy.RMIDemo.java.server;



import java.rmi.Remote;

import java.rmi.RemoteException;



/**

* RMI對外服務接口
*

*/

public interface HelloService extends Remote{

\tpublic String sayHello(String someone) throws RemoteException;

}/<code>

服務端接口實現:服務端方法實現必須繼承UnicastRemoteObject類,該類定義了服務調用方與服務提供方對象實例,並建立一對一的連接。

<code>package com.yyy.RMIDemo.java.server;



import java.rmi.RemoteException;

import java.rmi.server.UnicastRemoteObject;



/**

* 服務器端接口實現

*

*/

public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {



\tprivate static final long serialVersionUID = 4176511759435216154L;



\tprotected HelloServiceImpl() throws RemoteException {

\t\tsuper();

\t\t// TODO Auto-generated constructor stub

\t}



\t@Override

\tpublic String sayHello(String someone) throws RemoteException {

\t\t// TODO Auto- generated method stub

\t\treturn "Hello" + someone;

\t}



}

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{



\t

\t@Override

\tpublic ServerSocket createServerSocket(int port) throws IOException {

\t\t// TODO Auto-generated method stub

\t\tif (port == 0) {

\t\t\tport = 8051;

\t\t}

\t\tSystem.out.println("rmi notify port : " + port);

\t\treturn new ServerSocket(port);

\t}



\t//指定通信端口,防止被防火牆攔截

\t@Override

\tpublic Socket createSocket(String host, int port) throws IOException {

\t\t// TODO Auto-generated method stub

\t\treturn new Socket(host, port);

\t}



}
/<code>

RMI的通信端口是隨機產生的,因此有可能被防火牆攔截,為了防止被防火牆攔截,需要強制指定RMI的通信端口。一般通過自定義一個RMISocketFactory類來實現,代碼如下:

<code>package com.yyy.RMIDemo.java.server;



import java.rmi.Naming;

import java.rmi.registry.LocateRegistry;

import java.rmi.server.RMISocketFactory;



public class ServerMain {

\tpublic static void main(String[] args) throws Exception {

\t\t//創建服務

\t\tHelloService helloService = new HelloServiceImpl();

\t\t//註冊服務

\t\tLocateRegistry.createRegistry(8801);

\t\t//指定通信端口,防止被防火牆攔截

\t\tRMISocketFactory.setSocketFactory(new CustomerSocketFactory());

\t\t

\t\tNaming.bind("rmi://localhost:8801/helloService", helloService);

\t\t

\t\tSystem.out.println("ServceMain provide service now");

\t}

}
客戶端遠程調用RMI服務代碼:
package com.yyy.RMIDemo.java.client;



import java.rmi.Naming;



import com.yyy.RMIDemo.java.server.HelloService;



public class ClientMain {

\tpublic static void main(String[] args) throws Exception {

\t\tHelloService helloService =

\t\t\t\t(HelloService)Naming.lookup("rmi://localhost:8801/helloService");

\t\tSystem.out.println("RMI 客戶端接收到的結果是:" + helloService.sayHello("RMI"));

\t}

}/<code>

服務端RMI服務啟動:

<code>package com.yyy.RMIDemo.java.server;



import java.rmi.Naming;

import java.rmi.registry.LocateRegistry;

import java.rmi.server.RMISocketFactory;



public class ServerMain {

\tpublic static void main(String[] args) throws Exception {


\t\t//創建服務

\t\tHelloService helloService = new HelloServiceImpl();

\t\t//註冊服務

\t\tLocateRegistry.createRegistry(8801);

\t\t//指定通信端口,防止被防火牆攔截

\t\tRMISocketFactory.setSocketFactory(new CustomerSocketFactory());

\t\t

\t\tNaming.bind("rmi://localhost:8801/helloService", helloService);

\t\t

\t\tSystem.out.println("ServceMain provide service now");

\t}

}/<code>

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

<code>package com.yyy.RMIDemo.java.client;



import java.rmi.Naming;



import com.yyy.RMIDemo.java.server.HelloService;



public class ClientMain {

\tpublic static void main(String[] args) throws Exception {

\t\tHelloService helloService =

\t\t\t\t(HelloService)Naming.lookup("rmi://localhost:8801/helloService");

\t\tSystem.out.println("RMI 客戶端接收到的結果是:" + helloService.sayHello("RMI"));

\t}

}/<code>

先運行服務器端程序ServerMain,然後運行ClinetMain,運行結果如下:

<code>RMI 客戶端接收到的結果是:HelloRMI/<code>

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:

<code>

\t<servlet>

\t\t<servlet-name>hessian/<servlet-name>

\t\t<servlet-class>org.springframework.web.servlet.DispatcherServlet/<servlet-class>

\t\t<init-param>

\t\t\t<param-name>contextConfigLocation/<param-name>

\t\t\t<param-value>classpath:spring-hessian.xml/<param-value>

\t\t/<init-param>

\t\t<load-on-startup>1/<load-on-startup>

\t\t<async-supported>true/<async-supported>

\t/<servlet>

\t<servlet-mapping>

\t\t<servlet-name>hessian/<servlet-name>

\t\t<url-pattern>/pubservice/*/<url-pattern>

\t/<servlet-mapping>/<code>

spring-hessian.xml的配置:

<code><bean>

\t\t<property>

\t\t<property>\t

\t/<bean>/<code>

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

<code>/**

*

*/

package com.example.platform.hession;



import javax.servlet.ServletRequest;



/**

* Hession service線程上下文,用以線程安全地保存客戶端request


*

*/

public class HessionContext {

\tprivate ServletRequest request;

\tprivate static final ThreadLocal<hessioncontext> localContext

\t\t\t\t\t\t\t= new ThreadLocal<hessioncontext>(){

\t\t@Override

\t\tpublic HessionContext initialValue(){

\t\t\treturn new HessionContext();

\t\t}\t

\t};

\t

\tprivate HessionContext(){

\t\t

\t}






\tpublic static ServletRequest getRequest() {

\t\treturn localContext.get().request;

\t}





\tpublic static void setRequest(ServletRequest request) {

\t\tlocalContext.get().request = request;

\t}

\t

\tpublic static void clear() {

\t\tlocalContext.get().request = null;

\t}

\t

\t
}/<hessioncontext>/<hessioncontext>/<code>

自定義類:

<code>/**

*

*/



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;



/**

*

*/

public class MyHessianServiceExporter extends HessianExporter implements HttpRequestHandler {



\tprivate static final Logger logger = LoggerFactory.getLogger(InmpServiceExporter.class);

\t\t



\t@Override

\tpublic void handleRequest(HttpServletRequest request,

\t\t\t\t\t\t\t\tHttpServletResponse response)

\t\t\t\t\t\t\t\tthrows ServletException, IOException {

\t\t// TODO Auto-generated method stub

\t\tif (!"POST".equals(request.getMethod())){

\t\t\tthrow new HttpRequestMethodNotSupportedException(

\t\t\t\t\trequest.getMethod(), new String[]{"POST"},

\t\t\t\t\t"HessionSeriviceExporter only supports POST requests");

\t\t}

\t\tresponse.setContentType(CONTENT_TYPE_HESSIAN);

\t\ttry {

\t\t\t//保存request到Hession線程上下文

\t\t\tHessionContext.setRequest(request);

\t\t\tinvoke(request.getInputStream(), response.getOutputStream());

\t\t} catch (Throwable ex) {

\t\t\t// TODO: handle exception

\t\t\tlogger.error("Hession skeleton invocation failed");

\t\t\tthrow new NestedServletException("Hession skeleton invocation failed", ex);

\t\t} finally{

\t\t\tHessionContext.clear();

\t\t}

\t\t

\t\t

\t}



}/<code>

Service開發中就可以直接使用 HessianContext.getRequest(); 來獲得客戶端的request了,所有的客戶端信息隨你所用了,而且是線程安全的。


服務器端服務接口:

<code>package com.example.platform.pubservice;



public interface TestService {

\tpublic String sayHello(String datas);

}/<code>

接口實現類:

<code>package com.example.platform.pubservice.impl;



import com.example.platform.pubservice.TestService;



public class TestServiceImpl extends InmpHessianHandle implements TestService{



\t@Override

\tpublic String sayHello(String datas) {

\t\t// TODO Auto-generated method stub

\t\treturn "Hello " + datas;

\t}



}/<code>

client調用:

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

}

}

}/<code>

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、Axis1X

ML-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的特性以外,還包括負載均衡策略以及實現,服務的註冊、發佈與引入。服務的高可用策略以及服務治理等特性。

JAVA進階架構程序員福利:我這裡還總結整理了比較全面的JAVA相關的面試資料,都已經整理成了

PDF版,這些都可以分享給大家,關注私信我:【806】,免費領取!


分享到:


相關文章: