Java中RPC的简单实现即RMI、微服务

RMI(Remote Method Invocation):远程方法调用
RPC(Remote Procedure Call):远程过程调用

其中的核心思想或步骤是:通过网络通信,一个网络端调用另一个网络端的方法(过程),实现相对应的功能,得到相应的结果。RMI即JAVA中RPC的实现。“invocation”意为“反射”,也就是说明利用了java中的反射机制实现的。


从应用角度来分析,客户端通过一定方式调用代理对象的方法,得到了方法执行的结果。而内部是在经过代理机制拦截到相关方法之后,其参数和相关方法信息被发送至RMI服务器,服务器端执行了这个方法。然后再将执行结果发送至客户端。客户端再将返回值返回。即为同步调用。

目录:

实现想法与原理说明

——客户端

——服务器端

核心实现方式

——方法工厂

——注册过程

源代码链接

 

本文是针对于想法进行简单实现,如有意见或争议可在评论区向作者提出。

原创文章,转载复制请注明出处。

 

 

 


实现想法与原理说明:

——针对RPC客户端,设计一个代理模式。表面上用户是通过得到的代理对象直接执行了相关方法,实际上通过代理对象拦截到相关方法时,将执行方法所需要的相关信息(方法及其参数信息)通过网络通信发送给RPC服务器端(连接上服务器),而在RPC服务器端根据客户端所发来的相关信息真正执行这个方法,再将返回值再发送给客户端,RPC服务完成,断开与服务器之间的连接(面向服务的短链接)。

以实现功能为主,本次客户端设计的代理模式使用了JDK代理模式。所以支持RPC模式的方法,将是定义在接口中定义的方法。

客户端:

//RpcClient类
public <T> T getProxy(Class<?> klass) throws Exception;

而在代理对象拦截到方法时,我们在新产生的InvocationHandler对象覆盖的invoke()方法中,连接Rpc服务器,将方法、以及参数发送至Rpc服务器端并等待他返回的结果。考虑到代码的解耦,这个过程我们封装在类RpcClientExecutor中。

public class RpcClientExecutor {
	private String rpcServerIp;//rpc服务器的信息
	private int rpcServerPort;
	private Gson gson;//网络信息均使用Gson格式的字符串进行发送
	private Class<?> returnType;//方法的返回值,用于得到结果的Gson转换

    //相关set方法与get方法

    <T> T rpcExecutor(String rpcBeanId, Object[] para) throws Exception();
    //在此方法中实现与Rpc服务器的通讯过程
    //para为代理对象拦截到的参数数组
    //rpcBeanId
    //方法返回值为Gson转换回来的从服务器得到的结果

至于rpcBeanId代表什么意思,在叙述完大框架再详细说明。

服务器端:

——针对RPC服务器端,在启动服务器后,实时侦听客户端的链接请求。每次接收到一个请求时,为此客户端派发一个RPC服务线程与其交互。也就是说,服务器端的侦听线程是持续运行的,客户端的连接请求是并发的。

//服务器派发的服务线程
public class RpcServerExecutor implements Runnable {
	private Socket socket;//对端
	private DataInputStream dis;//通信信道
	private DataOutputStream dos;
	private RpcServer rpcServer;//Rpc服务器对象
	private Gson gson;

        //相关带参构造等

        public void run() {
        //接收RPC客户端传递的rpcBeanId和参数;
        // 定位相关类、对象和方法;
        // 执行RPC客户端要求执行的方法;
        // 向RPC客户端返回执行结果。
        // 服务完成,关闭相关通信信道
    }
//RpcServer即Rpc服务器
public class RpcServer implements Runnable {
	private ServerSocket server;
	private int port;
	private volatile boolean goon;
	private final RpcBeanFactory rpcBeanFactory;//Rpc方法工厂
	private static long executorId;//区分派发服务线程的ID

        //相关方法
        @Override
	public void run() {
		while (goon) {
			try {
				Socket rpcClient = server.accept();
				new RpcServerExecutor(this, rpcClient, ++executorId);
			} catch (Exception e) {
				goon = false;
			}
		}
		stopRpcServer();
	}

核心实现方式及其原理:

——既然在服务器端是通过反射机制去调用的,那么最最容易想到的方法就是:

客户端将方法名、所存在的接口、参数数组发送给服务器端;

服务器端先从接口定位到其实现类,再使用实现类对象和相关参数执行相关方法;

再将方法执行的结果回送给客户端。

这样确实能实现,但实属笨重,并且不太容易实现。仔细考虑,既然我们Rpc服务器端能执行代理拦截到的方法,那就一定拥有这个方法所在的接口的实现类。我们的RPC是基于网络通信方式进行运作的。对于每次信息的发送,都是要消耗“奢侈的”网络资源的。

所以我们要使用珍贵的网络资源,尽量发送尽可能少的数据,完成相对应的功能。

所以我们给出RpcBeanDefnation与RpcBeanFactory。

方法工厂

public class RpcBeanDefination {
	private Class<?> klass;//实现接口
	private Method method;//
	private Object object;//实现类对象
	
	RpcBeanDefination() {
	}

	//相关get与set方法

}
public class RpcBeanFactory {
	private final Map<String, RpcBeanDefination> rpcBeanMap;
	//其中Map中的键String存放了接口中每个方法的唯一识别码
	//识别码是每个相对应Method对象HashCode的字符串,即method.toString().hashCode();

	RpcBeanFactory() {
		rpcBeanMap = new HashMap<>();
	}
	
	void rpcBeanRegistry(String rpcBeanId, RpcBeanDefination rpcBeanDefination) {
		RpcBeanDefination rbd = rpcBeanMap.get(rpcBeanId);
		if (rbd != null) {
			return;
		}
		rpcBeanMap.put(rpcBeanId, rpcBeanDefination);
	}
	
	RpcBeanDefination getRpcBean(String rpcBeanId) {
		return rpcBeanMap.get(rpcBeanId);
	}
	
}

我们在RpcServer启动时,现在其RpcBeanFactory中对要进行Rpc服务的接口及其实现类进行注册(rpcBeanRegistry方法);

注册过程

其中注册过程:

得到接口中的每个方法,计算出他的识别码;

再通过实现类得到执行对象,构建出其RpcBeanDefination 对象;

以识别码为键,以其RpcBeanDefination 对象为值放入RpcBeanFactory中。

method.toString()包含方法所存在的包、类、名字、参数个数。所以不会存在重复的可能。但这也要求服务器端与客户端的接口存放的包路径必须一致。

 

我们把这个注册过程封装在RpcBeanRegistry类中。

/**
 * Rpc方法注册到RpcBeanFactory中的具体过程,以及多种注册方式的重载。
 */
public class RpcBeanRegistry {
	RpcBeanRegistry() {
	}
	
	static void registInterface(RpcBeanFactory rpcBeanFactory, Class<?> interfaces) {
		doRegist(rpcBeanFactory, interfaces, null);
	}
        //具体的注册过程
	private static void doRegist(RpcBeanFactory rpcBeanFactory, Class<?> interfaces, Object object) {
		Method[] methods = interfaces.getDeclaredMethods();
		for (Method method : methods) {
			String rpcBeanId = String.valueOf(method.toString().hashCode());
			//识别码的计算
			RpcBeanDefination rpcBeanDefination = new RpcBeanDefination();
			rpcBeanDefination.setKlass(interfaces);
			rpcBeanDefination.setMethod(method);
			rpcBeanDefination.setObject(object);
			
			rpcBeanFactory.rpcBeanRegistry(rpcBeanId, rpcBeanDefination);
		}
	}
	
	static void registInterface(RpcBeanFactory rpcBeanFactory, Class<?> interfaces, Object object) {
		if (!interfaces.isAssignableFrom(object.getClass())) {
			return;
		}
		doRegist(rpcBeanFactory, interfaces, object);
	}
	
	static void registInterface(RpcBeanFactory rpcBeanFactory, Class<?> interfaces, Class<?> klass) {
		if (!interfaces.isAssignableFrom(klass)) {
			return;
		}
		try {
			doRegist(rpcBeanFactory, interfaces, klass.newInstance());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}

也就是说,在代理对象拦截到方法及其参数时,内部将其方法的RpcBeanId计算出来,然后连接Rpc服务器,将rpcBeanId与参数发送过去,等待服务器端返回结果;

服务器端在接收到Rpc请求消息时,通过识别码从Factory中取出RpcBeanDefnation,从请求中得到参数信息并进行转换,反射执行,再将结果发送至客户端,服务结束。

源码链接:https://gitee.com/MEC_student_Conph/RPC_FrameWprkv0.0.1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值