服务框架学习(二):远程方法调用(RPC)的原理与实现

远程方法调用

1. 概念

远程方法调用(Remote Procedure Call),顾名思义就是调用在其他地方已经实现的方法
用图示表示就是:
RPC基本模型
总结一下:消费者在访问对应方法时,把参数(类名,方法名,参数) 发送给生产者代理(可以用Socket),然后传递到生产者的具体实现方法中。生产者通过参数得到返回值后再用同样的方法传递给消费者的具体使用地方。

注册服务器

当然,大家在网上也看到这样的图(尤其在整合Zookeeper的博客里面
RPC有注册机演示
这张图和第一张图的区别,就是多了个注册服务器(比如Zookeeper),注册中心服务器用来维护生产者发布的服务,其实信息最核心的有ip和端口,这样,当消费者拿着ip和端口来访问时,注册中心就找到他所需求的对应ip和端口。从生产者获取返回值后再返还给消费者。

RPC的意义

RPC解决了那些问题:

  1. 进程间通讯问题(异步通信)
  2. 面向服务的封装,提供和本地方法调用一样的调用机制
  3. 程序员无须对远程调用的细节实现。

注册机在RPC中不是必须的,不过有了注册机后有几个好处:

  1. 使整个系统分工更加清晰,
  2. 解耦,生产者就不用再成为服务器(在图1的模型中生产者还需要成为服务器)而可以和消费者一样作客户端。
  3. 若获取到多个IP,能依据负载策略去调用远程方法

2. Java简单实现

既然我们能看懂RPC,那么我们尝试手写一个。

  1. 服务接口
    消费者和生产者都要有一个
    HelloService.Java
    public interface HelloService {
       public String say(String msg);
    }
    
  2. 实现类
    HelloServiceImpl.java
    public class HelloServiceImpl implements HelloService{
    	public String say(String msg) {
    		return "saying: " + msg;
    	}
    }
    
    实现类也是我们要注册的服务,到时候消费者发来请求,就把请求放到实现类的具体实现方法中去,然后把返回值响应给消费者。
  3. 消费者代理
    消费者代理的工作是接受请求的方法和参数,并发送给生产者
    ClientMain.java:
    static String address = "localhost";
    static int port = 20880;
    /**
     * 远程方法调用,得到
     * @param clazz 传入的接口
     * @return 调用的方法的返回值
     */
    public static Object getObject(Class clazz)
    {
    	return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
    		/**
    		 * 方法代理
    		 * @method: 代理的方法
    		 * @args: 参数
    		 */
    		@Override
    		public Object invoke(Object proxy, Method method, Object[] args)
    				throws Throwable {
    			Socket socket = new Socket(address, port);
    			System.out.println("!!!获取对象: ");
    			String className = clazz.getName(); // 类名
    			String methodName = method.getName(); // 方法名
    			Class[] types = method.getParameterTypes(); // 参数类型
    			System.out.println("class: " + className);
    			System.out.println("method: " + methodName);
    			for (int i=0; i<args.length; i++)
    			{
    				System.out.println("param: " + types[i] + " = " + args[i]);
    			}
    			ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
    			oos.writeUTF(className);
    			oos.writeUTF(methodName);
    			oos.writeObject(types);
    			oos.writeObject(args);
    			// 在输出流中添加类名,方法名,传入参数类型,和具体参数(Object类型)
    			oos.flush();
    			
    			ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
    			Object ret = ois.readObject();
    			// 从输入流中得到返回值
    			oos.close();
    			ois.close();
    			socket.close();
    			return ret;
    		}
    	});
    }
    
  4. 生产者
    生产者需要接收参数并从具体实现方法中得到返回值
    Register.java:
    /**
     * 发布服务
     * @param serviceImpl 具体实现类
     * @param port 端口
     */
    public void publish(Class serviceImpl, int port)
    {
    	System.out.println("register: " + serviceImpl + " on " + "localhost" + ":" + port);
    	try {
    		ServerSocket ss = new ServerSocket(port);
    		// 建立一个服务监听port端口
    		while (true)
    		{
    			Socket socket = ss.accept(); // 等待连接,若没有连接线程会进入等待
    			System.out.println("~~~得到请求: ");
    			ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
    			
    			String className = ois.readUTF();
    			String methodName = ois.readUTF();
    			Class[] types = (Class[]) ois.readObject();
    			Object[] methodArgs = (Object[]) ois.readObject();
    			// 读取得到相应参数
    			System.out.println("className: " + className);
    			System.out.println("methodName: " + methodName);
    			for (int i=0; i<methodArgs.length; i++)
    			{
    				System.out.println("param: " + types[i] + " = " + methodArgs[i]);
    			}
    			Class clazz = serviceImpl;
    			// 得到具体方法
    			Method method = clazz.getMethod(methodName, types);
    			System.out.println("method: " + method.getName());
    			// 通过输入参数得到具体方法的返回值
    			Object invoke = method.invoke(clazz.newInstance(), methodArgs);
    			System.out.println("invoke: " + invoke);
    			ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
    			oos.writeObject(invoke);
    			// 将结果通过输出流传递到消费者
    			oos.flush();
    			
    			ois.close();
    			oos.close();
    			socket.close();
    		}
    	} catch (Exception e) {
    		// TODO Auto-generated catch block
    		e.printStackTrace();
    	}
    }
    
  5. 在本地发布一个服务
    RegisterMain.java:
    public static void main(String[] args) {
    	// 发布服务的类
    	Register register = new Register();
    	// 发布服务到指定端口
    	register.publish(HelloServiceImpl.class, 20880);
    }
    
  6. 消费指定服务
    ClientMain.java
    public static void main(String[] args) {
    	HelloService helloService = (HelloService) getObject(HelloService.class);
    	System.out.println("---得到了对象,准备实现---");
    	System.out.println("result: " + helloService.say("Hello Drake"));
    }
    

所有的类都写好了,我们运行试试看

3. 测试

生产者:

register: class rpc.HelloServiceImpl on localhost:20880
~~~得到请求: 
className: rpc.HelloService
methodName: say
param: class java.lang.String = Hello Drake
method: say
invoke: saying: Hello Drake

消费者:

---得到了对象,准备实现---
!!!获取对象: 
class: rpc.HelloService
method: say
param: class java.lang.String = Hello Drake
result: saying: Hello Drake

由控制台输出可得,服务成功的部署了,且能消费。
我还将继续更新更多的中间件服务框架,希望大家喜欢 ?
源代码:https://github.com/272437543/FrameWorkStudy
喜欢的star一下哟

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值