- 在单体式应用中,各个模块之间的调用是通过编程语言级别的方法或者函数来实现的。
- 但是一个基于微服务的分布式应用是运行在多台机器上的。一般来说,每个服务实例都是一个进程。
- 基于微服务的应用程序是在多个进程或服务上运行的分布式系统,通常甚至跨多个服务器或主机。 每个服务实例通常是一个进程。
- 因此,微服务必须使用进程内通信协议(如 HTTP、AMQP)或二进制协议(如 TCP)进行交互,具体取决于每个服务的性质。
当为某一个服务选择IPC(Inter-Process Communication,进程间通信)时,首先需要考虑服务之间如何交互。客户端和服务器之间有很多的交互模式,我们可以从两个维度进行归类。
第一个维度是一对一还是一对多:
• 一对一:每个客户端请求有一个服务实例来响应。
• 一对多:每个客户端请求有多个服务实例来响应
第二个维度是这些交互式同步还是异步:
• 同步模式:客户端请求需要服务端即时响应,甚至可能由于等待而阻塞。
• 异步模式:客户端请求不会阻塞进程,服务端的响应可以是非即时的。
下表显示了不同交互模式:
• 请求/响应:一个客户端向服务器端发起请求,等待响应。客户端期望此响应即时到达。在一个基于线程的应用中,等待过程可能造成线程阻塞。
• 通知(也就是常说的单向请求):一个客户端请求发送到服务端,但是并不期望服务端响应。
• 请求/异步响应:客户端发送请求到服务端,服务端异步响应请求。客户端不会阻塞,而且被设计成默认响应不会立刻到达。
• 发布/ 订阅模式:客户端发布通知消息,被零个或者多个感兴趣的服务消费。
• 发布/异步响应模式:客户端发布请求消息,然后等待从感兴趣服务发回的响应。
• 解耦客户端和服务端:客户端只需要将消息发送到正确的channel。客户端完全不需要了解具体的服务实例,更不需要一个发现机制来确定服务实例的位置。
• Message Buffering:在一个同步请求/响应协议中,例如HTTP,所有的客户端和服务端必须在交互期间保持可用。而在消息模式中,消息broker将所有写入channel的消息按照队列方式管理,直到被消费者处理。也就是说,在线商店可以接受客户订单,即使下单系统很慢或者不可用,只要保持下单消息进入队列就好了。
• 弹性客户端-服务端交互:消息机制支持以上说的所有交互模式。
• 直接进程间通信:基于RPC机制,试图唤醒远程服务看起来跟唤醒本地服务一样。然而,因为物理定律和部分失败可能性,他们实际上非常不同。消息使得这些不同非常明确,开发者不会出现问题。
• 额外的操作复杂性:消息系统需要单独安装、配置和部署。消息broker(代理)必须高可用,否则系统可靠性将会受到影响。
• 实现基于请求/响应交互模式的复杂性:请求/响应交互模式需要完成额外的工作。每个请求消息必须包含一个回复渠道ID和相关ID。服务端发送一个包含相关ID的响应消息到channel中,使用相关ID来将响应对应到发出请求的客户端。也许这个时候,使用一个直接支持请求/响应的IPC机制会更容易些。
很多开发者都表示他们基于HTTP的API是RESTful的。但是,如同Fielding在他的博客中所说,这些API可能并不都是RESTful的。Leonard Richardson为REST定义了一个成熟度模型,具体包含以下4个层次(摘自IBM):
• HTTP非常简单并且大家都很熟悉。
• 可以使用浏览器扩展(比如Postman)或者curl之类的命令行来测试API。
• 内置支持请求/响应模式的通信。
• HTTP对防火墙友好的。
• 不需要中间代理,简化了系统架构。
• 只支持请求/响应模式交互。可以使用HTTP通知,但是服务端必须一直发送HTTP响应才行。
• 因为客户端和服务端直接通信(没有代理或者buffer机制),在交互期间必须都在线。
• 客户端必须知道每个服务实例的URL。客户端必须使用服务实例发现机制。
提到RPC(Remote Procedure Call),就躲不开提到分布式,这个促使RPC诞生的领域。
假设你有一个Calculator,以及它的实现类CalculatorImpl,那么单体应用时,要调用Calculator的add方法来执行一个加运算,你可以方法中直接使用,因为在同一个地址空间,或者说在同一块内存,这个称为本地函数调用。
现在,将系统改造为分布式应用,接口调用和实现分别在两个子系统内,
服务A里头并没有CalculatorImpl这个类,那它要怎样调用服务B的CalculatorImpl的add方法呢?可以模仿B/S架构的调用方式,在B服务暴露一个Restful接口,然后A服务通过调用这个Restful接口来间接调用CalculatorImpl的add方法。
这样,已经很接近RPC了,不过,像这种每次调用时,是不是都需要写一串发起http请求的代码呢?比如httpClient.sendRequest...之类的,能不能简单一下,像本地方法调用一样,去发起远程调用,让使用者感知不到远程调用的过程。
屏蔽的工作,可以使用代理模式解决,生成一个代理对象,而这个代理对象的内部,就是通过httpClient来实现RPC远程过程调用的。
这就是很多RPC框架要解决的问题和解决的思路,比如阿里的Dubbo。
总结一下,RPC要解决的两个问题:
1. 解决分布式系统中,服务之间的调用问题。
2. 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
RPC是一种技术的概念名词
RPC=Remote Produce Call 是一种技术的概念名词,HTTP是一种协议,RPC可以通过 HTTP 来实现,也可以通过Socket自己实现一套协议来实现.所以可以换一种理解,为何 RPC 还有除 HTTP 之外的实现法,有何必要,毕竟除了HTTP实现外,私有协议不具备通用性.
RPC框架好处
http接口是在接口不多、系统与系统交互较少的情况下,解决信息孤岛初期常使用的一种通信手段;
优点就是简单、直接、开发方便。
如果是一个大型的网站,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了:
首先就是长链接,不必每次通信都要像http一样去3次握手什么的,减少了网络开销;
其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。
最后是安全性。
rpc是一种概念,http也是rpc实现的一种方式。
论复杂度,dubbo/hessian用起来是超级简单的。
至于为什么用dubbo/hessian,有几点:
- 一是调用简单,真正提供了类似于调用本地方法一样调用接口的功能 。
- 二是参数返回值简单明了 参数和返回值都是直接定义在jar包里的,不需要二次解析。
- 三是 轻量,没有多余的信息。
- 四是便于管理,基于dubbo的注册中心。
RPC能解耦服务
RPC:远程过程调用。RPC的核心并不在于使用什么协议。RPC的目的是让你在本地调用远程的方法,而对你来说这个调用是透明的,你并不知道这个调用的方法是部署哪里。
通过RPC能解耦服务,这才是使用RPC的真正目的。RPC的原理主要用到了动态代理模式,至于http协议,只是传输协议而已。简单的实现可以参考spring remoting,复杂的实现可以参考dubbo。
rpc=socket + 动态代理
Dubbo!是阿里开源的分布式服务框架,最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。
Motan!是微博开源的一套高性能、易于使用的分布式远程服务调用(RPC)框架。
gRPC!是Google开源的一套面向移动和HTTP/2设计的,高性能的、通用的远程调用框架。
Motan:我支持 Xml 配置和 Spring注解配置。
Dubbo:我支持 Xml 配置 、 注解配置、 属性配置 、 API 配置 !
gRPC:我,我只支持 API 配置 。
主持人:
Xml 配置是用 xml 文件来配置协议 、 服务 、 注册中心等信息 ,这是 rpc 框架最常用的配置方式,也是最基本的配置方式;
属性配置 是 用 properties 文件来配置协议 、 服务 、 注册中心等信息 , 和Xml 配置使用上异曲同工 ;
注释配置是声明 Annotation 用来指定需要解析的包名 , 使用 spring-boot 启动服务 ,这是很多 RPC 所追求的,简化了我们代码的书写, Maton 也是最新版本才开始支持的;
API 配置是 Dubbo 的 API 配置仅用于 OpenAPI, ESB, Test, Mock 等系统集成 , API 属性与配置项一对一。
Motan:我支持 Motan 协议,使用tcp 长连接模式,基于 netty通信。
Dubbo:我支持 Dubbo 协议、 Rmi 协议、 Hessian 协议、 HTTP 协议、 WebService 协议、Dubbo Thrift 协议、Memcached 协议!
gRPC:我,我支持 HTTP/2.0 协议,基于 Netty4.1.3 通信。
Motan:我默认使用对 java 更友好的 hessian2 进行序列化,还支持 Json 格式。
Dubbo:Dubbo 协议缺省序列化为hessian2 , rmi 协议缺省为java , http 协议缺省为 json!
gRPC:哼!说到序列化,我是独一无二的!我使用 ProtoBuf 来定义服务!
主持人:
gRPC 使用的 ProtoBuf 是由 Google 开发的一种数据序列化协议,用户使用 .proto 文件定义服务,并支持定义多种类型的方法参数。 ProtoBuf 能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。
不过,当前 gRPC 仅支持 Protobuf ,且不支持在浏览器中使用。但由于 gRPC 的设计能够支持支持多种数据格式,所以能够很容易实现对其他数据格式(如 XML 、 JSON 等)的支持。这就是我强大的 IDL 特性!
- Motan:我支持 ActiveWeight 、Random 、 RoundRobin 、LocalFirst 、 Consistent 、ConfigurableWeight 。
- Dubbo:我可以支持 Random 、RoundRobin 、ConsistentHash 、 LeastActive。
- gRPC:我,我提供了可插拔负载均衡器的机制。
主持人:这里让我来解释下每种负载均衡的模式吧 !
- ActiveWeight / LeastActive :低并发度优先, referer 的某时刻的 call 数越小优先级越高。
- Random :随机,按权重设置随机概率。在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
- RoundRobin :轮循,按公约后的权重设置轮循比率。存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
- LocalFirst :本地服务优先获取策略。
- Consistent :一致性 Hash ,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
- ConfigurableWeight :权重可配置的负载均衡策略。
Motan:我支持 Failover 失效切换、Failfast 快速失败。
Dubbo:我支持 Failover 、 Failfast 、Failsafe 、 Failback 、 Forking、 Broadcast 。
gRPC:我 具有 Failover 失效切换的容错策略。
主持人:依旧由我给大家介绍下各种容错机制 !
- Failover :失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。
- Failfast :快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- Failsafe :失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- Failback :失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking :并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
- Broadcast :广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
Motan:我支持使用 Consul 作为注册中心、使用 Zookeeper 作为注册中心、点对点直连。
Dubbo:我支持使用 Zookeeper 作为注册中心、使用 Redis 注册中心、使用 Multicast 注册中心、使用 Simple 注册中心。
gRPC:我,我只能让用户自己扩展注册中心 。
性能
Motan:在高并发、高负载场景的场景下,我的 平均 TPS 和平均响应时间依旧保持良好,我具备在高压力场景下的高可用能力。
Dubbo:Dubbo2.0 相比较 Dubbo1.0(默认使用的都是 hessian2序列化)性能均有提升。如对性能有更高要求可以使用dubbo 序列化,由其是在处理复杂对象时。 Dubbo 的设计目的是为了满足高并发小数据量的 rpc 调用,在大数据量下的性能表现并不好,建议使用 rmi 或 http 协议。
gRPC:我采用的是 ProtoBuf 序列化协议 , ProtoBuf 与其他协议的性能对比 ,非常明显 的ProtoBuf 要远远优于其他 。
Motan :通过 spring 配置方式集成,无需额外编写代码即可为服务提供分布式调用能力完全不需要任何 xml 配置文件, Dubbo 的注解配置还需要配合 xml 文件的哦 。
Dubbo :无论从支持的注册中心还是容错机制上看,都是我 Dubbo 的优势更大!
Motan : 明显支持负载均衡的模式我更多 。 我 拥有自定义动态负载均衡、跨机房流量调整等高级服务调度能力。
Dubbo :成熟度更高的我在健壮性和伸缩性上还能比你们差么?让我来一一例举。
说到健壮性 ,
监控中心宕掉不影响使用,只是丢失部分采样数据;
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务;
注册中心对等集群,任意一台宕掉后,将自动切换到另一台;注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯;
服务提供者无状态,任意一台宕掉后,不影响使用;
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复。
至于伸缩性,
注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心;
服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者。
Motan :对,我在功能上或许不是那么全面,但我更注重简单、易用以及在高并发高可用场景的使用。服务发现灵活支持多种配置管理组件,基于高并发高负载场景的高可用策略优化,良好的 SPI(Service Provider Interface) 扩展,详细的调用统计,灵活支持多种 RPC 传输协议。
Dubbo :说了这么多你能支持泛型调用么?我能! Dubbo 提供 GenericService 泛型调用接口 , 让用户的调用更加灵活 。
Motan : 我的 工程依赖只涉及核心 5 个模块,且可以按需依赖,不要的说舍弃就舍弃。看看你那么一堆堆的工程,啧啧啧 ……
gRPC : 哼 ! 本宝宝支持 服务的跨语言调用,目前所支持语言类型有 C++ 、 JAVA 、 GO 、 Python 、 Ruby 、 Node.js 、 Android 、 C# 、 PHP 、 Objective-C ,你们可以么?
Motan : 额 ,是啊,我们不能,可是你有服务发现相关机制么?
Dubbo :而且你的负载均衡和容错也太弱了 …..
gRPC : 嘤嘤嘤 ……
参考链接:
https://zhuanlan.zhihu.com/p/61364466
http://dockone.io/article/549
https://cloud.tencent.com/developer/article/1080834