參考鏈接
RPC相關(guān)
https://blog.csdn.net/weixin_38327420/article/details/85064617
http://www.itdecent.cn/p/32ca4fd5a7e2
http://www.itdecent.cn/p/5b90a4e70783
https://www.cnblogs.com/baizhanshi/p/6053213.html
java序列化
https://blog.csdn.net/so_geili/article/details/78931742
目錄
一、RPC的概念
二、RPC通信過程
三、RPC實例
一、RPC的概念
RPC:remote procedure call Protocol 遠程過程調(diào)用
RPC 是一種通過網(wǎng)絡(luò)從遠程計算機上請求服務(wù)但不需要關(guān)心底層的網(wǎng)絡(luò)通信細節(jié)的通信協(xié)議。簡單來說,RPC 是一種通信協(xié)議。
RPC允許像調(diào)用本地服務(wù)一樣調(diào)用遠程服務(wù),讓調(diào)用者對網(wǎng)絡(luò)通信細節(jié)透明。
RPC 的架構(gòu)
通常為 C/S 模型。一個典型的 RPC 結(jié)構(gòu)組成有如下幾個部分:
- 通信模塊
通信模塊就是用于在網(wǎng)絡(luò)上傳輸數(shù)據(jù)包的,它一般不會對數(shù)據(jù)包內(nèi)容作任何處理。 RPC 的通信有同步和異步兩種通信模式。
- Stub 程序
可以簡單理解成是一個代理。它上面記載了所有支持的遠程調(diào)用接口,服務(wù)端和客戶端各自持有一份完全相同的 Stub 程序。我們的 RPC 的過程,其本質(zhì)上就是去調(diào)用各自手里的這份 Stub 程序上的接口而已。
- 調(diào)度程序
調(diào)度器,運行在服務(wù)端。接收來自通信模塊的請求,并根據(jù)請求中的標識符來選擇對應(yīng)的 Stub 程序去執(zhí)行。
二、RPC通信過程
要讓網(wǎng)絡(luò)通信細節(jié)對使用者透明,我們需要對通信細節(jié)進行封裝,一個RPC調(diào)用的流程涉及的通信細節(jié)如下:

1.服務(wù)消費方(client)調(diào)用以本地調(diào)用方式調(diào)用服務(wù);
2.client stub接收到調(diào)用后負責(zé)將方法、參數(shù)等組裝成能夠進行網(wǎng)絡(luò)傳輸?shù)南Ⅲw;
3. client stub找到服務(wù)地址,并將消息發(fā)送到服務(wù)端;
4.server stub收到消息后進行解碼;
5.server stub根據(jù)解碼結(jié)果調(diào)用本地的服務(wù);
6.本地服務(wù)執(zhí)行并將結(jié)果返回給server stub;
7.server stub將返回結(jié)果打包成消息并發(fā)送至消費方;
8.client stub接收到消息,并進行解碼;
9.服務(wù)消費方得到最終結(jié)果。
三、RPC實例
本文利用的實例代碼,見 github
在本地模擬RPC運行原理,運行時先運行服務(wù)器(ProviderApp)端代碼,再運行客戶端(ComsumerApp)代碼。
利用RPC框架簡易實現(xiàn)1+2=3遠程調(diào)用的例子??蛻舳税l(fā)送兩個參數(shù),服務(wù)端返回兩個數(shù)字的相加結(jié)果。接下來對代碼的主要內(nèi)容進行說明。
主要分為客戶端、服務(wù)提供端,請求處理三個部分。

1)服務(wù)端
服務(wù)端提供客戶端所期待的服務(wù),一般包括三個部分:服務(wù)接口,服務(wù)實現(xiàn)以及服務(wù)的注冊暴露三部分
- 服務(wù)接口
定義Calculate接口
public interface Calculator {
int add(int a, int b);
}
- 服務(wù)實現(xiàn)
實現(xiàn)具體的加法計算,傳入兩個參數(shù),返回計算結(jié)果
public class CalculatorImpl implements Calculator {
public int add(int a, int b) {
return a + b;
}
}
- 服務(wù)暴露,服務(wù)暴露功能實現(xiàn)
只有把服務(wù)暴露出來,才能讓客戶端進行調(diào)用,這是RPC框架功能之一。
RPC服務(wù)端邏輯 :首先創(chuàng)建
ServerSocket負責(zé)監(jiān)聽特定端口并接收客戶連接請求,然后使用Java原生的序列化/反序列化機制來解析得到請求,包括所調(diào)用方法的名稱、參數(shù)列表和實參,最后反射調(diào)用服務(wù)端對服務(wù)接口的具體實現(xiàn)并將得到的結(jié)果回傳至客戶端。至此,一次簡單PRC調(diào)用的服務(wù)端流程執(zhí)行完畢。
//服務(wù)暴露
public class ProviderApp {
private static Logger log = LoggerFactory.getLogger(ProviderApp.class);
private Calculator calculator = new CalculatorImpl();
public static void main(String[] args) throws IOException {
new ProviderApp().run();
}
//服務(wù)端暴露服務(wù)具體實現(xiàn)
private void run() throws IOException {
ServerSocket listener = new ServerSocket(9090);
try {
while (true) {
Socket socket = listener.accept();
try {
// 將請求反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object object = objectInputStream.readObject();
log.info("request is {}", object);
// 調(diào)用服務(wù)
int result = 0;
if (object instanceof CalculateRpcRequest) {
CalculateRpcRequest calculateRpcRequest = (CalculateRpcRequest) object;
if ("add".equals(calculateRpcRequest.getMethod())) {
result = calculator.add(calculateRpcRequest.getA(), calculateRpcRequest.getB());
} else {
throw new UnsupportedOperationException();
}
}
// 返回結(jié)果
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(new Integer(result));
} catch (Exception e) {
log.error("fail", e);
} finally {
socket.close();
}
}
} finally {
listener.close();
}
}
}
2)客戶端
客戶端調(diào)用服務(wù)端所提供的服務(wù),包括服務(wù)接口、服務(wù)引用兩個部分。
- 服務(wù)接口:與服務(wù)端共享同一個服務(wù)接口
public interface Calculator {
int add(int a, int b);
}
- 服務(wù)引用
消費端通過RPC框架進行遠程調(diào)用
public class ComsumerApp {
private static Logger log = LoggerFactory.getLogger(ComsumerApp.class);
public static void main(String[] args) {
Calculator calculator = new CalculatorRemoteImpl();
int result = calculator.add(1, 2);
log.info("result is {}", result);
}
}
- 服務(wù)引用實現(xiàn)
通過CalculatorRemoteImpl,封裝客戶端引用RPC的邏輯。
RPC客戶端邏輯:首先創(chuàng)建Socket客戶端并與服務(wù)端建立鏈接,然后使用Java原生的序列化/反序列化機制將調(diào)用請求發(fā)送給客戶端,包括所調(diào)用方法的名稱、參數(shù)列表將服務(wù)端的響應(yīng)返回給用戶即可。至此,一次簡單PRC調(diào)用的客戶端流程執(zhí)行完畢。
分布式應(yīng)用下,一個服務(wù)可能有多個實例,比如Service B,可能有ip地址為198.168.1.11和198.168.1.13的兩個實例,lookupProviders其實就是在尋找要調(diào)用的服務(wù)的實例列表。在分布式應(yīng)用下,通常會有一個服務(wù)注冊中心,來提供查詢實例列表的功能。查到實例列表之后利用chooseTarget選擇具體調(diào)用哪個實例,內(nèi)部是一個負載均衡策略。
代碼僅實現(xiàn)了一個非常簡單的RPC框架,不考慮查找實例和動態(tài)代理問題,這里將端口固定為9090,ip地址為本地地址
127.0.0.1
public class CalculatorRemoteImpl implements Calculator {
public static final int PORT = 9090;
private static Logger log = LoggerFactory.getLogger(CalculatorRemoteImpl.class);
public int add(int a, int b) {
List<String> addressList = lookupProviders("Calculator.add");
String address = chooseTarget(addressList);
try {
Socket socket = new Socket(address, PORT);
// 將請求序列化
CalculateRpcRequest calculateRpcRequest = generateRequest(a, b);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
// 將請求發(fā)給服務(wù)提供方
objectOutputStream.writeObject(calculateRpcRequest);
// 將響應(yīng)體反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object response = objectInputStream.readObject();
log.info("response is {}", response);
if (response instanceof Integer) {
return (Integer) response;
} else {
throw new InternalError();
}
} catch (Exception e) {
log.error("fail", e);
throw new InternalError();
}
}
private CalculateRpcRequest generateRequest(int a, int b) {
CalculateRpcRequest calculateRpcRequest = new CalculateRpcRequest();
calculateRpcRequest.setA(a);
calculateRpcRequest.setB(b);
calculateRpcRequest.setMethod("add");
return calculateRpcRequest;
}
private String chooseTarget(List<String> providers) {
if (null == providers || providers.size() == 0) {
throw new IllegalArgumentException();
}
return providers.get(0);
}
public static List<String> lookupProviders(String name) {
List<String> strings = new ArrayList();
strings.add("127.0.0.1");
return strings;
}
}
request
CalculateRpcRequest類里是序列化定義相關(guān)內(nèi)容,實現(xiàn)Serializable接口
public class CalculateRpcRequest implements Serializable {
private static final long serialVersionUID = 7503710091945320739L;
private String method;
private int a;
private int b;
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
@Override
public String toString() {
return "CalculateRpcRequest{" +
"method='" + method + '\'' +
", a=" + a +
", b=" + b +
'}';
}
}
運行結(jié)果
如下,最終得到1+2=3的結(jié)果。
