浅论java中的rpc框架

本文探讨了Java中的RPC框架,特别是基于RMI的实现。分析了服务端和客户端的基本实现思路,包括服务端如何暴露对象,客户端如何通过对象ID找到并调用服务。文章还提出了几个关键点,如对象序列化、代理机制以及客户端和服务端的交互。最后,作者列举了一些关于RPC实现的疑问,如对象查找、GC引用计数和传输速度优化,同时提到了其他RPC框架如gRPC和Dubbo。
摘要由CSDN通过智能技术生成

时间记录:2019-7-24
在分布式系统中有很多的子系统,而子系统之间进行数据的交互会通过远程过程调用**(RPC)**来进行数据的交互的,那么在远程过程调用中需要注意到什么呢?其实最主要的就是传输的速度和使用的速度。我们传输的数据的过程和拿到数据进行使用的过程。既然是远程调用那么离不开远程的对象,那我们的客户机是怎么知道远程的对象呢?远程的对象怎么进行调用呢?客户机怎么知道这个远程对象在内存中是什么样子呢?如何使用这个远程对象呢?
以下基于java的RMI来描述RPC的实现【个人对rpc的设计有很多疑问,未得到验证很难受】

基本实现思路

首先rmi分为服务端和客户端,也就是服务端提供远程对象,而客户端根据远程对象的一个ID来获取到这个对象的数据,然后在客户端进行执行。客户端进行调用的请求,服务端将执行的结果返回给客户端。这里的过程是直接通过某类进行执行的,比reset方式跟简单,且对数据的转换跟方便。这里我们主要注意到几点。
1:客户端和服务端都存在实际的类(即需要被调用的)
2:服务端注册了对应的服务且被暴露出来
3:客户端对服务的访问正确,且服务端存在实例
4:客户端和服务端的代理,即进行序列化数据传输的代理
5:数据的格式【对象序列化?全部都是如此?】

先看简单例子然后具体分析代码如何实现的
服务端

package com.huo.rmi;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiServer {
   
	public static void main(String[] args) {
   
		try {
   
			HelloInterface helloInterface = new HelloInterfaceImpl();
			LocateRegistry.createRegistry(1099);    
			Registry registry = LocateRegistry.getRegistry();
			registry.bind("hello", helloInterface);
		} catch (Exception e) {
   
			e.printStackTrace();
		}
	}
}

客户端

package com.huo.rmi;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RmiClient {
   
	public static void main(String[] args) {
   
		try {
    
			Registry registry = LocateRegistry.getRegistry("localhost");        
			HelloInterface remoteMath = (HelloInterface)registry.lookup("hello");
			remoteMath.sayHello();
		}catch(Exception e) {
   
			e.printStackTrace();
		}			
	}
}

实际的Remote

package com.huo.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface HelloInterface extends Remote {
   
	public void sayHello() throws RemoteException;;
}
package com.huo.rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class HelloInterfaceImpl extends UnicastRemoteObject implements HelloInterface{
   
	
	protected HelloInterfaceImpl() throws RemoteException {
   
		super();
		// TODO Auto-generated constructor stub
	}

	@Override
	public void sayHello() throws RemoteException{
   
		// TODO Auto-generated method stub
		System.out.println("Hello world");
	}
}

执行结果显示

服务端:
Hello world
客户端:
Hello world toll

由这里可以知道,具体的方法是在服务端进行执行的,然后将return的数据返回到客户端
注:在进行远程调用的时候会判断Method的返回类型是什么,如果是Void类型那么不进行通信【这里
这种方式我们是不是可以做成通知类的东西?】
具体的执行过程分析

我们知道在服务端保存了一个具体的对象,那么这个对象是怎么存储在服务端的呢【如何去发现这个对象】,我们之前提到了Remote的接口,这里会通过这个接口对实例进行一个代理,不然如果需要处理所有的类多么痛苦。对象存储在服务端了之后,那么客户端如何发现这个呢?这里就有另外一个关键的点,对象给予一个ID,客户端在使用这个ID发现就可以。那么发现了这个对象后是怎么调用具体的方法的呢?这是就需要知道Method和具体的对象,然后进行调用,将具体的结果返回给客户端。我们说到服务端存在一个代理对象,同样的客户端也存在有个代理将具体的执行数据返回。这样就感觉是客户端在进行执行,让人方便理解。

具体的代码分析
我们的HelloInterfaceImpl是继承自UnicastRemoteObject,在这里就已经将服务进行暴露出来了,我们看具体的代码。在HelloInterfaceImpl的构造函数中会调用父类的构造函数,在父类的构造函数中会调用**exportObject((Remote) this, port)**的一个方法

    protected UnicastRemoteObject(int port) throws RemoteException
    {
   
        this.port = port;
        exportObject((Remote) this, port); //这里就是将具体的Remote暴露出来的
    }
    |到下面的方法
    public static Remote exportObject(Remote obj, int port)
        throws RemoteException
    {
   
        return exportObject(obj, new UnicastServerRef(port));
    }
    |到下面
    private static Remote exportObject(Remote obj, UnicastServerRef sref)
        throws RemoteException
    {
   
        // if obj extends UnicastRemoteObject, set its ref.
        if (obj instanceof UnicastRemoteObject) {
   
            ((UnicastRemoteObject) obj).ref = sref;
        }
        return sref.exportObject(obj, null, false);
    }
    |到下面
    public Remote exportObject(Remote impl, Object data,
                               boolean permanent)
        throws RemoteException
    {
   
        Class<?> implClass = impl.getClass();
        Remote stub;

        try {
   
            stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
        } catch (IllegalArgumentException e) {
   
            throw new ExportException(
                "remote object implements illegal remote interface", e);
        }
        if (stub instanceof RemoteStub) {
   
            setSkeleton(impl);
        }
        //注意看这里,将remote保存在Target中
        Target target =
            new Target(impl, this, stub, ref.getObjID(), permanent);
        ref.exportObject(target);//暴露
        hashToMethod_Map = hashToMethod_Maps.get(implClass);//这里将方法保存成Method对象,后面在收到客户端的方法名然后进行查找,返回Method对象
        return stub;
    }
    |到下面
    public void exportObject(Target target) throws RemoteException {
   
        ep.exportObject(target);
    }
    |到下面
    public void exportObject(Target target) throws RemoteException {
   
        /*
         * Ensure that a server socket is listening, and count this
         * export while synchronized to prevent the server socket from
         * being closed due to concurrent unexports.
         */
        synchronized (this) {
   
            listen(); //这里的listener就是将这个对象服务暴露出去
            exportCount++;
        }

        /*
         * Try to add the Target to the exported object table; keep
         * counting this export (to keep server socket open) only if
         * that succeeds.
         */
        boolean ok = false;
        try {
   
           //这里是将target保留在服务端的对象表中,供查询找到具体的对象
            super.exportObject(target);
            ok = true;
        } finally {
   
            if (!ok) {
   
                synchronized (this) {
   
                    decrementExportCount();
                }
            }
        }
    }
    |到下面 看监听部分
    private void listen() throws RemoteException {
   
        assert Thread.holdsLock(this);
        TCPEndpoint ep = getEndpoint();
        int port = ep.getPort();

        if (server == null) {
   
            if (tcpLog.isLoggable(Log.BRIEF)) {
   
                tcpLog.log(Log.BRIEF,
                    "(port " + port + ") create server socket");
            }

            try {
   
                server = ep.newServerSocket();
                /*
                 * Don't retry ServerSocket if creation fails since
                 * "port in use" will cause export to hang if an
                 * RMIFailureHandler is not installed.
                 */
                //给定一个Runnable【AcceptLoop】,执行任务
                Thread t = AccessController.doPrivileged(
                    new NewThreadAction(new AcceptLoop(server),
                                        "TCP Accept-" + port, true));
                t.start();
            } catch (java.net.BindException e) {
   
                throw new ExportException("Port already in use: " + port, e);
            } catch (IOException e) {
   
                throw new ExportException("Listen failed on port: " + port, e);
            }

        } else {
   
            // otherwise verify security access to existing server socket
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
   
                sm.checkListen(port);
            }
        }
    }
    |到下面,看具体的任务
    public void run() {
   
            try {
   
                executeAcceptLoop();
            } finally {
   
                try {
   
                    /*
                     * Only one accept loop is started per server
                     * socket, so after no more connections will be
                     * accepted, ensure that the server socket is no
                     * longer listening.
                     */
                    serverSocket.close();
                } catch (IOException e) {
   
                }
            }
        }
   |继续看executeAcceptLoop方法
   private void executeAcceptLoop() {
   
            if (tcpLog.isLoggable(Log.BRIEF)) {
   
                tcpLog.log(Log.BRIEF, "listening on port " +
                           getEndpoint().getPort());
            }

            while (true) {
   
                Socket socket = null;
                try {
   
                    socket = serverSocket.accept();

                    /*
                     * Find client ho
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值