需要具備的知識點
1. Java的網絡通信Socket和IO,忘記的趕緊去百度一個基於Socket的聊天的Demo,就差不多了。爲什麼要學習Socket和IO呢?因爲很大部分RPC框架底層就是基於Socke進行通信的,所以他是必不可少的知識點;
2. Java的JDK動態代理知識;
3. Java的序列化知識,簡單理解,就是把java對象進行序列化用於網絡傳輸,使得機器和機器之間方便進行數據通信的。
4. Java的反射基礎知識;
RPC介紹
什麼是RPC?
RPC(Remote Procedure Call Protocol)—— 遠程過程調用協議,它是一種通過網絡請求從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。
爲什麼需要RPC?
因爲近年來,隨着分佈式和微服務技術的發展,把單個服務程序按照功能拆分成多個模塊,並把每個模塊部署到多臺機器上,多個模塊之間相互配合提供服務。而多個機器之間相互配合需要進行通信,而如果直接寫原生的Socket代碼進行通信會造成代碼冗餘、資源浪費等。因此就產生了RPC框架,而RPC框架就是爲機器之間進行通信服務的,讓開發者不必關心底層實現,只需要按照指定的即可。
作爲程序員要做的不僅是學會如何使用,而且還要理解它的底層原理,這樣才成爲一位合格的開發者。
有哪些RPC的產物
Dubbo、Thrift、gRpc等。
我們要做什麼
本章是,自定義一個簡單的RPC,主要的功能是實現遠程接口服務調用,至於一些其他的功能不會實現。本章主要講的是調用的原理,知道了原理就大致瞭解了大部分RPC框架的是如何通信工作的。只需要知道調用的原理即可,其他的細節的東西不會多說,有些興趣的話可以留言或者自己查看。
RPC的簡單流程模型
一個簡單的RPC調用過程,大致流程:
最簡單的理解是,客戶端封裝一個服務端對象(包含類名,方法名,參數)通過socket傳遞給服務端,服務端拿到這個對象進行反射執行該類的方法,然後得到結果,在通過socket返回給客服端這個過程,就是一個rpc通信的基本過程。其他框架的也都是在此基礎上進行封裝,提供更加豐富的功能。下面是代碼的實現過程:
一、服務端A實現
首先建立一個服務端A,創建一個maven工程simple-rpc-server,然後在simple-rpc-server下創建兩個子模塊simple-rpc-api、simple-rpc-provider。
simple-rpc-provider是爲服務端私有的,不會發布出去,他的作用是simple-rpc-api接口的具體實現。
- A、simple-rpc-api
simple-rpc-api是服務接口的提供,目的用於暴露接口,給客戶端使用,使用方式是把simple-rpc-api打包發佈到本地倉庫或者私服上,客戶端引用該座標進行接口調用。該模塊下pojo包就三個類,這三個類可以單獨提出來到一個maven工程中,然後引用座標。但是一切從簡,就寫到帶模塊中。
1、User這個類就是一個簡單的實體類,幾個屬性和get/set方法,並實現序列化接口
public class User implements Serializable {
private Integer uid;//uid
private String username;//用戶名
private String password;//密碼
private String name;//名稱
private Integer age;//年齡
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
2、RpcRequest這個類是最主要的,也是核心,作用是客戶端把要請求的api信息,包裝成RpcRequest對象,通過socket發送給服務端,然後服務端對對象進行解析,通過反射調用服務端的實現方法,在把執行的結果封裝通過socket返回給客戶端,這就完成了Rpc的遠程調用。也是這個類的作用所在。並實現序列化接口
public class RpcRequest implements Serializable {
//類的全路徑
private String className;
//方法的名稱
private String methodName;
//方法的參數
private Object[] params;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
}
3、RpcResponse這個類,爲服務端對象RcRequest解析執行,得到的結果包裝成RpcResponse對象返回給客戶端。
public class RpcResponse implements Serializable {
private Integer code;
private String message;
private Object result;
public RpcResponse(Integer code, String message, Object result) {
this.code = code;
this.message = message;
this.result = result;
}
public static RpcResponse success(Object result){
return new RpcResponse(200, "結果正確", result);
}
public static RpcResponse failed(String message){
return new RpcResponse(500, message, null);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
4、UserService這個接口就是simple-rpc-server這個服務需要提供的功能,有保存user類和根據uid獲取到user類
public interface UserService {
public void saveUser(User user);
public User getUserByUid(Integer uid);
}
- B、simple-rpc-provider
simple-rpc-provider是simple-rpc-api的實現,主要用於發佈服務,用於接收服務端提交過來的RcRequest參數,然後通過反射調用實現方法進行執行,得到返回結果,封裝並返回。該模塊要引用simple-rpc-api的引用,對他的接口進行實現。該過程對應的是上圖的的藍色箭頭。
1、UserServiceImpl爲simple-rpc-api接口UserService的實現
public class UserServiceImpl implements UserService {
//模擬數據庫功能,進行存取
private static Map<Integer, User> userMap = new HashMap();
static {
User user1 = new User();
user1.setUid(10);
user1.setName("zhangsan");
user1.setAge(32);
user1.setPassword("OOSDIAIDSI123213");
user1.setUsername("41122400");
userMap.put(user1.getUid(), user1);
User user2 = new User();
user2.setUid(12);
user2.setName("lisi");
user2.setAge(22);
user2.setPassword("OSVASDASDASC112");
user2.setUsername("41122400");
userMap.put(user2.getUid(), user2);
}
@Override
public void saveUser(User user) {
//保存操作
userMap.put(user.getUid(), user);
System.out.println("數據庫中的數據:");
for (User dbUser : userMap.values()) {
System.out.println(dbUser);
}
}
@Override
public User getUserByUid(Integer uid) {
return userMap.get(uid);
}
}
2、ProxtService爲服務端的代理層(參照模型圖),主要用於發佈服務,並處理來自客戶端的請求,並響應結果。底層是基於Socket實現的。
public class ProxyService {
//創建一個線程池
private static ExecutorService executorService = new ThreadPoolExecutor(5, 10, 3, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10));
public static void publish(int port, Object instance){
System.out.println("RPC服務器 已啓動, 端口爲:" + port);
try {
ServerSocket serverSocket = new ServerSocket(port);
while (true){
//socket爲客戶端的一個引用,到該方法會連接阻塞
Socket socket = serverSocket.accept();
//使用線程池來處理請求,避免阻塞
executorService.execute(new RpcHandler(socket, instance));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、RpcHandler是用來做真正的業務處理的
public class RpcHandler implements Runnable {
private Socket socket;
private Object instance;//暴露哪一個實例
public RpcHandler(Socket socket, Object instance) {
this.socket = socket;
this.instance = instance;
}
@Override
public void run() {
ObjectInputStream objectInputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
//獲取到客戶端的輸入,其實就是RpcRequest對象
objectInputStream = new ObjectInputStream(socket.getInputStream());
RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
//把rpcReuqest交給MethodHandler方法處理,通過反射調用執行,並得到返回結果
RpcResponse rpcResponse = MethodHandler.process(instance, rpcRequest);
//獲取到輸出流,把返回結果寫出到客戶端
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(rpcResponse);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
if(objectInputStream != null){
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(objectOutputStream != null){
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4、MethodHandler是用來進行對RpcRequest進行分析,通過反射調用對象的實現方法,本例中是UserServiceImpl類中的方法,並返回結果,對其進行包裝RpcRequest。
public class MethodHandler {
public static RpcResponse process(Object instance, RpcRequest rpcRequest){
//獲取到類的全路徑
String className = rpcRequest.getClassName();
//獲取參數的類信息,用於指定方法獲取
Object[] params = rpcRequest.getParams();
Class[] paramsType = null;
if(params != null || params.length != 0){
paramsType = new Class[params.length];
for (int i = 0; i < params.length; i++) {
paramsType[i] = params[i].getClass();
}
}
try {
Class<?> clazz = Class.forName(className);
Method method = clazz.getMethod(rpcRequest.getMethodName(), paramsType);
Object result = method.invoke(instance, params);
return RpcResponse.success(result);
} catch (ClassNotFoundException e) {
return RpcResponse.failed("類不存在");
} catch (NoSuchMethodException e) {
return RpcResponse.failed("方法不存在");
} catch (IllegalAccessException e) {
return RpcResponse.failed("訪問異常");
} catch (InvocationTargetException e) {
return RpcResponse.failed("調用方法失敗");
}
}
}
5、App類爲我們的啓動測試類,用於發佈服務:
public class AppService {
public static void main(String[] args) {
//要發佈服務端實例
UserService userService = new UserServiceImpl();
ProxyService.publish(8080, userService);
}
}
到這來我們的服務端,以及底層的原理基本以及完成了,當啓動的時候會在連接的時候一直阻塞,直到有客戶端連接服務端,服務端纔開始工作。
二、客戶端A實現
可以在另一臺電腦上或者同一個電腦上建立一個客戶端A,創建一個maven工程simple-rpc-client,然後在simple-rpc-server下創建一個子模塊simple-rpc-consumer。
- A、simple-rpc-consumer
simple-rpc-consumer服務主要是對simple-rpc-api裏面的接口進行代理,封裝RpcRequest對象通過socket傳遞給服務端,讓服務端解析對象並執行,在得到結果,返回給客戶端。
並且記得引入simple-rpc-api在倉庫中的引用位置。
1、ProxyService主要對接口進行代理
public class ProxyService {
private int port;
private String url;
public ProxyService(String url, int port) {
this.port = port;
this.url = url;
}
//根據傳遞的接口類型信息,進行代理
public Object proxy(Class<?> clazz){
//得到代理的對象
Object obj;
obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, new RpcHandler(url, port));
return obj;
}
}
2、RpcHandler爲具體執行方法,當代用接口的getUserById方法時,會執行該類的invoke方法,封裝對象傳遞給服務端執行,並返回結果。
public class RpcHandler implements InvocationHandler {
private String url;
private int port;
public RpcHandler(String url, int port) {
this.url = url;
this.port = port;
}
//爲UserService的代理對象,當帶哦用UserService的任何方法時,都會調用invoke這個方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//只需要封裝好RpcRequest對象,傳給服務端即可
RpcRequest rpcRequest = new RpcRequest();
rpcRequest.setClassName(method.getDeclaringClass().getName());
rpcRequest.setMethodName(method.getName());
rpcRequest.setParams(args);
//端口寫死,可以自己做修改
Socket socket = new Socket(url, port);
//獲取到輸出流,把rpcRequest對象傳到服務端,讓服務端進行解析
ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
os.writeObject(rpcRequest);
//獲取到輸入流,從服務器端獲取到執行的結果,並對其進行解析
ObjectInputStream is = new ObjectInputStream(socket.getInputStream());
RpcResponse rpcResponse = (RpcResponse) is.readObject();
if(rpcResponse.getCode() == 200){
return rpcResponse.getResult();
}
return null;
}
}
到這裏客戶端A就已經寫完。
並添加一個測試類:
public class AppClient {
public static void main(String[] args) {
//創建一個代理對象,對接口進行代理
ProxyService proxyService = new ProxyService("localhost", 8080);
//獲取代理對象,當調用方法的時候,其實是執行代理對象的invoke方法
UserService userService = (UserService) proxyService.proxy(UserService.class);
//獲取id爲10的用戶
User user = userService.getUserByUid(10);
System.out.println(user);
User user1 = new User();
user1.setUid(1001);
user1.setName("rpc Cient");
//執行保存操作
userService.saveUser(user1);
}
}
先啓動服務端,在啓動客戶端,會得到一下執行結果:
客服端:
服務端:
到這裏就完成了rpc的遠程調用。可以參照之前的圖在此對比一邊,只是一個簡單的rpc調用過程,只需要知道他大致是如何實現的,一些其他功能可自己進行擴展。