当我们找到了微服务边界,将应用分解成了多个微服务,那么接下来一个重要的问题就是这些微服务的集成了。
一个健壮的微服务集成环境需要考虑多方面要素:
- 通信协议
- 接口协议
- 服务注册、发现
- 服务版本控制
- 负载均衡
- 服务可用性
- 服务幂等性
- 服务扩展性
- 服务安全性
- 服务弹性伸缩
- 服务降级、熔断
- 调用链可追溯
头大了?别愁,其实也不是一定要面面俱到,还是那句话:“架构不是设计出来的,而是演进出来的”,所以只要满足当下的需要就够了,适合的就是最好的,过早优化往往只会带来更多痛苦。
微服务通信
此篇我想先谈谈在通信方面我的一些思考,因为通信是集成的基础,所以选择合适的通信方案至关重要。
通信方案选型
可能有人会说服务提供者完全可以提供多种通信方案,由服务的消费者根据其偏好自行选择。那么我想问问,你见过一部手机提供了多种数据线接口么?即便真的有过这种手机,你觉得它更好用更合理么?可能这个比方并不很恰当,你明白我要表达的意思就好。
同时提供多种通信方案可能带来哪些问题,我大概列了一下:
- 开发和维护成本增加
- 可能带来潜在的性能问题
- 加大了系统耦合性
所以选择合适的通信方案是要从多角度多层面来考量的,
- 首先,前篇说过好的微服务是松耦合和高内聚的,在选择通信方案时也要围绕这一核心
- 其次,服务的消费者在调用服务时不应该有大量工作要做,所以要选择一个易于使用的通信方案
- 此外,微服务由于是高度自治的,允许技术异构的,所以通信方案也不应该受到具体技术的限制,即技术无关
所以,选用什么通信方案最好?答案是没有。但是可以将上述几点作为参考,结合自己的实际情况,比如业务特点、服务规模、性能压力、团队结构等,来找寻合适的方案。所以本文中只有思考过程,没有现成的答案。
针对具体的通信方案,我们可能要进一步考虑其细分的一些方面,包括通信协议、接口风格、数据封装、同步异步等,下面说说我的理解。
通信协议
通信协议有很多,常用的比如 HTTP、TCP、UDP等,此外还有如 WebSockets、XMPP、MQTT等,这些都是网络通信,非网络通信的还包括 串口通信、USB等等。
现今除非是极为特殊的环境,一般应该都是使用网络通信协议的,所以就不说非网络通信了。要说网络通信协议就离不开TCP/IP四层模型(现已基本淘汰OSI七层模型),总不能连 HTTP 和 TCP 有什么区别都不知道就做选择吧。对四层模型我们一般只需要关注上面两层:应用层和传输层,应用层协议(HTTP、XMPP等)是建立在传输层协议(TCP、UDP)上的。
- 应用层协议:针对一些特定应用采用特定的格式进行数据传输,数据被编码成标准协议,并传送到下一层。该层的协议相比下层协议提供了更多特性,如QoS、安全性等都要比下层丰富,这些特性为我们提供了很多方便。
- 传输层协议:目前主要使用的就是TCP和UDP两个协议。前者是一个较可靠的、面向连接的传输机制,它提供一种可靠的字节流保证数据完整、无损并且按顺序到达。后者是一个无连接的数据报协议,它是一个“不可靠”协议,因为它不检查数据包是否已经到达目的地,并且不保证它们按顺序到达。相比应用层协议,该层缺少了很多特性,因此需要一些特性的时候就只能自己实现。
所以通信协议的选择最好是结合自己的应用场景所需的特性,考虑是否有较为匹配的协议,如无匹配则考察是否有一些其他工具或技术可以提供相关特性,或者自己是否能够轻松实现这些特性,总之要做到:
- 清楚了解自己在通信层面需要什么样的特性
- 对各种主要的网络通信协议的特点和特性有所掌握
接口风格
应用最广泛的接口风格当属 RPC(远程过程调用)了,从当年的基于SOAP和WSDL的WebServices,到现在的Thrift、Dubbo等,RPC 技术和工具在 SOA 生态中一直扮演着重要的角色。RPC 技术和工具帮助我们解决了网络通信和数据序列化/反序列化的问题,让我们像调用本地方法一样调用远程服务,有些 RPC 框架会帮你生成服务端和客户端桩代码(Thrift等),让你可以快速开发,有些 RPC 框架甚至提供了服务注册、发现能力(Dubbo等),便于你对服务进行水平伸缩。但是 RPC 多多少少会增加一定的耦合性,比如有时服务端的修改也会引起客户端的修改,而且修改后要求服务提供者和消费者要同时发布。
随着万维网技术的日新月异,REST 架构风格也越来越被推崇,但我发现实际上大部分人(包括我)都并没有真正理解 REST 精髓并加以应用。很多人以为使用 HTTP + JSON 并使用了 PUT、DELETE 等方法就是 RESTful 服务了,实际上这只是一种表面形式。首先 REST 并没有说一定是要用 HTTP 协议,也没有说要用 JSON 数据格式,只不过 HTTP 的特性为 REST 提供了更好的支持,所以使用 HTTP 对 REST 来说是一种较好的实践。REST 包含了非常多内容,我也尚在学习中,所以要深入了解 REST 还是建议读读 Roy Fielding 博士论文 以及 Richardson 成熟度模型。不过在使用 REST + HTTP 风格时,相比一些 RPC 方案,自己可能要做更多的工作。
异步通信也是现在的一个主流技术,因为它使得系统资源利用率更高,并有助于降低耦合性,提高系统的扩展性。一些 RPC 框架和 REST 框架也同时提供了同步和异步通信的能力,事件驱动架构(EDA)则能够借助异步技术(如消息系统)实现更加松耦合且易于扩展的应用系统。
在选择接口风格时我认为需要考虑:
- 自己当前最需要什么,如易用性、开发速度、熟悉程度、通信性能、系统扩展性、水平伸缩性、同步异步、高可用等等。
- 尽可能多的了解各种接口风格及相关技术和工具提供的能力,学习曲线,优缺点等,筛选较为满足自己需要的方案。
- 评估筛选出来的技术和工具如果应用到自己的系统中,需要做些什么样的工作,花费多大成本,带来的效果和好处是否值得,有必要的话做一些基准测试,用数据说话。
数据封装
如果你是用的是 RPC,十有八九你都不需要考虑数据如何封装,因为 RPC 框架基本都已经实现了数据的序列化/反序列化,例如 Protocol buffers、Hession 等。不同的框架在不同场景下的性能表现也各不相同,相关的文章有很多,这里不多赘述。
若你需要自己来选取一种数据封装方案,比如使用 REST + HTTP 时,面对 JSON、XML 或 二进制 等格式该如何选择?我认为可以从以下几方面思考:
- 技术无关性。JSON 和 XML 这类格式天生是技术无关的,如果使用 二进制 格式则要避免使用特定技术,比如Java自带的序列化/反序列化。
- 数据大小。相比 JSON,XML 可能会略大一点,如果传输的数据量较大,那么不同数据格式封装的结果可能会相差很多。
- 性能。不同数据格式各自都有多种序列化/反序列化工具,它们的实现方式使得性能也存在很大差别,有些对较小的数据量处理很高效,对较大数据量则性能低下,有些则恰恰相反。
- 使用场景。如果有的客户端可能不对数据反序列化,而是直接取用其中的某个特定部分数据,则使用 XML、JSON 这类格式会更方便,实际上消费方如何使用数据是它自己的事情,服务提供者理应提供这种便利。
同步异步
何时用同步何时用异步?我觉得这个问题现今已经没有多少意义了。因为同步能做到的,异步也都能做到,例如在异步调用中注册一个回调,或者使用 Future + Listener 这种模型,而相比异步,同步会产生大量阻塞,带来延迟,对系统资源的利用率低下。
那么为什么很多系统仍然在使用同步?因为简单。同步的开发能够很快上手,需要做的工作也相对较少,而异步技术对很多开发者尚存在一定的壁垒,如果完全自己来实现异步要做的工作也不少。
前篇提到了领域驱动设计(DDD),我们可以使用其中的思想来将单块应用分解为多个微服务,在 DDD 中较为推崇的一种协作方式是事件驱动架构(EDA),在微服务中这种架构更是如鱼得水。基于事件的协作能让微服务更加松耦合,业务完全内聚在各个微服务中,不需要有一个集中控制业务逻辑的中枢,从而大大提高系统的可扩展性。
所以我的想法是,
- 如果开发团队了解EDA那么尽可能使用EDA。
- 如果开发团队不了解EDA但是熟悉异步,那么结合业务的实际情况合理使用异步技术。
- 如果开发团队即不了解EDA也不熟悉异步,那么可以用同步快速开发,然后逐步建立异步的知识体系,随着业务的开展针对系统中对低延迟、低耦合要求高的地方用异步一步步进行重构。
结语
本篇主要针对微服务集成中的基础——通信——该如何选择合适方案谈了我的思路,指出了一些方面是我在做选择时会去考量的,不是十分全面,更不敢说都是正确的,有什么不妥之处欢迎和我交流。