1. 为什么要使用RPC?



RPC(remote procedure
call)是指远程过程调用,比如两台服务器A和B,A服务器上部署一个应用,B服务器上部署一个应用,A服务器上的应用想调用B服务器上的应用提供的接口,由于不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和传达调用的数据。



RPC(remote procedure call,远程过程调用):



*
首先,要解决通讯的问题,主要是通过客户端和服务器端之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享一个连接。
*
第二,要解决寻址的问题,A服务器上的应用要调用B服务器上的应用,A服务器上的应用需要通过底层RPC框架得知:如何连接到B服务器(主机或IP地址)以及特定的端口,方法的名称等信息,这样才能完成调用。
*
第三,A服务器上的应用发起远程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数需要序列化成二进制形式,然后再通过寻址和传输将序列化的二进制发送给B服务器。
* 第四:B服务器收到请求后,需要进行反序列化,恢复为内存中的表达方式,然后找到对应的方法进行本地调用并返回,序列化返回值并发送给A服务器。
* 第五:A服务器收到B服务器的返回值后,进行反序列化,恢复为内存中的表达方式,然后交给A服务器上的应用进行处理。
以上参考: 谁能用通俗的语言解释一下什么是 RPC 框架? <https://www.zhihu.com/question/25536695>

RPC的协议有很多,比如Java RMI、WebService的RPC风格、Hession、Thrift、REST API


2. RPC、RMI、SOAP、REST的区别




RMI(remote method invocation,面向对象的远程方法调用)
RPC(remote procedure call,远程过程调用)
SOAP(simple object access protoal,简单对象访问协议)
REST(representational state transfer,表达性状态转移)

可以都理解为调用远程方法的一些通信技术“风格”:


· RMI就好比它是本地工作,采用tcp/ip协议,客户端直接调用服务端上的一些方法。优点是强类型,编译期可检查错误,缺点是只能基于JAVA语言,客户机与服务器紧耦合。

· RPC是一个泛化的概念,严格来说一切远程过程调用手段都属于rpc范畴,包括rmi、hessian、soap、thrift、protobuf等等。


· SOAP是在XML-RPC基础上,使用标准的XML描述了RPC的请求信息(URI/类/方法/参数/返回值)。因为XML-RPC只能使用有限的数据类型种类和一些简单的数据结构,SOAP能支持更多的类型和数据结构。优点是跨语言,非常适合异步通信和针对松耦合的C/S,缺点是必须做很多运行时检查。

· REST一般用来和SOAP做比较,它采用简单的URL方式来代替一个对象,优点是轻量,可读性较好,不需要其他类库支持,缺点是URL可能会很长,不容易解析。


3. Java RMI

Java RMI(Romote Method
Invocation)是一种基于Java的远程方法调用技术,是Java特有的一种RPC实现。它能够部署在不同主机上的Java对象之间进行透明的通信与方法调用。



RMI工作原理:








首先,在一个JVM中启动rmiregistry服务,启动时可以指定服务监听的端口,也可以使用默认的端口。


其次,RMIServer在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming,Context,Registry等类的bind或rebind方法将刚才实例化好的实现类注册到RMIService上并对外暴露一个名称。


最后,RMIClient通过本地的接口和一个已知的名称(即RMIServer暴露出的名称)再使用RMI提供的Naming,Context,Registry等类的lookup方法从RMIService那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,想怎么调就怎么调。

RMI 采用stubs 和 skeletons 来进行远程对象(remote object)的通讯。stub
充当远程对象的客户端代理,有着和远程对象相同的远程接口,远程对象的调用实际是通过调用该对象的客户端代理对象stub来完成的,通过该机制RMI就好比它是本地工作,采用tcp/ip协议,客户端直接调用服务端上的一些方法。优点是强类型,编译期可检查错误,缺点是只能基于Java语言,客户机与服务器紧耦合。

具体RMI的原理可参考:java RMI原理详解
<http://blog.csdn.net/xinghun_4/article/details/45787549>

深入理解rmi原理 <https://www.jianshu.com/p/2c78554a3f36>

RMI使用Demo:
定义RMI对外服务接口:RMI接口方法定义必须显示声明抛出RemoteException异常。


package com.yyy.RMIDemo.java.server; import java.rmi.Remote; import
java.rmi.RemoteException; /** * RMI对外服务接口 * @author * */ public interface
HelloService extends Remote{ public String sayHello(String someone) throws
RemoteException; }
服务端接口实现:服务端方法实现必须继承UnicastRemoteObject类,该类定义了服务调用方与服务提供方对象实例,并建立一对一的连接。



package com.yyy.RMIDemo.java.server; import java.rmi.RemoteException; import
java.rmi.server.UnicastRemoteObject; /** * 服务器端接口实现 * @author * */ public class
HelloServiceImpl extends UnicastRemoteObject implements HelloService { private
static final long serialVersionUID = 4176511759435216154L; protected
HelloServiceImpl() throws RemoteException { super(); // TODO Auto-generated
constructor stub } @Override public String sayHello(String someone) throws
RemoteException { // TODO Auto- generated method stub return "Hello" + someone;
} }



RMI的通信端口是随机产生的,因此有可能被防火墙拦截,为了防止被防火墙拦截,需要强制指定RMI的通信端口。一般通过自定义一个RMISocketFactory类来实现,代码如下:


package com.yyy.RMIDemo.java.server; import java.io.IOException; import
java.net.ServerSocket; import java.net.Socket; import
java.rmi.server.RMISocketFactory; public class CustomerSocketFactory extends
RMISocketFactory{ @Override public ServerSocket createServerSocket(int port)
throws IOException { // TODO Auto-generated method stub if (port == 0) { port =
8051; } System.out.println("rmi notify port : " + port); return new
ServerSocket(port); } //指定通信端口,防止被防火墙拦截 @Override public Socket
createSocket(String host, int port) throws IOException { // TODO Auto-generated
method stub return new Socket(host, port); } }



服务端RMI服务启动:

package com.yyy.RMIDemo.java.server; import java.rmi.Naming; import
java.rmi.registry.LocateRegistry; import java.rmi.server.RMISocketFactory;
public class ServerMain { public static void main(String[] args) throws
Exception { //创建服务 HelloService helloService = new HelloServiceImpl(); //注册服务
LocateRegistry.createRegistry(8801); //指定通信端口,防止被防火墙拦截
RMISocketFactory.setSocketFactory(new CustomerSocketFactory());
Naming.bind("rmi://localhost:8801/helloService", helloService);
System.out.println("ServceMain provide service now"); } }



客户端远程调用RMI服务代码:


package com.yyy.RMIDemo.java.client; import java.rmi.Naming; import
com.yyy.RMIDemo.java.server.HelloService; public class ClientMain { public
static void main(String[] args) throws Exception { HelloService helloService =
(HelloService)Naming.lookup("rmi://localhost:8801/helloService");
System.out.println("RMI 客户端接收到的结果是:" + helloService.sayHello("RMI")); } }
先运行服务器端程序ServerMain,然后运行ClinetMain,运行结果如下:



RMI 客户端接收到的结果是:HelloRMI




4. Hessian 
Hessian 是由 caucho 提供的一个基于 binary-RPC 实现的远程通讯 library 。Hessian基于Http协议进行传输。



Binary-RPC 是一种和 RMI 类似的远程调用的协议,它和 RMI 的不同之处在于它以标准的二进制格式来定义请求的信息 ( 请求的对象、方法、参数等
) ,这样的好处是什么呢,就是在跨语言通讯的时候也可以使用。传输协议基于TCP。




Hessian可通过Servlet提供远程服务,需要将匹配某个模式的请求映射到Hessian服务。也可Spring框架整合,通过它的DispatcherServlet可以完成该功能,DispatcherServlet可将匹配模式的请求转发到Hessian服务。Hessian的server端提供一个servlet基类,
用来处理发送的请求,而Hessian的这个远程过程调用,完全使用动态代理来实现的,,建议采用面向接口编程,Hessian服务通过接口暴露。

Hessian处理过程示意图:

客户端——>序列化写到输出流——>远程方法(服务器端)——>序列化写到输出流 ——>客户端读取输入流——>输出结果
Spring + Hessian实现服务器端的demo:
在工程中导入hessian的jar包

在web.xml中,我们配置SpringMVC的DispatcherServlet:



<!-- Spring hessian servlet --> <servlet>
<servlet-name>hessian</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param> <param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-hessian.xml</param-value> </init-param>
<load-on-startup>1</load-on-startup> <async-supported>true</async-supported>
</servlet> <servlet-mapping> <servlet-name>hessian</servlet-name>
<url-pattern>/pubservice/*</url-pattern> </servlet-mapping>
spring-hessian.xml的配置:



<bean id="/testService"
class="com.example.platform.hession.MyHessianServiceExporter"> <property
name="service" ref="testServiceImpl" /> <property name="serviceInterface"
value="com.xinwei.platform.pubservice.TestService" /> </bean>

使用了org.springframework.remoting.caucho.HessianServiceExporter来发布服务。将程序部署在tomcat中。如果我们想从
HessianServiceExporter的handleRequest
方法中可以获得客户端request,那我们就会有很多种方法得到此request;Aop方式或重写方法,我们采用重写的方式:



/** * */ package com.example.platform.hession; import
javax.servlet.ServletRequest; /** * Hession service线程上下文,用以线程安全地保存客户端request *
@author yangyunyun * */ public class HessionContext { private ServletRequest
request; private static final ThreadLocal<HessionContext> localContext = new
ThreadLocal<HessionContext>(){ @Override public HessionContext initialValue(){
return new HessionContext(); } }; private HessionContext(){ } public static
ServletRequest getRequest() { return localContext.get().request; } public
static void setRequest(ServletRequest request) { localContext.get().request =
request; } public static void clear() { localContext.get().request = null; } }
自定义类:



/** * */ import java.io.IOException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import
javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import
org.slf4j.LoggerFactory; import
org.springframework.remoting.caucho.HessianExporter; import
org.springframework.web.HttpRequestHandler; import
org.springframework.web.HttpRequestMethodNotSupportedException; import
org.springframework.web.util.NestedServletException; /** * @author * */ public
class MyHessianServiceExporter extends HessianExporter implements
HttpRequestHandler { private static final Logger logger =
LoggerFactory.getLogger(InmpServiceExporter.class); @Override public void
handleRequest(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException { // TODO Auto-generated method stub if
(!"POST".equals(request.getMethod())){ throw new
HttpRequestMethodNotSupportedException( request.getMethod(), new
String[]{"POST"}, "HessionSeriviceExporter only supports POST requests"); }
response.setContentType(CONTENT_TYPE_HESSIAN); try { //保存request到Hession线程上下文
HessionContext.setRequest(request); invoke(request.getInputStream(),
response.getOutputStream()); } catch (Throwable ex) { // TODO: handle exception
logger.error("Hession skeleton invocation failed"); throw new
NestedServletException("Hession skeleton invocation failed", ex); } finally{
HessionContext.clear(); } } }Service开发中就可以直接使用 HessianContext.getRequest();
 来获得客户端的request了,所有的客户端信息随你所用了,而且是线程安全的。


服务器端服务接口:


package com.example.platform.pubservice; public interface TestService { public
String sayHello(String datas); }接口实现类:




package com.example.platform.pubservice.impl; import
com.example.platform.pubservice.TestService; public class TestServiceImpl
extends InmpHessianHandle implements TestService{ @Override public String
sayHello(String datas) { // TODO Auto-generated method stub return "Hello " +
datas; } }client调用:



public class TestClient { public static void main(String[] args) { try {
String url = "http://localhost:8080/HessianDemo/pubservice/testService";
HessianProxyFactory factory = new HessianProxyFactory();
factory.setOverloadEnabled(true); TestService testService = (TestService)
factory.create(TestService.class, url);
System.out.println(basic.sayHello("SW")); }catch (Exception e){
e.printStackTrace(); } } }


Hessian的原理以及使用请参考: Hessian的使用以及理解
<http://blog.csdn.net/sunwei_pyw/article/details/74002351>

5. WebService




WebService是一种跨平台的RPC技术协议。WebService技术栈由SOAP(简单对象访问协议)、UDDI(统一描述、发现与集成)、WSDL(网络服务描述语言:用来描述Service、以及如何访问WebService)组成。SOAP是一种使用XML进行数据编码的通信协议。独立于平台、独立于语言,简单可扩展。

WebService常用的两种实现:CXF和Axis2



详解见 从零开始写分布式服务框架 1.3小节



目前三种主流的web服务实现方法:

REST(新型):表象化状态转变 (软件架构风格)RESTEasy、Wink、CXF、Axis2…….

SOAP(比较成熟):简单对象访问协议  Xfire、Axis2、CXF、Axis1

XML-RPC(淘汰):远程过程调用协议(慢慢被soap 所取代)

1、Java开发WebService最重要的两个规范:

JSR-224 (JAX-WS:Java API for XML-Based Web Services ) ,主要使用soap协议,使用wsdl来描述;

JSR-311 (JAX-RS:The Java API for RESTful Web Services),简化了 web service 的设计,
它不再需要 wsdl ,也不再需要 soap 协议,而是通过最简单的 http 协议传输数据 ( 包括 xml 或 json) 
。既简化了设计,也减少了网络传输量(因为只传输代表数据的 xml 或 json ,没有额外的 xml 包装)。



JAX-WS是针对WebService。而JAX-RS是针对RESTful HTTP Service。

RESTful HTTP Service相关介绍可以参考:RESTful Service API 设计最佳工程实践和常见问题解决方案
<https://www.jianshu.com/p/cf80d644727e>

JAX-WS与JAX-RS两者是不同风格的SOA架构。前者以动词为中心,指定的是每次执行函数。而后者以名词为中心,每次执行的时候指的是资源。



JAX-RS是JAVA EE6 引入的一个新技术。是一个Java
编程语言的应用程序接口,支持按照表述性状态转移(REST)架构风格创建Web服务。JAX-RS使用了Java
SE5引入的Java标注来简化Web服务的客户端和服务端的开发和部署。JAX-WS规范是一组XML web services的JAVA
API,JAX-WS允许开发者可以选择RPC-oriented或者message-oriented 来实现自己的web services。


支持JAX-WS服务规范的框架有:CXF,Axis,Xfire。结合java语言均可可实现JAX-WS

支持JAX-RS服务规范的框架有: 

1.CXF——XFire和Celtix的合并 

2.Jersey——Sun公司的JAX-RS参考实现。 

3.RESTEasy——JBoss的JAX-RS项目。

4.Restlet——也许是最早的REST框架了,它JAX-RS之前就有了。








在分布式服务框架中,除了实现RPC的特性以外,还包括负载均衡策略以及实现,服务的注册、发布与引入。服务的高可用策略以及服务治理等特性。


RESTful架构风格参考:RESTful 架构风格概述
<https://blog.igevin.info/posts/restful-architecture-in-general/>

RESTful Web 服务:教程 <https://zhuanlan.zhihu.com/p/21644769>

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信