RPC是什么
RPC(Remote Procedure Call)—远程过程调用
区分于本地过程调用,一个是自己来做,一个是通知别人去做。
RPC框架主要解决两个问题
1.分布式架构下的服务调用
2.使服务调用无需关注细节
用白话来说就是让我们调用远程方法像调用本地方法一样方便,不用过多的去关注实现细节。
前言
本博客仅涉及RPC的基本原理与简单实现,实际的RPC框架会涉及到网络、注册中心、负载均衡等等方面,
然后也是为了从基础开始学习,从手写简单RPC开始。
RPC代码实现
涉及模块
1.api 对外暴露的接口,限制了使用规范
2.provider 接口的提供者(实现)
3.consumer 接口的消费者(使用)
api模块
1.对外的接口及传输的实体类
/**
* @author :cjd
* @description: 对外接口
* @create 2019-09-23 15:43
**/
public interface IUserService {
UserDTO findUser(UserDTO user);
}
/**
* @author :cjd
* @description: 传输实体类
* @create 2019-09-23 15:44
**/
@Data
public class UserDTO implements Serializable {
private static final long serialVersionUID = 5944175587666080727L;
private String name;
private int age;
private int status;
}
2.定义Rpc的传输规则实体类RpcDTO
/**
* @author :cjd
* @description: Rpc传输实体
* @create 2019-09-23 15:59
**/
@Data
public class RpcDTO implements Serializable {
private static final long serialVersionUID = -4046604323140976013L;
//完整的实现类路径
private String fullPathName;
//调用的方法
private String methodName;
//方法需要的属性
private Object[] params;
//属性的类型,可从Method获取
private Class[] paramTypes;
}
注意要点:
1.引入lombok的@Data注解优化了代码量
2.网络传输实体类需实现Serializable接口并赋值serialVersionUID
3.完成项目后通过maven install 放到Maven库中后才可被别的模块使用pom引用
provider模块
pom引入api依赖
<dependencies>
<dependency>
<groupId>com.cjdjyf.rpc</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
1.对外接口实现类
/**
* @author :cjd
* @description: 接口实现类
* @create 2019-09-23 15:49
**/
public class UserImpl implements IUserService {
public UserDTO findUser(UserDTO userDTO) {
//业务逻辑操作
userDTO.setStatus(200);
return userDTO;
}
}
- BIO+多线程的入口类
/**
* @author :cjd
* @description: 入口
* @create 2019-09-23 15:51
**/
public class Provider {
public static void main(String[] args) throws IOException {
publish(8888);
}
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
public static void publish(int port) throws IOException {
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
Socket socket = serverSocket.accept();
fixedThreadPool.execute(new MyThread(socket));
}
}
}
- 线程池运行线程通过反射进行方法调用与返回
1)读取Socket流中的对象,获取实现类的全路径、方法名、方法参数、方法类型进而调用对应方法
2)方法执行后获得结果放到输出流返回
/**
* @author :cjd
* @description: 任务分发
* @create 2019-09-23 15:55
**/
public class MyThread implements Runnable {
private Socket socket;
public MyThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
//1.读取Socket流中的对象,获取实现类的全路径、方法名、方法参数、方法类型进而调用对应方法
RpcDTO rpcDTO = (RpcDTO) objectInputStream.readObject();
String fullPathName = rpcDTO.getFullPathName();
String methodName = rpcDTO.getMethodName();
Object[] params = rpcDTO.getParams();
Class[] paramsType = rpcDTO.getParamTypes();
Class clazz = Class.forName(fullPathName);
Method declaredMethod = clazz.getDeclaredMethod(methodName, paramsType);
//2.方法执行后获得结果放到输出流返回
Object result = declaredMethod.invoke(clazz.newInstance(), params);
objectOutputStream.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意要点:
1.通过自旋Socker接收连接后使用了线程池+BIO来进行任务处理
2.通过反射进行对应方法的调用,所以要获取1.实现类的全路径 2.执行的方法名 3.方法的属性值 4.方法的属性类,即传输规范中的RpcDTO。
consumer模块
pom引入api依赖
<dependencies>
<dependency>
<groupId>com.cjdjyf.rpc</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
1.通过jdk代理隐藏网络连接逻辑
/**
* @author :cjd
* @description: jdk动态代理
* @create 2019-09-23 16:20
**/
public class MyInvocationHandler<T> implements InvocationHandler {
@Override
public T invoke(Object proxy, Method method, Object[] args) throws Throwable {
T t = null;
Socket socket = new Socket("127.0.0.1", 8888);
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
RpcDTO rpcDTO = new RpcDTO();
rpcDTO.setFullPathName("impl.UserImpl");
rpcDTO.setMethodName(method.getName());
rpcDTO.setParams(args);
rpcDTO.setParamTypes(method.getParameterTypes());
objectOutputStream.writeObject(rpcDTO);
objectOutputStream.flush();
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
t = (T) objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return t;
}
}
2.方法调用
/**
* @author :cjd
* @description: 方法消费者
* @create 2019-09-23 16:02
**/
public class RpcConsume {
public static void main(String[] args) {
IUserService userService = (IUserService) Proxy.newProxyInstance(RpcConsume.class.getClassLoader(), new Class[]{IUserService.class}, new MyInvocationHandler<IUserService>());
UserDTO user = new UserDTO();
UserDTO result = userService.findUser(user);
System.out.println(result.getStatus());
}
}
注意要点:
1.通过动态代理技术隐藏Socket相关代码,使程序员不必关注网络上的实现。
2.使用泛型减少代码中的类判断逻辑。
3.可使用Spring IOC等技术进一步优化。
4.代码中我写死了fullPathName,这里其实可以通过各类途径来获取类的全路径名
举几个例子好了
1)用Map进行对应储存
2)提供一个值后将逻辑放到提供者去处理
3)约束接口的同时通过注解或者值的形式直接对外暴露全路径名
个人认为提供接口类名,然后在提供服务处进行判断获取实现类的全名即可。