RPC
RPC(Remote Procedure Call,遠端過程呼叫)一般用來實現部署在不同機器上的系統之間的方法呼叫,使得程式能夠像訪問本地資源一樣,通過網路傳輸去訪問遠端系統資源。(此段百度的,不管來自哪兒,弄明白就是自己的)
RPC框架實現的架構原理:
下面來展示下本人拙劣的畫圖技術
Client:客戶端,服務消費者,負責發起RPC呼叫,通過遠端代理物件呼叫遠端服務。 Serialization/Deserilization:負責對RPC呼叫通過網路傳輸的內容進行序列化與反序列化。 Stub Proxy:代理物件,遮蔽RPC呼叫過程中複雜的網路處理邏輯,使得RPC呼叫透明化,能夠保持與本地呼叫一樣的程式碼風格。 TransPort:作為RPC框架底層的通訊傳輸模組,一般通過Socket在客戶端與服務端之間傳遞請求與應答資訊。
我所理解的原理過程(不對的地方求指教):
- 服務消費方(client)呼叫以本地呼叫方式呼叫服務;
- stub Proxy接收到呼叫後負責將方法、引數等組裝成可以序列化的Invocation物件;
- stub Proxy找到服務地址,建立socket連線並將訊息傳送到服務端;
- server端將接收到的資料反序列化,交給stub Proxy處理;
- server端將stub收集到的呼叫資訊分發給具體的方法去執行,這裡並不是真正的client去執行該方法,而是在server端通過反射生成的client動態物件去執行;
- RPC介面執行完畢,返回執行結果,思路類似。
RPC的簡易實現
技術方案
採用的是Socket通訊、動態代理與反射與Java原生的序列化,由於能力有限,只會基於BIO來實現,以後會嘗試用zookeeper、netty、redis等來實現!
具體實現
1、服務提供者提供的服務實現
package com.ooliuyue.simplRpc.test;
/**
* @Auther: ly
* @Date: 2019/3/14 11:45
*/
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return "hello " + name;
}
}
複製程式碼
2、客戶端獲取遠端服務,服務端暴露自己的服務,Socket來完成通訊工作(註釋比較詳細,一目瞭然)
package com.ooliuyue.simplRpc.rpc;
import com.ooliuyue.simplRpc.utils.StringUtils;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Auther: ly
* @Date: 2019/3/13 17:53
*/
public class SimpleRpc {
/**
*將指定服務暴露出來,供客戶端遠端呼叫
* @param service 需要暴露的服務
* @param port 暴露的埠號
* @throws Exception
*/
public static void export(final Object service,int port) throws Exception{
if (service == null)
throw new IllegalArgumentException("service not null");
if (port < 0 || port > 65535)
throw new IllegalArgumentException("port in [0,65535]");
System.out.println("Export Service:" + service.getClass().getName() + " port:" + port );
/**
* Socket程式設計步驟
* 伺服器端建立ServerSocket物件,呼叫accept方法返回Socket物件
* 客戶端建立Socket物件,通過埠連線到伺服器
* 客戶端、伺服器端都使用Socket中的getInputStream方法和getOutputStream方法獲得輸入流和輸出流,
* 進一步進行資料讀寫操作
*/
/**
* socket在伺服器端上的使用
* 1.getInputStream方法得到的是一個輸入流,服務端的Socket物件上的getInputStream方法得到的輸入流其實就是從客戶端傳送給伺服器端的資料流。
* 2.getOutputStream方法得到的是一個輸出流,服務端的Socket物件上的getOutputStream方法得到的輸出流其實就是傳送給客戶端的資料。
*/
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
final Socket socket = serverSocket.accept();
new Thread(() -> {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
try {
//將客戶端傳送給服務端的資料流反序列化成物件
input = new ObjectInputStream(socket.getInputStream());
String methodName = input.readUTF();
Class<?>[] paramTypes = (Class<?>[]) input.readObject();
Object[] arguments = (Object[]) input.readObject();
Method method = service.getClass().getMethod(methodName, paramTypes);
//反射呼叫服務實現,獲取執行結果
Object result = method.invoke(service, arguments);
//將執行結果反序列化,然後傳送給客戶端
output = new ObjectOutputStream(socket.getOutputStream());
output.writeObject(result);
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (input != null)
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
if (output != null)
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
/**
* 獲取遠端服務
* @param interfaceClass 服務介面class
* @param host 遠端IP地址
* @param port 遠端埠號
* @param <T> 指定介面的例項
* @return
* @throws Exception
*/
/**
*
* socket在客戶端上的使用
* 1.getInputStream方法可以得到一個輸入流,客戶端的Socket物件上的getInputStream方法得到輸入流其實就是從伺服器端返回的資料。
* 2.getOutputStream方法得到的是一個輸出流,客戶端的Socket物件上的getOutputStream方法得到的輸出流其實就是傳送給伺服器端的資料。
*/
public static <T> T getRemoteService(final Class<T> interfaceClass,final String host,final int port)
throws Exception {
verifyGetRemoteService(interfaceClass,host,port);
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class<?>[]{interfaceClass},(Object proxy, Method method, Object[] args) ->{
Socket socket = new Socket(host,port);
System.out.println("get remote service :" + interfaceClass.getName() + " from " + host + ":" + port);
//將遠端服務呼叫所需的介面類、方法名、引數列表等編碼後傳送給服務提供者
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
//result就是從伺服器返回的資料
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
Object result = input.readObject();
return result;
});
}
private static <T> void verifyGetRemoteService(final Class<T> interfaceClass, final String host, final int port) {
if (interfaceClass == null)
throw new IllegalArgumentException("interfaceClass not null");
if (!interfaceClass.isInterface())
throw new IllegalArgumentException("interfaceClass not a interface");
if (!StringUtils.isNotBlank(host))
throw new IllegalArgumentException("host not blank");
if (port < 0 || port > 65535)
throw new IllegalArgumentException("port in [0,65535]");
}
}
複製程式碼
3、測試 首先服務端暴露服務,執行main
package com.ooliuyue.simplRpc.test;
/**
* @Auther: ly
* @Date: 2019/3/14 11:43
*/
import com.ooliuyue.simplRpc.rpc.SimpleRpc;
/**
* 服務提供者
*/
public class Server {
public static void main(String[] args) throws Exception {
HelloService helloService = new HelloServiceImpl();
SimpleRpc.export(helloService,1000);
}
}
複製程式碼
執行結果: Export Service:com.ooliuyue.simplRpc.test.HelloServiceImpl port:1000
然後客戶端進行呼叫,執行main
package com.ooliuyue.simplRpc.test;
import com.ooliuyue.simplRpc.rpc.SimpleRpc;
/**
* @Auther: ly
* @Date: 2019/3/14 11:43
*/
public class Client {
public static void main(String[] args) throws Exception {
HelloService remoteService = SimpleRpc.getRemoteService(HelloService.class, "127.0.0.1", 1000);
String str = remoteService.hello("王大錘");
System.out.println(str);
}
}
複製程式碼
執行結果 get remote service :com.ooliuyue.simplRpc.test.HelloService from 127.0.0.1:1000 hello 王大錘