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,遠程過程調用)
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】,免費領取!
閱讀更多 欣然1013 的文章