
RPC(Remote Procedure Call)—遠程過程調(diào)用,它是一種不需要了解底層網(wǎng)絡(luò)技術(shù)的協(xié)議,就可以通過網(wǎng)絡(luò),請求遠程服務(wù)器上的服務(wù)。
我們可以調(diào)用本地的一個 RPC , 實際上,響應(yīng)結(jié)果是由遠程服務(wù)器返回的 。RPC 有很多種類型 , 比如 SOAP、Thrift、 protocol buffers 等等 )。不同的技術(shù)棧,可以通過其接口定義,很方便地生成客戶端或服務(wù)端的樁代碼 。
- SOAP(Simple Object Access Protocol),即簡單對象訪問協(xié)議。它是交換數(shù)據(jù)的一種協(xié)議規(guī)范,是一種輕量的、簡單的、基于XML的協(xié)議,它被設(shè)計成可在 WEB 上交換結(jié)構(gòu)化或固化的信息。
- Thrift 是一種接口描述語言和二進制通訊協(xié)議,它被用來定義和創(chuàng)建跨語言服務(wù) 。它是由 Facebook 為支持 “ 大規(guī)模跨語言服務(wù)” 而開發(fā)的 。
- protocol buffer 是 google 的一個開源項目。它可串行化結(jié)構(gòu)化的數(shù)據(jù)。就像 XML ,但它比 XML 更小 、 更快 、 也更簡單 。 我們可以定義自己的數(shù)據(jù)結(jié)構(gòu),然后使用代碼生成器所生成的代碼來讀寫這個數(shù)據(jù)結(jié)構(gòu) 。甚至可以在無需重新部署程序的情況下更新自定義的數(shù)據(jù)結(jié)構(gòu)。
比如, 我們可以讓一個 Java 服務(wù)對外表現(xiàn)為一個 SOAP 服務(wù)接口 , 調(diào)用方可以依據(jù)使用 WSDL( Web Service Definition Language,Web 服務(wù)描述語言 ) 接口定義內(nèi)容,來生成基于 .NET 的客戶端代碼 。 這些技術(shù)都有一個共同點 , 那就是使用本地調(diào)用的方式和遠程服務(wù)器進行交互。
Java RMI、 Thrift、 protocol buffers 是以二進制作為消息格式;而 SOAP 用的是 XML,而且綁定特定的網(wǎng)絡(luò)協(xié)議(HTTP)。不同的網(wǎng)絡(luò)協(xié)議,特性也不同。比如, TCP 協(xié)議能夠保證消息送達對端;而 UDP 雖然會丟包,但開銷較小。 所以我們可以根據(jù)實際應(yīng)用場景來選擇不同的技術(shù)棧。
這些 RPC 實現(xiàn)一般會提供工具,快速生成服務(wù)端或客戶端的樁代碼 , 這樣我們就可以直接開始編碼 。
實際應(yīng)用中,RPC 調(diào)用方式并沒有那么好。一開始,問題還不那么明顯 , 但慢慢就會暴露出來 , 其帶來的負面影響要遠遠大于一開始快速編碼所帶來的好處。
(1)耦合
比如 Java RMI(Remote Method Invocation), 會導(dǎo)致服務(wù)端和客戶端緊密耦合 , 因為雙方都必須使用相同的 Java 技術(shù)棧。而 Thrift 和 protocol buffers 可以支持不同編程語言 , 從而在一定程度上緩解了這個問題 。
(2)遠程調(diào)用的復(fù)雜性
RPC 的原意是隱藏遠程調(diào)用的復(fù)雜性 。但遠程調(diào)用特定涉及網(wǎng)絡(luò)通信時間、對傳輸對象的序列化與反序列化,這樣都會影響性能。
還有網(wǎng)絡(luò)本身并不可靠。所以即使客戶端和服務(wù)端都正常,也會因為網(wǎng)絡(luò)問題,導(dǎo)致服務(wù)調(diào)用失敗。還有黑客攻擊情況也要予以考慮。
(3)脆弱性
假設(shè)我們使用 Java RMI 定義了一個服務(wù)接口:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface CustomerRemote extends Remote {
/**
* 查找客戶
*
* @param id
* @return
* @throws RemoteException
*/
Customer find(String id) throws RemoteException;
/**
* 創(chuàng)建客戶
* @param firstname
* @param surname
* @param email
* @return
* @throws RemoteException
*/
Customer create(String firstname, String surname, String email) throws RemoteException;
}
在這個接口定義中 , “創(chuàng)建客戶” 方法,接受姓名及電子郵件作為入?yún)?。 如果客戶端希望只通過電子郵件就可以創(chuàng)建客戶,我們可以在這個接口中,新定義一個方法 , 如下所示:
Customer create( String email) throws RemoteException;
因為重新定義了接口,所以所有的客戶端都需要重新生成樁,即使某些客戶端根本不需要這個新方法 。這是一個普遍現(xiàn)象,所以認為RPC 調(diào)用方式是脆弱的。
此外,還有一種形式的脆弱。 現(xiàn)在讓我們來看看 Customer 對象:
import java.io.Serializable;
public class Customer implements Serializable {
private String firstName;
private String surName;
private String email;
private String age;
}
這里的 Customer 客戶對象,除了之前在接口中所看到的 firstName、surName 和 age 之外,還定義了 age 屬性。后來發(fā)現(xiàn)這個屬性,完全沒有任何客戶端在使用它,是一個冗余字段。但不能直接在服務(wù)端刪除它,因為會影響各個調(diào)用者的 Customer 客戶對象,即使是基于二進制消息格式的 RPC 也存在同樣的問題,即服務(wù)端和客戶端無法實現(xiàn)部署分離。
如果一定要選用 RPC 調(diào)用方式,那么注意不要對遠程調(diào)用過度抽象,讓客戶端留意網(wǎng)絡(luò)調(diào)用的影響。還要確保我們可以獨立地升級服務(wù)端接口,而不是強迫客戶端升級。