正文
在单体应用上,组件通过语言级别的方法或者方法彼此调用。相比之下,基于微服务的应用是在多台机器上运行的分布式系统。每个服务实例通常是一个进程。
因此,如下图所示,服务必须用进程间通信(IPC)机制进行交互。
稍后我们会看一下特定的IPC技术,但是首先让我们探索各种设计问题。
交互方式
当为服务选择IPC机制时,首先要考虑服务是如何交互的。有很多客户端服务端交互方式。他们可以被分成两个维度。
-
一对一 - 每个客户端的请求都被每一个服务实例处理
-
一对多 - 每个请求都被多个服务实例处理
第二个维度在同步和异步交互方面:
-
同步 - 客户端期望及时响应但可能被服务器阻塞
-
异步 - 客户端在等待回复时不会阻塞自己,响应不一定立即被服务器发送
下表显示了各种交互方式。
|
一对一
|
一对多
|
同步
|
请求/回复
|
—
|
异步1
|
通知
|
发布/订阅
|
异步2
|
请求/异步回复
|
发布/异步回复
|
下面是一对一交互:
-
请求/回复 - 客户端发送请求给服务并且等待回复。客户端期待回复能及时到达。在基于线程的应用中,处理请求的线程可能被阻塞。
-
通知 - 客户端发送请求给服务,但是不期待回复。
-
请求/异步响应 - 客户端发送请求给服务,该服务异步回复。客户端不会等待并且假设回复可能不会马上到达。
下面是一对多交互:
-
发布/订阅 - 客户端发布通知信息,可能被0或者更多感兴趣的服务消费掉
-
发布/异步响应 - 客户端发布请求信息,对感兴趣的服务的响应等待一定的时间
每个服务通常使用这些交互方式的组合。对于一些服务,一种IPC机制就够了。其他服务可能需要用IPC机制的组合。下面的图表展示了当用户请求时,出租车应用中可能有的内部交互。
服务用
通知
,
请求/回复
和
发布/订阅
的组合。比如乘客的智能手机发送
通知
给旅程管理服务区请求一次打车。旅程管理服务通过用
请求/回复
调用乘客服务来验证乘客的账户是激活的。旅程管理服务就可以创建旅程并且用
发布/订阅
去通知其他服务,包括用调度器来定位可用的司机。
定义API
服务的API是服务和客户端的合同。不管你选什么IPC机制,用某种接口定义语言(IDL)定义服务的API是很重要的。用
API-first approach
去定义服务是很好的参考。你通过编写接口和审查客户端开发人员来开始服务的开发。只有在对API定义进行迭代之后才能实现服务。进行这样的设计可以增加构建符合客户需求的服务的机会。
如本文后面你将看到的,API定义的的性质取决于你选择哪种IPC机制。如果你用消息传递,API包含消息通道和消息种类。如果你用HTTP,API包含URL和请求与回复的格式。稍后我们将更详细地描述一些IDL。
不断更新的API
服务的API总是随着时间变化。在单体应用中,更改API和更新所有调用者通常是直接的。在基于微服务的应用中,即使全部的消费者都是统一应用中的其他服务,还是比较困难。你通常无法强制所有客户端去升级。此外,你可能会
逐步部署新的版本的服务
,以便新老版本的服务同时运行。有一个处理问题的策略非常重要。
处理API的更改方式取决于改变的大小。一些改变是次要的并且向后兼容以前的版本。比如,你可能给请求和相应添加属性。涉及客户端和服务端是有意义的,以便遵守鲁棒性原则。客户端用老的API应该继续与新版本服务正常工作。服务对瘸着的请求提供默认值并且客户端忽略任何额外响应属性。用IPC机制和响应消息传递格式非常重要,你可以轻松开发你的API。
但是有时候你必须对API进行主要的不兼容的更改。因为你不能强制客户端立即升级,服务必须支持老版本的API一段时间。如果你用基于HTTP的机制比如REST,一个方法是把版本号嵌入URL中。每个服务实例可能同时处理多个版本。或者你可以部署每个处理特定版本的不同实例。
处理部分失败
在前面关于API网关的文章所述,在分布式系统中存在着部分故障的风险。因为客户端和服务是独立的进程,服务可能不会及时响应客户端的请求。由于故障或者维护,服务可能会关闭。或者服务变得过载并且响应速度非常慢。
比如,考虑下这篇文章的
产品详情方案
。我们假设推荐服务没有响应。客户端的垃圾实现可能无限期地等待响应。结果不仅会导致用户体验不好,也会消耗线程这样的宝贵资源。最终运行时会用完线程并变得无法响应,就和下面的图一样。
为了防止这个问题,你必须设计你的服务去处理部分失败。
一个好的方法是使用
Netflix描述的方法
。这种策略用来处理部分错误包括:
-
网络超时 - 当等待回复时永远不会无限期等待并且总是使用超时策略。这可以确定资源不会被无限期被捆绑在一起
-
限制未完成的请求的数量 - 对于客户端可以使用特定服务的未完成请求数量强加一个上线。如果到达限制,提出额外的请求可能没有意义,这些尝试需要立即失败。
-
断路器模式
- 跟踪成功和失败的数量。如果错误率超过预定的阈值,断路器跳闸,以便以后的尝试失败。如果很多请求失败,建议服务设为不可用,发请求也是没有意义的。超时之后,客户端应该再次尝试,如果成功,关闭断路器。
-
提供备用 - 当请求失败后执行备用逻辑。比如返回缓存数据或者默认值比如空的推荐。
Netflix Hystrix
是一个实现这些或者其他模式的开源库。如果你用JVM你应该考虑用它。
IPC技术
有很多不同的IPC句式可以选择。服务能用同步请求/响应的通信机制比如基于HTTPS REST或者Thrift。或者,他们可以用异步的,基于消息的交流机制比如AMQP或STOMP。有很多不用的消息格式。服务能用易读的,基于文本的个数比如JSON和XML。或者二进制格式比如Avro或者协议缓冲区。后面我们将看一下同步的IPC机制,但是首先我们来讨论异步IPC机制。
异步,基于消息的通信
当用异步交换消息,多进程通信。客户端通过发送消息给服务。如果服务预期回复,则通过发送单独的消息给客户端实现。因为通信是异步的,客户端不会等待回复。而是假设客户端不会立即受到回复。
消息由标题(比如发件人之类的元数据)和消息体组成。消息通过通道交换。任何数量的生产者都能发送消息给通道。相似的,任何数量的消费者可从通道接受消息。有两种通道,点对点或者发布订阅。点对点频道向正在从通道读取的消费者提供一个消息。服务使用点对点通道来描述前面提到的一对一交互风格。发布订阅通道将每个消息传递给全部附加的消费者。服务用发布订阅通道给上面描述的一对多风格。
下图显示了出租车应用可能的发布订阅通道:
旅行管理服务通过将旅途创建这个消息写到
针对旅程创建这个业务的发布订阅通道
来通知感兴趣的服务比如
调度器
。调度器发现可用的司机并通过写入司机推荐这个消息给
分发乘客的发布订阅通道
的方式来通知其他服务。
有很多消息系统可以选择。你应该选择一个支持各种语言的一种。一些消息系统支持标准协议比如AMQP和STOMP。其他消息系统有专有的但是记录的协议。有很多开源消息系统去选择,包括RabbitMQ, Apache Kafka, Apache ActiveMQ,和 NSQ。在高层次上来说,他们都支持消息和通道。他们都努力做到可靠,高性能和可扩展。然而,每个消息模型的细节存在不同的差异。