和林格尔网站制作,创意品牌网站,网站开发项目任务,wordpress 用户点赞插件见#xff1a;http://www.dockone.io/article/549简介
在单体式应用中#xff0c;各个模块之间的调用是通过编程语言级别的方法或者函数来实现的。但是一个基于微服务的分布式应用是运行在多台机器上的。一般来说#xff0c;每个服务实例都是一个进程。因此#xff0c;如下…见http://www.dockone.io/article/549简介
在单体式应用中各个模块之间的调用是通过编程语言级别的方法或者函数来实现的。但是一个基于微服务的分布式应用是运行在多台机器上的。一般来说每个服务实例都是一个进程。因此如下图所示服务之间的交互必须通过进程间通信IPC来实现。后面我们将会详细介绍IPC技术现在我们先来看下设计相关的问题。交互模式
当为某一个服务选择IPC时首先需要考虑服务之间如何交互。客户端和服务器之间有很多的交互模式我们可以从两个维度进行归类。第一个维度是一对一还是一对多•
一对一
每个客户端请求有一个服务实例来响应。•
一对多
每个客户端请求有多个服务实例来响应第二个维度是这些交互式同步还是异步• 同步模式客户端请求需要服务端即时响应甚至可能由于等待而阻塞。• 异步模式客户端请求不会阻塞进程服务端的响应可以是非即时的。下表显示了不同交互模式一对一的交互模式有以下几种方式• 请求/响应一个客户端向服务器端发起请求等待响应。客户端期望此响应即时到达。在一个基于线程的应用中等待过程可能造成线程阻塞。• 通知也就是常说的单向请求一个客户端请求发送到服务端但是并不期望服务端响应。• 请求/异步响应客户端发送请求到服务端服务端异步响应请求。客户端不会阻塞而且被设计成默认响应不会立刻到达。一对多的交互模式有以下几种方式• 发布/ 订阅模式客户端发布通知消息被零个或者多个感兴趣的服务消费。• 发布/异步响应模式客户端发布请求消息然后等待从感兴趣服务发回的响应。每个服务都是以上这些模式的组合对某些服务一个IPC机制就足够了而对另外一些服务则需要多种IPC机制组合。下图展示了在一个打车服务请求中服务之间是如何通信的。上图中的服务通信使用了通知、请求/响应、发布/订阅等方式。例如乘客通过移动端给『行程管理服务』发送通知希望申请一次出租服务。『行程管理服务』发送请求/响应消息给『乘客服务』以确认乘客账号是有效的。紧接着创建此次行程并用发布/订阅交互模式通知其他服务包括定位可用司机的调度服务。现在我们了解了交互模式接下来我们一起来看看如何定义API。定义API
API是服务端和客户端之间的契约。不管选择了什么样的IPC机制重要的是使用某种交互式定义语言IDL来精确定义一个服务的API。甚至有一些关于使用
API first的方法
API-first approach来定义服务的很好的理由。在开发之前你需要先定义服务的接口并与客户端开发者详细讨论确认。这样的讨论和设计会大幅度提到API的可用度以及满意度。在本文后半部分你将会看到API定义实质上依赖于选择哪种IPC。如果使用消息机制API则由消息频道channel和消息类型构成如果选择使用HTTP机制API则由URL和请求、响应格式构成。后面将会详细描述IDL。API的演化
服务端API会不断变化。在一个单体式应用中经常会直接修改API然后更新给所有的调用者。而在基于微服务架构应用中这很困难即使只有一个服务使用这个API不可能强迫用户跟服务端保持同步更新。另外开发者可能会尝试性的
部署新版本的服务
这个时候新旧服务就会同事运行。你需要知道如何处理这些问题。你如何处理API变化这依赖于这些变化有多大。某些改变是微小的并且可以和之前版本兼容。比如你可能只是为某个请求和响应添加了一个属性。设计客户端和服务端时候应该遵循
健壮性原理
这很重要。客户端使用旧版API应该也能和新版本一起工作。服务端仍然提供默认响应值客户端忽略此版本不需要的响应。使用IPC机制和消息格式对于API演化很有帮助。但是有时候API需要进行大规模的改动并且可能与之前版本不兼容。因为你不可能强制让所有的客户端立即升级所以支持老版本客户端的服务还需要再运行一段时间。如果你正在使用基于基于HTTP机制的IPC例如REST一种解决方案是把版本号嵌入到URL中。每个服务都可能同时处理多个版本的API。或者你可以部署多个实例每个实例负责处理一个版本的请求。处理部分失败
在上一篇
关于API gateway
的文章中我们了解到分布式系统中部分失败是普遍存在的问题。因为客户端和服务端是都是独立的进程一个服务端有可能因为故障或者维护而停止服务或者此服务因为过载停止或者反应很慢。考虑这篇文章中描述的
部分失败的场景
。假设推荐服务无法响应请求那客户端就会由于等待响应而阻塞这不仅会给客户带来很差的体验而且在很多应用中还会占用很多资源比如线程以至于到最后由于等待响应被阻塞的客户端越来越多线程资源被耗费完了。如下图所示为了预防这种问题设计服务时候必须要考虑部分失败的问题。Netfilix提供
了一个比较好的解决方案具体的应对措施包括• 网络超时当等待响应时不要无限期的阻塞而是采用超时策略。使用超时策略可以确保资源不会无限期的占用。• 限制请求的次数可以为客户端对某特定服务的请求设置一个访问上限。如果请求已达上限就要立刻终止请求服务。•
断路器模式Circuit Breaker Pattern
记录成功和失败请求的数量。如果失效率超过一个阈值触发断路器使得后续的请求立刻失败。如果大量的请求失败就可能是这个服务不可用再发请求也无意义。在一个失效期后客户端可以再试如果成功关闭此断路器。• 提供回滚当一个请求失败后可以进行回滚逻辑。例如返回缓存数据或者一个系统默认值。Netflix Hystrix
是一个实现相关模式的开源库。如果使用JVM推荐考虑使用Hystrix。而如果使用非JVM环境你可以使用类似功能的库。IPC技术
现在有很多不同的IPC技术。服务之间的通信可以使用同步的请求/响应模式比如基于HTTP的REST或者Thrift。另外也可以选择异步的、基于消息的通信模式比如AMQP或者STOMP。除以之外还有其它的消息格式供选择比如JSON和XML它们都是可读的基于文本的消息格式。当然也还有二进制格式效率更高的比如Avro和Protocol Buffer。接下来我们将会讨论异步的IPC模式和同步的IPC模式首先来看异步的。异步的基于消息通信
当使用基于异步交换消息的进程通信方式时一个客户端通过向服务端发送消息提交请求。如果服务端需要回复则会发送另外一个独立的消息给客户端。因为通信是异步的客户端不会因为等待而阻塞相反客户端理所当然的认为响应不会立刻接收到。一个
消息
由头部元数据例如发送方和消息体构成。消息通过
channel
发送任何数量的生产者都可以发送消息到channel同样的任何数量的消费者都可以从渠道中接受数据。有两类channel
点对点
和
发布/订阅
。点对点channel会把消息准确的发送到某个从channel读取消息的消费者服务端使用点对点来实现之前提到的一对一交互模式而发布/订阅则把消息投送到所有从channel读取数据的消费者服务端使用发布/订阅channel来实现上面提到的一对多交互模式。下图展示了打车软件如何使用发布/订阅行程管理服务在发布-订阅channel内创建一个行程消息并通知调度服务有一个新的行程请求调度服务发现一个可用的司机然后向发布-订阅channel写入司机建议消息Driver Proposed message来通知其他服务。有很多消息系统可以选择最好选择一种支持多编程语言的。一些消息系统支持标准协议例如AMQP和STOMP。其他消息系统则使用独有的协议有大量开源消息系统可选比如
RabbitMQ
、
Apache Kafka
、
Apache ActiveMQ
和
NSQ
。它们都支持某种形式的消息和channel并且都是可靠的、高性能和可扩展的然而它们的消息模型完全不同。使用消息机制有很多优点•
解耦客户端和服务端
客户端只需要将消息发送到正确的channel。客户端完全不需要了解具体的服务实例更不需要一个发现机制来确定服务实例的位置。•
Message Buffering
在一个同步请求/响应协议中例如HTTP所有的客户端和服务端必须在交互期间保持可用。而在消息模式中消息broker将所有写入channel的消息按照队列方式管理直到被消费者处理。也就是说在线商店可以接受客户订单即使下单系统很慢或者不可用只要保持下单消息进入队列就好了。• 弹性客户端-服务端交互消息机制支持以上说的所有交互模式。•
直接进程间通信
基于RPC机制试图唤醒远程服务看起来跟唤醒本地服务一样。然而因为物理定律和部分失败可能性他们实际上非常不同。消息使得这些不同非常明确开发者不会出现问题。然而消息机制也有自己的缺点•
额外的操作复杂性
消息系统需要单独安装、配置和部署。消息broker代理必须高可用否则系统可靠性将会受到影响。•
实现基于请求/响应交互模式的复杂性
请求/响应交互模式需要完成额外的工作。每个请求消息必须包含一个回复渠道ID和相关ID。服务端发送一个包含相关ID的响应消息到channel中使用相关ID来将响应对应到发出请求的客户端。也许这个时候使用一个直接支持请求/响应的IPC机制会更容易些。现在我们已经了解了基于消息的IPC接下来我们来看看基于请求/响应模式的IPC。同步的基于请求/响应的IPC
当使用一个同步的基于请求/响应的IPC机制客户端向服务端发送一个请求服务端处理请求返回响应。一些客户端会由于等待服务端响应而被阻塞而另外一些客户端也可能使用异步的、基于事件驱动的客户端代码Future或者Rx Observable的封装。然而不像使用消息机制客户端需要响应及时返回。这个模式中有很多可选的协议但最常见的两个协议是REST和Thrift。首先我们来看下REST。REST现在很流行使用
RESTful
风格的API。REST是基于HTTP协议的。另外一个需要理解的比较重要的概念是REST是一个资源一般代表一个业务对象比如一个客户或者一个产品或者一组商业对象。REST使用HTTP语法协议来修改资源一般通过URL来实现。举个例子GET请求返回一个资源的简单信息响应格式通常是XML或者JSON对象格式。POST请求会创建一个新资源PUT请求更新一个资源。这里引用下REST之父Roy Fielding说的当需要一个整体的、重视模块交互可扩展性、接口概括性、组件部署独立性和减小延迟、提供安全性和封装性的系统时REST可以提供这样一组满足需求的架构。下图展示了打车软件是如何使用REST的。乘客通过移动端向行程管理服务的
/trips
资源提交了一个POST请求。行程管理服务收到请求之后会发送一个GET请求到乘客管理服务以获取乘客信息。当确认乘客信息之后紧接着会创建一个行程并向移动端返回201译者注状态码响应。很多开发者都表示他们基于HTTP的API是RESTful的。但是如同Fielding在他的
博客
中所说这些API可能并不都是RESTful的。Leonard Richardson为REST定义了一个
成熟度模型
具体包含以下4个层次摘自
IBM第一个层次Level 0的 Web 服务只是使用 HTTP 作为传输方式实际上只是远程方法调用RPC的一种具体形式。SOAP 和 XML-RPC 都属于此类。第二个层次Level 1的 Web 服务引入了资源的概念。每个资源有对应的标识符和表达。第三个层次Level 2的 Web 服务使用不同的 HTTP 方法来进行不同的操作并且使用 HTTP 状态码来表示不同的结果。如 HTTP GET 方法来获取资源HTTP DELETE 方法来删除资源。第四个层次Level 3的 Web 服务使用 HATEOAS。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。使用基于HTTP的协议有如下好处• HTTP非常简单并且大家都很熟悉。• 可以使用浏览器扩展比如
Postman
或者curl之类的命令行来测试API。• 内置支持请求/响应模式的通信。• HTTP对防火墙友好的。• 不需要中间代理简化了系统架构。不足之处包括• 只支持请求/响应模式交互。可以使用HTTP通知但是服务端必须一直发送HTTP响应才行。• 因为客户端和服务端直接通信没有代理或者buffer机制在交互期间必须都在线。• 客户端必须知道每个服务实例的URL。如之前那篇关于
API Gateway的文章
所述这也是个烦人的问题。客户端必须使用服务实例发现机制。开发者社区最近重新发现了RESTful API接口定义语言的价值。于是就有了一些RESTful风格的服务框架包括
RAML
和
Swagger
。一些IDL例如Swagger允许定义请求和响应消息的格式。其它的例如RAML需要使用另外的标识例如
JSON Schema
。对于描述APIIDL一般都有工具来定义客户端和服务端骨架接口。ThriftApache Thrift
是一个很有趣的REST的替代品。它是Facebook实现的一种高效的、支持多种编程语言的远程服务调用的框架。Thrift提供了一个C风格的IDL定义API。使用Thrift编译器可以生成客户端和服务器端代码框架。编译器可以生成多种语言的代码包括C、Java、Python、PHP、Ruby, Erlang和Node.js。Thrift接口包括一个或者多个服务。服务定义类似于一个JAVA接口是一组方法。Thrift方法可以返回响应也可以被定义为单向的。返回值的方法其实就是请求/响应类型交互模式的实现。客户端等待响应并可能抛出异常。单向方法对应于通知类型的交互模式服务端并不返回响应。Thrift支持多种消息格式JSON、二进制和压缩二进制。二进制比JSON更高效因为二进制解码更快。同样原因压缩二进制格式可以提供更高级别的压缩效率。JSON是易读的。Thrift也可以在裸TCP和HTTP中间选择裸TCP看起来比HTTP更加有效。然而HTTP对防火墙浏览器和人来说更加友好。消息格式
了解完HTTP和Thrift后我们来看下消息格式方面的问题。如果使用消息系统或者REST就可以选择消息格式。其它的IPC机制例如Thrift可能只支持部分消息格式也许只有一种。无论哪种方式我们必须使用一个跨语言的消息格式这非常重要。因为指不定哪天你会使用其它语言。有两类消息格式文本和二进制。文本格式的例子包括JSON和XML。这种格式的优点在于不仅可读而且是自描述的。在JSON中一个对象就是一组键值对。类似的在XML中属性是由名字和值构成。消费者可以从中选择感兴趣的元素而忽略其它部分。同时小幅度的格式修改可以很容器向后兼容。XML文档结构是由XML schema定义的。随着时间发展开发者社区意识到JSON也需要一个类似的机制。一个选择是使用JSON Schema要么是独立的要么是例如Swagger的IDL。基于文本的消息格式最大的缺点是消息会变得冗长特别是XML。因为消息是自描述的所以每个消息都包含属性和值。另外一个缺点是解析文本的负担过大。所以你可能需要考虑使用二进制格式。二进制的格式也有很多。如果使用的是Thrift RPC那可以使用二进制Thrift。如果选择消息格式常用的还包括
Protocol Buffers
和
Apache Avro
。它们都提供典型的IDL来定义消息架构。一个不同点在于Protocol Buffers使用的是加标记tag的字段而Avro消费者需要知道模式schema来解析消息。因此使用前者API更容易演进。这篇
博客
很好的比较了Thrift、Protocol Buffers、Avro三者的区别。总结
微服务必须使用进程间通信机制来交互。当设计服务的通信模式时你需要考虑几个问题服务如何交互每个服务如何标识API如何升级API以及如何处理部分失败。微服务架构有两类IPC机制可选异步消息机制和同步请求/响应机制。在下一篇文章中我们将会讨论微服务架构中的服务发现问题。原文链接Building Microservices: Inter-Process Communication in a Microservices Architecture翻译杨峰 校对李颖杰