基于Spring的RPC通信模型.

1、概念和原理

    RPC(remote procedure call),远程过程调用,是客户端应用和服务端之间的会话。在客户端,它所须要的一些功能并不在该应用的实现范围以内,因此应用要向提供这些功能的其余系统寻求帮助。而远程应用经过远程服务暴露这些功能。RPC 是同步操做,会阻塞调用代码的执行,直到被调用的过程执行完毕。java

    Spring支持多种不一样的RPC模型,包括RMI、Caucho的Hessian和Burlap以及Spring自带的HTTP invoker:git

    客户端:github

    在全部的模型中,服务都是做为 Spring 所管理的 bean 配置到咱们的应用中。这是经过一个代理工厂 bean 实现的,这个bean可以把远程服务像本地对象同样装配到其余bean的属性中。spring

    客户端向代理发起调用,就像代理提供了这些服务同样。代理表明客户端和远程服务进行通讯,由它负责处理链接的细节并向远程服务发起调用。网络

    服务端:app

Spring 使用远程导出器(remote exporter)将bean方法发布为远程服务。框架

2、RMI

    RMI 最初在JDK 1.1被引入到Java平台中,它为Java开发者提供了一种强大的方法来实现Java程序间的交互。ide

    Spring 提供了简单的方式来发布RMI服务,在服务端,RmiServiceExporter 能够把任何 Spring 管理的bean发布为RMI服务 ,如图所示,RmiServiceExporter 把bean包装在一个适配器类中,而后适配器类被绑定到RMI注册表中,而且代理到服务类的请求。 ui

    /**
     * 服务端:
     * <p>
     * 一、默认状况下,RmiServiceExporter 会尝试绑定到本地机器1099端口上的RMI注册表。
     * 二、若是在这个端口没有发现RMI注册表,RmiServiceExporter 将会启动一个注册表。
     * 三、可重写注册表的路径和端口,这个是个大坑,当你设置了registryHost属性的时候,源码中就不建立Registry,而是直接去获取,但是咱们本身也没有建立,因此就会报链接不上。
     *
     * @param userService
     * @return
     */
    @Bean(name = "rmiServiceExporter")
    public RmiExporter rmiServiceExporter(UserService userService, Environment environment) {
        String registryHost = environment.getProperty("registryHost");
        int registryPort = environment.getProperty("registryPort", Integer.class);
        RmiExporter rmiExporter = new RmiExporter();
        rmiExporter.setService(userService); //要把该bean(即rmiServiceImpl)发布为一个RMI服务
        rmiExporter.setServiceName("RmiService"); //命名RMI 服务
        rmiExporter.setServiceInterface(UserService.class); //指定服务所实现的接口
        rmiExporter.setRegistryHost(registryHost);
        rmiExporter.setRegistryPort(registryPort);
        return rmiExporter;
    }
/**
 * Created by XiuYin.Cui on 2018/5/14.
 * 
 * 解决设置 registryHost 后,报链接拒绝的问题。
 */
public class RmiExporter extends RmiServiceExporter {

    @Override
    protected Registry getRegistry(String registryHost, int registryPort, RMIClientSocketFactory clientSocketFactory,
                                   RMIServerSocketFactory serverSocketFactory) throws RemoteException {


        if (registryHost != null) {
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
                }
                //把spring源代码中这里try起来,报异常就建立一个
                Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
                testRegistry(reg);
                return reg;
            } catch (RemoteException ex) {
                LocateRegistry.createRegistry(registryPort);
                Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
                testRegistry(reg);
                return reg;
            }
        } else {
            return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
        }
    }
}
View Code

     接下来,来看看客户端是怎么使用这些远程服务的吧!Spring的RmiProxyFactoryBean是一个工厂bean,该bean能够为RMI服务建立代理。该代理表明客户端来负责与远程的RMI服务进行通讯。客户端经过服务的接口与代理进行交互,就如同远程服务就是一个本地的POJO。url

 

    @Bean(name = "rmiUserServiceClient")
    public RmiProxyFactoryBean RmiUserServiceClient(){
        RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
        rmiProxyFactoryBean.setServiceUrl("rmi://127.0.0.1:9999/RmiService");
        rmiProxyFactoryBean.setServiceInterface(UserService.class);
        rmiProxyFactoryBean.setLookupStubOnStartup(false);//不在容器启动后建立与Server端的链接
        rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);//链接出错的时候自动重连
        rmiProxyFactoryBean.afterPropertiesSet();
        return rmiProxyFactoryBean;
    }
    @Resource(name="rmiUserServiceClient")
    private UserService userService;

     RMI 的缺陷:

一、RMI很难穿越防火墙,这是由于RMI使用任意端口来交互——这是防火墙一般所不容许的。
二、RMI是基于Java的。这意味着客户端和服务端必须都是用java开发。由于RMI使用了Java的序列化机制,因此经过网络传输的对象类型必需要保证在调用两端的Java运行时中是彻底相同的版本。

    tips:最近发现 Dubbo 底层也是用 RMI 实现的,它把 zookeeper 看成注册表。

3、Hessian 和 Burlap

    hessian 和 Burlap 是 Caucho Technology 的两种基于HTTP的轻量级远程服务解决方案。借助于尽量简单的API和通讯协议,它们都致力于简化Web服务。

    hessian,像RMI同样,使用二进制消息进行客户端和服务端的交互。可是它与RMI不一样的是,它的二进制消息能够移植到其余非Java的语言中。因为它是基于二进制的,因此它在带宽上更具优点。

    Burlap 是一种基于XML的远程调用技术,这使得它能够天然而然的移植到任何可以解析XML的语言上。正由于它基于XML,因此相比起Hessian的二进制格式而言,Burlap可读性更强。可是和其余基于XML的远程技术(例如SOAP或XML-RPC)不一样,Burlap的消息结构尽量的简单。

    下面咱们会介绍 hessian 的使用。Spring 不推荐使用 Burlap,BurlapServiceExporter 在4.0后被废弃,再也不提供支持。5.0 后直接从开发包丢弃了。

    服务端,相似于 RmiServiceExporter ,hessian 也有一个 HessianServiceExporter 将 Spring 管理的 bean 发布为 Hessian 服务,不一样于RMI的是,HessianServiceExporter是一个Spring MVC控制器,它接收Hessian请求(HTTP协议的请求),并将这些请求转换成对被导出POJO的方法调用。既然是HTTP请求,那咱们就必须配置Spring 的 DispatcherServlet ,并配置 HandlerMapping,将相应的URL映射给 HessianServiceExporter。

 

    /**
     * hessian没有注册表,不须要设置 serviceName
     */
    @Bean(name = "hessianServiceExporter")
    public HessianServiceExporter hessianServiceExporter(UserService userService) {
        HessianServiceExporter hessianServiceExporter = new HessianServiceExporter();
        hessianServiceExporter.setService(userService);
        hessianServiceExporter.setServiceInterface(UserService.class);
        return hessianServiceExporter;
    }
    /**
     * 须要配置一个URL映射来确保DispatcherServlet把请求转给HessianServiceExporter
     */
    @Bean(name = "handlerMapping")
    public HandlerMapping handlerMapping() {
        SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
        Properties mappings = new Properties();
        mappings.setProperty("/user.service", "hessianServiceExporter");
        handlerMapping.setMappings(mappings);
        return handlerMapping;
    }

    客户端,相似于 RmiProxyFactoryBean ,Hessian 也有一个代理工厂Bean——HessianProxyFactoryBean,来建立代理与远程服务进行通讯:

    @Bean(name = "hessianUserServiceClient")
    public HessianProxyFactoryBean hessianUserServiceClient(){
        HessianProxyFactoryBean proxy = new HessianProxyFactoryBean();
        proxy.setServiceUrl("http://127.0.0.1:8080/user.service");
        proxy.setServiceInterface(UserService.class);
        return proxy;
    }
    @Resource(name="hessianUserServiceClient")
    private UserService userService; 

    Hessian 的缺陷:

    hessian 和 Burlap 都是基于HTTP的,它们都解决了RMI所头疼的防火墙渗透问题。可是当传递过来的RPC消息中包含序列化对象时,RMI就完胜 Hessian 和 Burlap 了。由于 Hessian 和 Burlap 都采用了私有的序列化机制,而RMI使用的是Java自己的序列化机制。

4、HttpInvoker

    RMI 和 Hessian 各有本身的缺陷,一方面,RMI使用Java标准的对象序列化机制,可是很难穿透防火墙。另外一方面,Hessian和Burlap能很好地穿透防火墙,可是使用私有的对象序列化机制。就这样,Spring的HTTP invoker应运而生了。HTTP invoker是一个新的远程调用模型,做为Spring框架的一部分,可以执行基于HTTP的远程调用,并使用Java的序列化机制。

    HttpInvoker 的使用和 Hessian 很相似,HttpInvokerServiceExporter 也是一个Spring MVC 控制器,也是经过 DispatcherServlet 将请求分发给它...

    /*Http Invoker*/
    @Bean(name = "httpInvokerServiceExporter")
    public HttpInvokerServiceExporter httpInvokerServiceExporter(UserService userService){
        HttpInvokerServiceExporter httpInvokerServiceExporter = new HttpInvokerServiceExporter();
        httpInvokerServiceExporter.setService(userService);
        httpInvokerServiceExporter.setServiceInterface(UserService.class);
        return httpInvokerServiceExporter;
    }
    /**
     * 须要配置一个URL映射来确保DispatcherServlet把请求转给HessianServiceExporter
     */
    @Bean(name = "handlerMapping")
    public HandlerMapping handlerMapping() {
        SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
        Properties mappings = new Properties();
        mappings.setProperty("/user.service", "hessianServiceExporter");
        mappings.setProperty("/userInvoker.service", "httpInvokerServiceExporter");
        handlerMapping.setMappings(mappings);
        return handlerMapping;
    }

    客户端,像 RmiProxyFactoryBean 和 HessianProxyFactoryBean 同样,HttpInvoker 也提供了一个代理工厂Bean——HttpInvokerProxyFactoryBean,用于建立HttpInvoker代理来与远程服务通讯:

    @Bean(name = "httpInvokerUserServiceClient")
    public HttpInvokerProxyFactoryBean httpInvokerUserServiceClient(){
        HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean();
        proxy.setServiceUrl("http://127.0.0.1:8080//userInvoker.service");
        proxy.setServiceInterface(UserService.class);
        return proxy;
    }
    @Resource(name="httpInvokerUserServiceClient")
    private UserService userService;

 

 

参考资料:《Spring 实战第四版》

演示源代码连接:https://github.com/JMCuixy/SpringForRpc

相关文章
相关标签/搜索