分布式基础-RMI原理简单通俗版

这篇文章在两年前第一次接触RMI时就想写。但当时百度了许多资料,都觉得讲的太深理解不来。最近学习分布式系统相关的知识时,好好的把RMI原理再学了一遍,希望可以写出一篇简单通俗易懂的RMI基本原理解析出来,帮助一下当初和我一样的朋友。

注:本篇文章不涉及源码解析,只讲解最简单的两个问题:
RMI到底是个什么东西?
它又是怎么实现的?

定义

讲解之前,首先弄懂三个名词和它们之间的关系:分布式,RPC,RMI

1. 分布式:

简单来说,就是一个系统大了以后,单个服务器肯定性能不够,所以进行拆分。将原有的一个复杂业务分拆多个子业务,部署在不同的服务器上,相互进行调用。

关于分布式和集群我以前一直有点混淆,这里引用 知乎:分布式与集群的区别是什么?上一位大神的讲解:

小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群。为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群

2. RPC(Remote Process Call)

远程进程调用,一个服务器的进程调用另外一个服务器上的进程,这也是分布式应用实现的基础。通过RPC,分布式应用之间互相调用,给整个系统的处理能力和吞吐量带来了近似无限制提升的可能。

3. RMI(Remote Method Invocation)

远程方法调用,一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。

RMIRPC的一种实现方案,基于的是TCP协议;还有基于HTTP协议实现的RPC,例如Hessian

第一个问题:RMI到底是什么?

定义里面已经讲解了部分,RMI是分布式应用之间调用的一种手段。

再具体一点,现在有一个场景,应用A里面有个方法Method1,想要调用应用B里面的方法Method2,怎么办?答案:用RMI。

下面给出RMI调用的Demo代码:

其中,应用B为RMI服务端,提供HelloImpl类的sayHello方法给应用A调用

/*  
 * 任何远程对象都必须直接或间接实现接口Remote 。它的方法才可被远程调用。  
 */  
public interface IHello extends Remote {   
    public String sayHello(String name) throws java.rmi.RemoteException;  
}  

/* 
 * 远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象时, 
 * 该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为“存根”, 
 * 而服务器端本身已存在的远程对象则称之为“骨架”。其实此时的存根是客户端的一个代理,用于与服务器端的通信, 
 * 而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。 
 */   

/* java.rmi.server.UnicastRemoteObject构造函数中将生成stub和skeleton */  
public class HelloImpl extends UnicastRemoteObject implements IHello {  

    // 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常    
    protected HelloImpl() throws RemoteException {  
        super();  
    }  

    private static final long serialVersionUID = 4077329331699640331L;  
    public String sayHello(String name) throws RemoteException {  
        return "Hello " + this.name + " ^_^ ";  
    }  
}  


public class HelloServer {  
    public static void main(String[] args) {  
        try {   
            IHello hello = new HelloImpl(); /* 生成stub和skeleton,并返回stub代理引用 */  
            /* 本地创建并启动RMI Service,被创建的Registry服务将在指定的端口上侦听到来的请求  
             * 实际上,RMI Service本身也是一个RMI应用,我们也可以从远端获取Registry: 
             *     public interface Registry extends Remote; 
             *     public static Registry getRegistry(String host, int port) throws RemoteException; 
             */  
            LocateRegistry.createRegistry(4099);  
            /* 将stub代理绑定到Registry服务的URL上 */  
            java.rmi.Naming.rebind("rmi://localhost:4099/hello", hello);  
            System.out.print("Ready");  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
} 

应用A为RMI客户端,通过RMI调用应用B中HelloImpl类的sayHello方法

/*  
 * 任何远程对象都必须直接或间接实现接口Remote 。它的方法才可被远程调用。  
 */  
public interface IHello extends Remote {   
    public String sayHello(String name) throws java.rmi.RemoteException;  
}  

public class Hello_RMI_Client {
    public static void main(String[] args) {  
        try {  
            /* 从RMI Registry中请求stub 
             * 如果RMI Service就在本地机器上,URL就是:rmi://localhost:1099/hello 
             * 否则,URL就是:rmi://RMIService_IP:1099/hello 
             */  
            IHello hello = (IHello) Naming.lookup("rmi://localhost:4099/hello");  
            /* 通过stub调用远程接口实现 */  
            System.out.println(hello.sayHello("zhangxianxin"));  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}/**output:
Hello zhangxianxin ^_^ 
**/

第二个问题:RMI是怎么实现的?

RMI的实现思路

我们先思考一个问题,如果让我们自己用Java实现RMI,要怎么写?

一种思路是:应用A调用应用B的方法时,我们可以将A调用B的方法的信息封装起来,通过Socket通信传到B的监听端口,B收到信息后进行解析,获取出调用方法信息后调用对应的方法,然后将方法的返回信息再通过Socket通讯返回给A

这种思路其实就是RMI的大致实现。

RMI的框架

现在我们来更细节的看下RMI的框架:

RMI框架中有五个“对象”:

  1. 调用服务的客户对象
  2. Stub(客户辅助对象)
  3. 提供服务的服务对象
  4. Skeleton(服务辅助对象)
  5. RMI Registry

从图中可以看成,当应用A的客户对象想要调用应用B的服务对象中的方法时,找的是Stub这个客户辅助对象。而应用B的服务对象中的方法被调用时,也是通过Skeleton这个服务辅助对象。

Stub和Skeleton

那么Stub和Skeleton这两个辅助对象到底是什么呢?搞清楚了这两个概念你就差不多懂RMI了。

前面我们讲过,RMI的实现思路其实就是把调用方法的信息通过Socket传到应用B去,然后应用B解析方法信息进行调用。那么这个Socket传输和解析的过程在哪里呢?答案就是Stub和Skeleton了。

Stub和Skeleton就是用来负责Socket通信的。它们都是由RMI自动生成的,下面给个代码例子:

public interface Person {        
    public int getAge() throws Throwable;        
    public String getName() throws Throwable;        
}  

public class Person_Stub implements Person {        
    private Socket socket;        
    public Person_Stub() throws Throwable {        
        // connect to skeleton        
        socket = new Socket("computer_name", 9000);        
    }        
    public int getAge() throws Throwable {        
        // pass method name to skeleton        
        ObjectOutputStream outStream =        
            new ObjectOutputStream(socket.getOutputStream());        
        outStream.writeObject("age");        
        outStream.flush();        
        ObjectInputStream inStream =        
            new ObjectInputStream(socket.getInputStream());        
        return inStream.readInt();        
    }        
    public String getName() throws Throwable {        
        // pass method name to skeleton        
        ObjectOutputStream outStream =        
            new ObjectOutputStream(socket.getOutputStream());        
        outStream.writeObject("name");        
        outStream.flush();        
        ObjectInputStream inStream =        
            new ObjectInputStream(socket.getInputStream());        
        return (String)inStream.readObject();        
    }  
}   

public class Person_Skeleton extends Thread {        
    private PersonServer myServer;        
    public Person_Skeleton(PersonServer server) {        
        // get reference of object server        
        this.myServer = server;        
    }        
    public void run() {        
        try {        
            // new socket at port 9000        
            ServerSocket serverSocket = new ServerSocket(9000);        
            // accept stub's request        
            Socket socket = serverSocket.accept();        
            while (socket != null) {        
                // get stub's request        
                ObjectInputStream inStream =        
                    new ObjectInputStream(socket.getInputStream());        
                String method = (String)inStream.readObject();        
                // check method name        
                if (method.equals("age")) {        
                    // execute object server's business method        
                    int age = myServer.getAge();        
                    ObjectOutputStream outStream =        
                        new ObjectOutputStream(socket.getOutputStream());        
                    // return result to stub        
                    outStream.writeInt(age);        
                    outStream.flush();        
                }        
                if(method.equals("name")) {        
                    // execute object server's business method        
                    String name = myServer.getName();        
                    ObjectOutputStream outStream =        
                        new ObjectOutputStream(socket.getOutputStream());        
                    // return result to stub        
                    outStream.writeObject(name);        
                    outStream.flush();        
                }        
            }        
        } catch(Throwable t) {        
            t.printStackTrace();        
            System.exit(0);        
        }        
    }             
}  
RMI Registry

那RMI Registry呢?
RMI Registry也可以看成一个应用,它是Java内部自己的应用。主要用于查找对象对应的“Stub”。

/* 将stub代理绑定到Registry服务的URL上 */  
 java.rmi.Naming.rebind("rmi://localhost:1099/hello", hello); 

服务端通过上面的代码,会将服务对象hello的Stub绑定在Registry的服务上面

IHello hello = (IHello) Naming.lookup("rmi://localhost:1099/hello");

客户端通过Registry查找出对应的Stub(注意,此处返回的其实是IHelloImpl的Stub对象),然后客户端就可以通过该Stub对象和服务端通信,调用对应的方法了

这个过程如下图:


本文部分内容参考:
java RMI原理详解
深究Java中的RMI底层原理

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值