前言
微服务之间如何集成应该可以说是微服务相关技术中最重要的知识之一。具体可以表示成服务之间的调用方式、通信协议、序列化协议等。
如果服务集成做得好,你的微服务可以最大程度地保持自治,你可以独立地修改和发布,相反,前期考虑得不周全的话,会给你带来灾难。
本篇是微服务设计学习系列的第二篇(继上一篇发布居然已经一个多月了,惭愧惭愧)。
欢迎阅读往期系列:
- 微服务设计学习(一)关于微服务和如何建模服务
一、对于集成技术的期望
服务集成的方式方法如此非常的多样化,我们如何在纷杂的技术中选出最适合的?
首先,我们要知道我们想要从这些集成技术中得到什么?我们期望的效果是怎么样的。这样,我们才能有目的去选型。虽然根据业务不同会有不同的考虑,但是我们总希望得到这么几点的好处:
1. 避免破坏性的修改
我们不希望当我们对某一个服务进行修改发布的时候,需要对该服务的消费者们也进行修改发布。我们希望选用的技术能够尽量避免破坏性修改的发生。比如,一个微服务在一个响应中添加了某个字段,已有的消费者不应该受到影响。
2. 服务通信的技术无关性
干我们这一行的,应该最清楚这个领域唯一不变的就是不断地变化。
新的工具、框架、语言层出不穷。比如现在的你是一个Java开发者,可能几年后你会想尝试Go语言这种更适合云原生应用的语言来构建微服务。
微服务灵活开放的特性来自于构建微服务的技术异构性,但是集成技术的选用不当,就会对微服务的具体实现技术产生限制。保证微服务之间通信方式的技术无关性是非常重要的。这就意味着,不应该选择那种对微服务的具体实现技术有限制的集成方式。
3. 服务易于消费方使用
消费方应该能很容易地使用我们的服务。如果消费方使用该服务比登天还难,那么无论该微服务多漂亮都没有任何意义。
理想情况下,消费方应该可以使用任何技术来实现,从另一方面来说,提供一个客户端库也可以简化消费方的使用。但是通常这种库与其他我们想要得到的东西不可兼得。举个例子,使用客户端库对于消费方来说很方便,但是会造成耦合的增加。
(这一点常常会前面两点冲突起来)
4. 隐藏内部实现细节
服务的内部实现细节应该最大程度地隐藏起来,不暴露出来。这也是从解耦的角度出发的。
所有倾向于暴露内部实现细节的技术都不应该被采用。
二、服务通信方式
服务之间的方式可以分为同步通信和异步通信两种方式。
如果使用同步通信,发起一个远程服务调用后,调用方会阻塞自己并等待整个操作的完成。如 RPC
,HTTP
调用。
如果使用异步通信,调用方不需要等待操作完成就可以返回,甚至可能不需要关心这个操作完成与否。如MQ
。
两种通信模式中,同步通信的协作方式是 请求/响应 的方式,客户端发起一个请求,然后等待响应。这种模式能够与同步通信模式很好地匹配,但异步通信也可以使用这种模式。我可以发起一个请求,然后注册一个回调,当服务端操作结束之后,会调用该回调。
异步通信的主要协作方式则是 基于事件 的方式,客户端不是发起请求,而是发布一个事件,然后期待其他的协作者接收到该消息然后进行处理。
基于事件的系统天生就是异步的。整个系统都很聪明,也就是说,业务逻辑并非集中存在于某个核心大脑,而是平均地分布在不同的协作者中。基于事件的协作方式耦合性很低。客户端发布一个事件,但并不需要知道谁或者什么会对此做出响应,这也意味着,你可以在不影响客户端的情况下对该事件添加新的订阅者。
延伸阅读: 微服务模式-同步与异步,里面对微服务通信架构提出了更多的探讨。上面的图片也是从中引用。
通常来讲,使用基于事件的协同工作的方式可以有效降低系统的耦合度,并且你能更加灵活地对现有系统进行修改。但是,确实需要额外的工作来对业务流程做跨服务的监控。
这里有好几个因素需要考虑。同步调用比较简单,而且很容易知道整个流程的工作是否正常。如果想要请求 / 响应风格的语义,又想避免其在耗时业务上的困境,可以采用异步请求加回调的方式。另一方面,使用异步方式有利于协同方案的实施,从而大大减少服务间的耦合,这恰恰就是我们为了能独立发布服务而追求的特性。
当然,在大部分的生产场景下,我们看到的应该都是根据业务需要或者一些其他原因,混用不同的方式。
三、RPC or REST
我们先来看看目前流行的几种集成方案:
- RPC
- REST over HTTP
- 通过消息代理(如消息中间件)进行消息传递
本节主要针对 RPC(Remote Procedure Call,远程过程调用)和 REST(REpresentational State Transfer,表述性状态转移)进行讨论。
RPC
远程调用(Remote Procedure Call,RPC)是一种网络间的通信方式,允许程序调用共享网络中其他服务器的方法或函数,而向应用开发者屏蔽远程调用的相关技术细节。RPC应该尽量做到简单、高效和透明化。客户端应用可以像调用本地对象方法一样直接调用另一台服务器上的服务端应用的对象方法。
RPC是一种技术思想而非一种规范。协议只规定了 Client 与 Server 之间的点对点调用流程,包括 stub、通信协议、RPC 消息解析等部分,在实际应用中,还需要考虑服务的高可用、负载均衡等问题。
RPC要解决的两个问题:
- 解决分布式系统中,服务之间的调用问题。
- 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
核心是通信协议、序列化和调用框架。
现今流行的RPC 的实现会帮你生成服务端和客户端的桩代码,从而让你快速开始编码。基本不用花时间,我就可以在服务之间进行内容交互了。这通常也是 RPC 的主要卖点之一:易于使用。从理论上来说,这种可以只使用普通的方法调用而忽略其他细节的做法简直是给程序员的巨大福利。
(常见的RPC框架有:Java RMI, gRPC, Thrift, Dubbo等等)
然而有一些 RPC 的实现确实存在一些问题,比如和技术的强耦合特性,如Java RMI。这一点上有些RPC框架会通过不同语言的客户端来解决调用的问题(如Dubbo),也有像gRPC一样通过一个通用的服务契约,来生成代码的方式来支持多种编程语言。
谈谈REST 和 RPC
有很多的文章和书籍会将这两者放在一起讲,惯性思维中,REST的实现方式是基于HTTP,RPC的通信协议一般是TCP,但是RPC 这种方式也可以实现REST风格的服务,只不过你需要自己定义动词( GET, POST, PUT, DELETE),RPC 也可以使用HTTP 实现,只用到 HTTP 很少的特性,而动词和 HTTP 的错误码都被忽略了。
最关键的是我认为这两者不是一个维度的概念,很早之前我看一些文章把这两个一起对比的时候,我看着总会有点迷糊。
我觉得RPC 是面向过程(面向远端服务函数调用),REST 则是面向资源。比如你提供一个查询用户的接口,用RPC风格,你可能会这样写:
/queryUser?userId=123
用Restful风格呢?
Get
/user?userId=123
再精炼一点,甚至可以这样:
Get
/user/123
RPC的思想是把本地函数映射到API,也就是说一个API对应的是一个function,我本地有一个getAllUsers,远程也能通过某种约定的协议来调用这个getAllUsers。至于这个协议是Socket、是HTTP还是别的什么并不重要;
PS:实现一个RPC不难,难的是如何实现一个高性能高可靠的RPC框架
至于REST,是一种架构风格。REST 风格包含了很多原则和限制,我觉的最核心的点就在于规范,例如使用HTTP status code做错误码,使用HTTP METHOD来表达本次请求要做的动作,使不同的业务系统都有一个“统一”的规范可以参照。
但是这个统一的规范,真的好统一吗?
我个人觉得在实际业务开发中,REST 这种设计思路是反程序员直觉的,因为在本地业务代码中仍然是一个个的函数,是动作,但表现在接口形式上则完全是资源的形式。就像面向对象的「万物皆对象」理论在习惯了纯粹面向过程开发的程序员眼里显得十分别扭一样:我的代码本来就是按顺序、循环、分支这么运行的啊,为啥非得在很明确的结构上封装一层一层的基类子类接口,还要故意给两个函数起同一个名字,调用时才选择用哪一个呢?使用「万物皆资源」的思想编写实际项目中的API接口时,最常见的问题就是「这玩意到底是个什么资源?……算了,我就直接写吧,不管什么风格了」
主要是想说明,RESTful API在很多实际项目中并不实用。因此真的做了项目,要设计接口了,你可能会发现只能用HTTP+JSON来定义接口,API的语义和设计无法严格遵守REST风格。(JSON+HTTP != REST)
这两者可以共存吗?有啊,SpringCloud 使用 Feign 组件将 REST 接口封装成 RPC 调用,这不就是吗?在这里你的SpringBoot 应用接口可以是REST风格的,只不过Feign 组件做了面向服务的包装。
我个人觉得,REST 在前后端分离 的场景会比较有价值,或者说这个服务可能会被多个客户端(网页、移动端、其他服务)集成的时候,REST的统一“规范”就显示出了他的价值。
如果只是内部服务的话,至少我了解的更多的使用的是成熟的RPC框架,因为一个成熟的RPC框架,更多的是封装了“服务发现”,"负载均衡",“熔断降级”一类面向服务的高级特性。可以这么理解,RPC框架是面向服务的更高级的封装,Java 中 SpringCloud 是这样,Dubbo也是这样。
关于REST、RPC、HTTP之间的概念,我发现网上很多的文章讲的实际上不是很清楚。
我也是看了这篇文章Debunking the Myths of RPC & REST之后,才慢慢理解了这三者之间的概念。推荐你阅读哦~
如果你觉得英文理解有难度的话,可以阅读这篇,非常的大白话:RPC-与-Restful
REST 风格包含的内容很多,强烈建议你看一看 Richardson 的成熟度模型,其中有对 REST 不同成熟度的比较。
REST 就像是讲普通话,好处就是谁都听得懂,谁都会讲。
RPC 就像是讲黑话,好处是可以更精简、更加保密、更加可定制,坏处就是要求“说”黑话的那一方(client端)也要懂,而且一旦大家都说一种黑话了,换黑话就困难了。
以上对于RPC和REST的讨论,虽然思考了很久,但终归还是一家之言,非常希望有经验有心得体会的同道们有不同意见可以指明出来。
小结
因为时间精力的原因,本文并没有过多的涉及代理消息传递这种方式的讨论。但需要在这里重提的是,REST、RPC和代理消息传递不是互斥的,它们都可以在你的微服务体系结构中一起工作。 大公司的系统(基于云)都在某种程度上有效利用了这几种方式。最重要的还是要根据业务场景合适的选择适合的技术,不同通信方式如何界定服务域边界和解析方式。
关于RPC/REST/Brokered Messaging的更深入的讨论,推荐阅读这一篇文章 REST, RPC, and Brokered Messaging
以上,是我对微服务集成技术的一些学习。希望能对你有所启发和帮助。
如果本文有帮助到你,希望能点个赞,这是对我的最大动力。