一、基础概念
RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样。
RPC 可基于 HTTP 或 TCP 协议,Web Service 就是基于 HTTP 协议的 RPC,它具有良好的跨平台性,但其性能却不如基于 TCP 协议的 RPC。会两方面会直接影响 RPC 的性能,一是传输方式,二是序列化。
但rpc在实际项目中基于http协议的webService使用的更多。
对于远程调用的基于restful的webservice主要有以下几种实现方式
1.org.springframework.web.client.RestTemplate
2.org.apache.commons.httpclient.HttpClient
可以使用这些组件来进行远程调用,就像是在浏览器或者postman等工具上调用一样的效果。
二、RPC原理
由于各服务部署在不同机器,服务间的调用免不了网络通信过程,服务消费方每调用一个服务都要写一坨网络通信相关的代码,不仅复杂而且极易出错。
如果有一种方式能让我们像调用本地服务一样调用远程服务,而让调用者对网络通信这些细节透明,那么将大大提高生产力,比如服务消费方在执行helloWorldService.sayHello(“test”)时,实质上调用的是远端的服务。这种方式其实就是RPC(Remote Procedure Call Protocol),在各大互联网公司中被广泛使用,如阿里巴巴的hsf、dubbo(开源)、Facebook的thrift(开源)、Google grpc(开源)、Twitter的finagle(开源)等。
要让网络通信细节对使用者透明,我们需要对通信细节进行封装,我们先看下一个RPC调用的流程涉及到哪些通信细节:
2.1 RPC调用的流程
1.服务消费方(client)调用以本地调用方式调用服务;
2.client stub接收到调用后负责将方法、参数等按照协议组装成能够进行网络传输的消息体,即进行编码和系列化。
3.client stub找到服务地址,并将消息发送到服务端;
4.server stub收到消息后进行解码和反系列化
5.server stub根据解码结果调用本地的服务;
6.本地服务执行并将结果返回给server stub;
7.server stub将返回结果打包成消息并发送至消费方;
8.client stub接收到消息,并进行解码;
9.服务消费方得到最终结果。
RPC的目标就是要2~8这些步骤都封装起来,让用户对这些细节透明。
三、架构设计
在一个典型RPC的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,其中RPC协议就指明了程序如何进行网络传输和序列化 。
3.1 rpc协议基本组成
3.1.1 http协议报文
请求消息Request
请求行:用来说明请求类型,要访问的资源以及所使用的HTTP版本.
请求头:紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息。
从第二行起为请求头部,HOST将指出请求的目的地.
User-Agent,服务器端和客户端脚本都能访问它,它是浏览器类型检测逻辑的重要基础.该信息由你的浏览器来定义,并且在每个请求中自动发送等等
空行:请求头部后面的空行是必须的
请求body:数据也叫主体,可以添加任意的其他数据。
响应消息Response
状态:,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
消息报头:用来说明客户端要使用的一些附加信息
空行:消息报头后面的空行是必须的
响应body:服务器返回给客户端的文本信息。
3.1.2 Dubbo 协议报文
通过查看dubbo控制台事列如下:
provider节点:
consumer节点:
其实质dubbo协议就是仿造http协议规范而进行自定义的。
Dubbo协议的编解码过程
dubbo在编码或解码的过程中就会对参数或结果进行序列化。
3.2 技术选型
1.序列化
什么是序列化?序列化就是将数据结构或对象转换成二进制串的过程,也就是编码的过程。
什么是反序列化?将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
为什么需要序列化?转换为二进制串后才好进行网络传输嘛!
为什么需要反序列化?将二进制转换为对象才好进行后续处理!
Java 提供了默认的序列化方式,但在高并发的情况下,这种方式将会带来一些性能上的瓶颈,于是市面上出现了一系列优秀的序列化框架,
比如:Protobuf、Kryo、Hessian、Jackson 等,它们可以取代 Java 默认的序列化,从而提供更高效的性能。
2.网路传输
为了支持高并发,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO,即 NIO。
Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持,用 Java 实现 NIO 并不是遥不可及的事情,只是需要我们熟悉 NIO 的技术细节。
3.注册中心
如何让别人使用我们的服务呢?有同学说很简单嘛,告诉使用者服务的IP以及端口就可以了啊。确实是这样,这里问题的关键在于是自动告知还是人肉告知。
人肉告知的方式:如果你发现你的服务一台机器不够,要再添加一台,这个时候就要告诉调用者我现在有两个ip了,你们要轮询调用来实现负载均衡;调用者咬咬牙改了,结果某天一台机器挂了,调用者发现服务有一半不可用,他又只能手动修改代码来删除挂掉那台机器的ip。现实生产环境当然不会使用人肉方式。
有没有一种方法能实现自动告知,即机器的增添、剔除对调用方透明,调用者不再需要写死服务提供方地址?当然可以,现如今zookeeper被广泛用于实现服务自动注册与发现功能!
简单来讲,zookeeper可以充当一个服务注册表(Service Registry),让多个服务提供者形成一个集群,让服务消费者通过服务注册表获取具体的服务访问地址(ip+端口)去访问具体的服务提供者
心跳检测设想:
可以使用一个定时任务线程每隔1s去检测服务的长连接状态
4.透明化远程服务调用–动态代理
怎么封装通信细节才能让用户像以本地调用方式调用远程服务呢?对
java来说就是使用代理!
java代理有两种方式:
1.jdk 动态代理
2.字节码生成
尽管字节码生成方式实现的代理更为强大和高效,但代码维护不易,大部分公司实现RPC框架时还是选择动态代理方式。
消费端创建的代理对象主要的功能为:请求编码,请求参数序列化,网络传输,返回解码,返回结果反系列化等骗底层操作。
具体实现请参考:http://www.importnew.com/22003.html
根据以上技术需求,我们可使用如下技术选型:
Spring:它是最强大的依赖注入框架,也是业界的权威标准。
Netty:它使 NIO 编程更加容易,屏蔽了 Java 底层的 NIO 细节。
Protostuff:它基于 Protobuf 序列化框架,面向 POJO,无需编写 .proto 文件。
ZooKeeper:提供服务注册与发现功能,开发分布式系统的必备选择,同时它也具备天生的集群能力。
jdk动态代理:
2.2 对消息进行编码和解码
2.2.1 确定消息数据结构
http 报文编码原理
2.2.2 序列化和反序列化
具体实现请参考:http://www.importnew.com/22003.html
2.3 通信
2.4 requestID(待续)
3.发布自己的服务
通过zookeeper来实现服务自动注册与发现功能
具体实现请参考:http://www.importnew.com/22003.html
思考
1.Dubbo 和 Spring Cloud 有什么区别?
两个没关联,如果硬要说区别,有以下几点。
1)通信方式不同
Dubbo 使用的是 RPC 通信,而 Spring Cloud 使用的是 HTTP RESTFul 方式。
2)组成部分不同
Dubbo | Spring Cloud | |
---|---|---|
服务注册中心 | Zookeeper | Spring Cloud Netflix Eureka |
服务调用方式 | 基于TCP协议 | 基于Http协议+rest接口 |
服务服务网关 | 无 | Spring Cloud Netflix Zuul |
断路器 | 不完善 | Spring Cloud Netflix Hystrix |
分布式配置 | 无 | Spring Cloud Config |
服务跟踪 | 无 | Spring Cloud Sleuth |
消息总线 | 无 | Spring Cloud Bus |
数据流 | 无 | Spring Cloud Stream |
批量任务 | 无 | Spring Cloud Task |
四、Rpc架构图
1.分布式RPC流程图
参考资料:
1)轻量级分布式 RPC 框架:http://www.importnew.com/20327.html
2)RPC原理及RPC实例分析:http://www.importnew.com/22003.html