在分布式時代的今天,我們經常使用rpc技術來實現(xiàn)不同機器的系統(tǒng)相互調用闷盔。原則上來說系統(tǒng)間跨進程的調用都屬于RPC范疇弯洗。
RPC的原理
RPC也就是遠程過程調用,一般用來實現(xiàn)部署在不同機器的系統(tǒng)之間的相互調用逢勾,使得調用方像訪問本地資源一樣通過網絡傳輸去訪問遠端系統(tǒng)資源牡整。
在rpc架構種有幾個重要的概念(角色):
- Client code:客戶端調用方式代碼,也就是我們經常說的消費方溺拱。負責發(fā)起rpc調用逃贝,為調用方客戶提供api。
- Server code:服務端提供調用方式代碼迫摔,簡單的沐扳,既然有了調用方,那么就得有服務提供方句占。也就是生產端沪摄,服務端實現(xiàn)具體的業(yè)務邏輯。
- Serializable/Deserialization:在rpc調用過程中,負責對通過網絡傳輸?shù)臄?shù)據(jù)進行序列化與反序列化杨拐,不同的rpc產品有不同的實現(xiàn)方式祈餐。主要分為文本和二進制兩大類。文本最為常見哄陶,就是我們經常使用的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實現(xiàn)的一種rpc疫稿。它能使部署在不同機器上的Java對象之間進行通信培他,方法調用
RMI有以下幾個特點:
- 支持多態(tài)性。這是RMI有區(qū)別于其他RPC框架的主要優(yōu)勢之一遗座。
- 只支持java語言舀凛。
- 使用了java原生的序列化機制。也就是必須實現(xiàn)java.io.Serializable接口途蒋。
- 底層基于BIO實現(xiàn)Socket猛遍。
由于BIO機制的原因,導致性能較差号坡,所以在高性能的場景下不建議使用RMI懊烤。
RMI簡單入手
1)創(chuàng)建UserService接口:
public interface UserService extends Remote {
public void save(String username) throws Exception ;
}
2)創(chuàng)建UserServiceImpl實現(xiàn)類
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{
}
}
服務端的實現(xiàn)要繼承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類來實現(xiàn)。
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.......................");
}
}
本人水平有限,難免有錯誤或遺漏之處极景,望大家指正和諒解察净,提出寶貴意見,愿與之交流盼樟。