在分散式時代的今天,我們經常使用rpc技術來實現不同機器的系統相互呼叫。原則上來說系統間跨程序的呼叫都屬於RPC範疇。
RPC的原理
RPC也就是遠端過程呼叫,一般用來實現部署在不同機器的系統之間的相互呼叫,使得呼叫方像訪問本地資源一樣透過網路傳輸去訪問遠端系統資源。
在rpc架構種有幾個重要的概念(角色):
Client code:客戶端呼叫方式程式碼,也就是我們經常說的消費方。負責發起rpc呼叫,為呼叫方客戶提供api。
Server code:服務端提供呼叫方式程式碼,簡單的,既然有了呼叫方,那麼就得有服務提供方。也就是生產端,服務端實現具體的業務邏輯。
Serializable/Deserialization:在rpc呼叫過程中,負責對透過網路傳輸的資料進行序列化與反序列化,不同的rpc產品有不同的實現方式。主要分為文字和二進位制兩大類。文字最為常見,就是我們經常使用的xml以及json。二進位制的序列化機制包括java原生的序列化機制,也包括常見的Hessian,protobuf,Thrift,MessagePack等。
Stub proxy:可以看做一個代理物件,遮蔽了rpc呼叫過程中複雜的網路處理邏輯。使得rpc呼叫就像是本地呼叫一樣的程式碼風格。
Transport:rpc底層的通訊傳輸模組,透過Socket在客戶端與服務端之間傳遞請求以及響應資訊
常見的RPC框架
常見的RPC框架有RMI,WebService,Thrift,gRPC,Http Client等等。今天著重瞭解RMI。
RMI
Java RMI是一種基於Java的遠端方法呼叫技術,是Java實現的一種rpc。它能使部署在不同機器上的Java物件之間進行通訊,方法呼叫
RMI有以下幾個特點:
支援多型性。這是RMI有區別於其他RPC框架的主要優勢之一。
只支援java語言。
使用了java原生的序列化機制。也就是必須實現java。io。Serializable介面。
底層基於BIO實現Socket。
由於BIO機制的原因,導致效能較差,所以在高效能的場景下不建議使用RMI。
RMI簡單入手
1)建立UserService介面:
public interface UserService extends Remote { public void save(String username) throws Exception ; }
2)建立UserServiceImpl實現類
public class UserServiceImpl extends UnicastRemoteObject implements UserService, Serializable { private static final long serialVersionUID = -9206357578911294402L; @Override public void save(String username) throws Exception { System。out。println(“rmi………………username=”+username); }
public UserServiceImpl ()throws Exception{
} }
服務端的實現要繼承UnicastRemoteObject,該類定義了服務呼叫方與服務提供方物件例項,並建立一對一的連線
3)UserServer服務註冊端
public class UserServer { public static void main(String[] args) throws Exception { UserService userService = new UserServiceImpl(); //註冊服務 LocateRegistry。createRegistry(8866); Naming。bind(“rmi://localhost:8866/userService”,userService); System。out。println(“Server code init finish………………。。。。。”); } }
4)UserClient:
public class UserClient { public static void main(String[] args) throws Exception { UserService userService = (UserService) Naming。lookup(“rmi://localhost:8866/userService”); userService。save(“張三”); } }
5)先執行UserServer的main方法,然後再執行UserClient的main方法。控制檯已經列印;
RMI穿透防火牆
RMI的通訊埠是隨機產生的,因此有可能被防火牆攔截。為了防止被攔截,需要強制RMI指定的通訊介面。一般透過自定義RMISocketFactory類來實現。
public class UserSocketFactory extends RMISocketFactory { @Override public Socket createSocket(String host, int port) throws IOException { return new Socket(host, port); }
@Override public ServerSocket createServerSocket(int port) throws IOException { if(port == 0){ port = 8866; } return new ServerSocket(port); } }
然後在使用的時候注入即可:
public static void main(String[] args) throws Exception { UserService userService = new UserServiceImpl(); //使用UserSocketFactory,指定通訊介面,防止防火牆攔截 RMISocketFactory。setSocketFactory(new UserSocketFactory());
//註冊服務 LocateRegistry。createRegistry(8866); Naming。bind(“rmi://localhost:8866/userService”,userService); System。out。println(“Server code init finish………………。。。。。”); } }
本人水平有限,難免有錯誤或遺漏之處,望大家指正和諒解,提出寶貴意見,願與之交流。