1.手写简单的RPC
目录
什么是RPC
RPC是⼀种协议:是⼀种远程过程调⽤(remote procudure call)协议 rpc协议是在应⽤层之上的协议,规定了通信双⽅进⾏通信的数据格式是什么样的,及数据如何传输: 指明调⽤的类或接口 指明调⽤的⽅法及参数
手写RPC
模拟远程调用过程,通过这个过程帮助了解Dubbo的原理
创建功能接口、通用类项目
功能/目的
- 把功能接口、通用类提取,方便生产者和消费者使用
接口
package myinterface;
/**
* 定义一个功能接口
*/
public interface HelloWord {
String helloWord(String parameter);
}
RPC传参对象
package myInvocation;
import java.io.Serializable;
/**
* 生产者和消费者所用的传参对象
*/
public class MyInvocation implements Serializable {
//请求的接口名称
private String interfaceName;
//请求的方法名称
private String methodName;
//请求的参数类型-根据这个匹配具体实现方法
private Class[] paramTypes;
//请求的具体参数
private Object[] params;
public MyInvocation(String interfaceName, String methodName, Class[] paramTypes, Object[] params) {
this.interfaceName = interfaceName;
this.methodName = methodName;
this.paramTypes = paramTypes;
this.params = params;
}
public MyInvocation() {
}
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class[] getParamTypes() {
return paramTypes;
}
public void setParamTypes(Class[] paramTypes) {
this.paramTypes = paramTypes;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
}
Util
ImplRegisterUtil.java
package util;
import java.util.HashMap;
import java.util.Map;
/**
* 服务实现类注册工具
*/
public class ImplRegisterUtil {
//接口名与实现类映射关系存储在内存-可用NoSql等其他介质
private static Map<String, Class> map = new HashMap();
/**
* 传入接口名和实现类的Class对象,用于根据接口名获得实现类
* @param interfaceName 接口名
* @param implClass 实现类Class对象
*/
public static void regist(String interfaceName, Class implClass) {
//注册到服务端内存中
map.put(interfaceName, implClass);
}
public static Class get(String interfaceName) {
return map.get(interfaceName);
}
}
ServiceRegisterUtil.java
package util;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 服务写入(注册)工具
*/
public class ServiceRegisterUtil {
private static Map<String, List<ServiceUrl>> SERVICE_REGISTER = new HashMap<String, List<ServiceUrl>>();
/**
* 将服务写入(注册)到txt文件中
* @param interfaceName
* @param url
*/
public static void regist(String interfaceName, ServiceUrl url){
List<ServiceUrl> list = SERVICE_REGISTER.get(interfaceName);
if (list == null) {
list = new ArrayList();
}
list.add(url);
SERVICE_REGISTER.put(interfaceName, list);
//将服务写入写入文件
saveService();
}
public static List<ServiceUrl> get(String interfaceName) {
SERVICE_REGISTER = getService();
List<ServiceUrl> list = SERVICE_REGISTER.get(interfaceName);
return list;
}
/**
* 写入(注册)地址
*/
private static void saveService() {
try {
//写入到文件
File file =new File("D:\\study\\MyStudy\\dubbo\\register.txt");
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(SERVICE_REGISTER);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获得已经写入(注册)的地址
* @return
*/
private static Map<String, List<ServiceUrl>> getService() {
try {
File file =new File("D:\\study\\MyStudy\\dubbo\\register.txt");
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
return (Map<String, List<ServiceUrl>>) objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
ServiceUrl.java
package util;
import java.io.Serializable;
/**
* 服务地址
*/
public class ServiceUrl implements Serializable {
private String hostName;
private int port;
public ServiceUrl(String hostName, int port) {
this.hostName = hostName;
this.port = port;
}
public ServiceUrl() {
}
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
完成后通过maven install 打包进仓库
创建服务提供项目(生产者)
功能/目的
- 实现具体功能
- 启动时将服务注册到注册中心
POM
<!--引入上面创建的接口和工具类jar(命名有点偏差,最好应该是common***,且工具和接口分两个jar)-->
<dependency>
<groupId>org.example</groupId>
<artifactId>rpc-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
启动类
添加@ServletComponentScan
@SpringBootApplication
@ServletComponentScan //开启servlet扫描
public class RpcApplication {
public static void main(String[] args) {
SpringApplication.run(RpcApplication.class, args);
}
}
接口实现方法
package com.example.rpc.impl;
import myinterface.HelloWord;
/**
* 对接口的实现
*/
public class HelloWordImpl implements HelloWord {
@Override
public String helloWord(String s) {
return "HelloWordImpl:"+s;
}
}
启动服务注册Bean
package com.example.rpc.config;
import com.example.rpc.impl.HelloWordImpl;
import myinterface.HelloWord;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import util.ImplRegisterUtil;
import util.ServiceRegisterUtil;
import util.ServiceUrl;
/**
* 注册服务到本地的一个txt文件中(dubbo就是把服务注册到zk或redis或nacos)
*/
@Configuration
public class RegistService {
@Bean
void regist(){
//配置自己的ip和端口
ServiceUrl url = new ServiceUrl("localhost",8080);
//将自己的ip和端口注册到.txt文件
ServiceRegisterUtil.regist(HelloWord.class.getName(), url);
//指明服务的实现类
ImplRegisterUtil.regist(HelloWord.class.getName(), HelloWordImpl.class);
}
}
RPC调用时执行的方法
package com.example.rpc.common;
import myInvocation.MyInvocation;
import org.apache.commons.io.IOUtils;
import util.ImplRegisterUtil;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Method;
/**
* 远程调用具体执行方法
*/
@WebServlet(name = "myServlet", urlPatterns = "/myServlet/*") //标记为servlet,以便启动器扫描。
public class MyServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
ObjectInputStream obj = new ObjectInputStream(req.getInputStream());
//接到消费端传入的请求参数
MyInvocation invocation = (MyInvocation)obj.readObject();
//获得要请求的接口名
String interfaceName = invocation.getInterfaceName();
//根据接口名从内存中获得生产者启动时注册的实现类Class对象
Class implClass = ImplRegisterUtil.get(interfaceName);
//根据消费端传入方法名和参数类型,通过反射获得对应的方法
Method method = implClass.getMethod(invocation.getMethodName(), invocation.getParamTypes());
//传入消费端请求参数,返回结果
String result = (String) method.invoke(implClass.newInstance(), invocation.getParams());
//结果返回
IOUtils.write(result.getBytes(), resp.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建服务调用项目(消费者)
功能/目的
- 通过RPC调用上面创建的项目
POM
<!--引入上面创建的接口和工具类jar(命名有点偏差,最好应该是common***,且工具和接口分两个jar)-->
<dependency>
<groupId>org.example</groupId>
<artifactId>rpc-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
负载均衡类
注:只为模拟RPC调用,实际这个应该也抽取出来
package com.example.demo;
import util.ServiceUrl;
import java.util.List;
import java.util.Random;
/**
* 负载均衡
*/
public class LoadBalance {
public static ServiceUrl random(List<ServiceUrl> list) {
Random random =new Random();
int n = random.nextInt(list.size());
return list.get(n);
}
}
请求代理类
职责:负责发送RPC请求
package com.example.demo;
import myInvocation.MyInvocation;
import org.aopalliance.intercept.Invocation;
import org.apache.commons.io.IOUtils;
import org.apache.tomcat.util.net.openssl.ciphers.Protocol;
import util.ServiceRegisterUtil;
import util.ServiceUrl;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
/**
* 代理
* @param <T>
*/
public class ProxyFactory<T> {
@SuppressWarnings("unchecked")
public static <T> T getProxy(final Class interfaceClassName) {
return (T)Proxy.newProxyInstance(interfaceClassName.getClassLoader(), new Class[]{interfaceClassName}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//组装RPC调用的参数 接口名 接口方法 入参类型 具体参数
MyInvocation invocation = new MyInvocation(interfaceClassName.getName(), method.getName(), method.getParameterTypes(), args);
//去文件中找到有哪些服务提供者(注册中心)
List<ServiceUrl> urlList = ServiceRegisterUtil.get(interfaceClassName.getName());
//如果文件中记录了多个生产者,进行负载均衡选择
ServiceUrl serviceUrl = LoadBalance.random(urlList);
try {
//请求生产者。封装http调用过程,此处通讯协议可用其他
URL url = new URL("http", serviceUrl.getHostName(), serviceUrl.getPort(), "/myServlet");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
OutputStream os = connection.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(invocation);
oos.flush();
oos.close();
//获得结果
InputStream inputStream = connection.getInputStream();
String result = IOUtils.toString(inputStream);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return "-1";
}
});
}
}
发起调用
@Test
void contextLoads() {
//获得代理对象
HelloWord obj = ProxyFactory.getProxy(HelloWord.class);
//发送请求,实际执行的是代理对象方法
String rb = obj.helloWord("RB");
System.out.println(rb);
}
完整启动流程
- install 接口、通用类项目,生成jar
- 启动服务提供者
- 将服务注册到txt文件中
- 调用服务消费者
此RPC执行流程
-
消费者
调用 obj.helloWord("RB") 方法
↓
进入ProxyFactory.getProxy方法中
↓
组装RPC请求参数
↓
进行获得已注册的服务列表
↓
进行负载均衡
↓
发送请求
-
生产者
进入自定义的Servlet
↓
获得请求参数
↓
获得对应实现类Class对象
↓
使用反射调用实际业务方法
↓
返回结果