美文网首页数客联盟
微服务(二 三)

微服务(二 三)

作者: 言隐 | 来源:发表于2018-02-11 13:50 被阅读81次

    使用API网关(二)

    本书关于设计,构建和部署微服务的第一章介绍了微服务架构模式。 讨论了应用这种架构的优缺点,以及抛开微服务的复杂性,为什么说微服务架构对于复杂应用来说是理想的选择。 本部分是这个系列的第二章,会讨论用API网关构建微服务。

    当你选择用一组微服务去构建你的应用的时候,你需要确定,微服务应该如何和应用的客户端打交道。 对单体应用来说意味着仅仅是一组服务端点(endpoints),他们通常是多个副本(replicated),在不同副本之间通过负载均衡服务请求。

    介绍

    让我们假设一下,你正在为购物应用开发一个移动客户端应用程序。你可能需要实现一个产品细节的页面显示特定的产品的信息。

    举例说来,图2-1 是Amazon 安卓移动应用程序,当你移动页面时看到的产品的细节信息。

    图2-1 一个购物应用

    尽管这是个智能手机应用,产品信息页展示了很多的信息。例如,不仅仅是产品的基本信息如名字,描述和价格,还展示:

    1. 购物车数量

    2. 订单历史

    3. 客户评价

    4. 低库存警告

    5. 装运选项

    6. 各种推荐,包括和本产品经常一起购买的其他产品,购买这个产品的客户也购买的其他的产品,购买本产品的用户同时查看了其他的产品。

    7. 其他的购买选择。

    当使用一个单体应用架构的时候,移动客户端通过调用一个REST服务获取这些数据,比如:

    Get api.company.com/productdetails/productid

    一个负载均衡器路由这个请求到相同应用实例中一个上去。 这个应用然后查询数据库的不同表并返回响应数据到客户端。

    相反地,当应用微服务架构时,现在在产品细节页面上的数据将会来自于多个不同的微服务实例。以下是一些可能的服务,这些服务拥有显示在产品细节页面上的数据:

    购物车服务-购物车购物数量

    订单服务-订单历史信息

    产品目录服务-基本产品信息,比如产品名称,描述和价格等

    产品评论服务-客户评论

    库存信息服务-低库存报警

    装运服务-装运选项,期限和价格,从装运服务提供的API获取。

    推荐服务-建议的其他产品。

    图 2-2 移动客户端需要微服务的映射

    我们需要确定移动客户端如何调用这些服务。 让我们来看看有哪些选项或方法。

    直接客户端到微服务通信

    理论上来说,一个客户端可以直接请求任何一个微服务。 每个微服务有一个公共的服务端点:

    https://serviceName.api.company.name

    这个服务链接会映射到微服务的负载均衡器,然后有负载均衡器选择并分配这些请求到可用的服务实例上去。 为了获取产品页面的信息,移动客户端会请求每个以上列出的微服务。

    不幸的是,这样做会有些限制和挑战。一个问题是客户端需求和每个微服务暴露的细粒度API不匹配的问题。例子中,客户端不得不分别请求各个(7个)微服务。 在更复杂的应用里,它或许不得不请求更多。比如,Amazon描述的为了渲染一个产品页面,会涉及到上百个服务。 尽管客户端可以通过本地网发起多个请求,但是如果通过公共互联网效率会很低下,这种做法绝对不是好的实践方法。 这种方法也会是客户端代码实现异常复杂。

    另一个客户端调用微服务的问题是有些服务协议可能不是web访问友好的。 一个服务可能是用Thrift 二进制RPC协议,同时另一服务或许是用AMQP消息协议。 这两种协议都不是浏览器或防火墙友好的协议。 所以这种协议最好是内部使用。一个应用应该使用Http或是Websocket,这两种是web或防火墙友好的协议(注:都是80端口,公司等一般会开放这两个端口)。

    应用这种方法的另一种缺点是微服务会不好重构。随着时间的推移我们或许会重新调整服务分区(partitioned)。比如,我们或许会合并或拆分一个服务成两个或多个服务。 如果客户端直接调用微服务,这种类似的重构(refactoring)实施起来会非常的困难。

    使用一个API网关

    通常一种更好的方法是使用API网关技术。 一个API网关是一个服务器,它是整个系统的唯一的入口。这个概念和面向对象(OOD-Object-Oriented-Design)的门面模式(Façade Pattern)相似。这个API网关封装了内部系统架构并且提供给每个客户裁剪后的API。 它或许会提供其他的功能如身份验证,监控,负载均衡,缓存,请求整形和管理,和静态请求处理等等。

    图2-3 在微服务中使用API网关

    API网关负责路由请求,组合,和协议转换。 所有的来自客户端的请求都会经过API网关。然后被路由到合适的微服务。 API网关通常将会通过调用多个微服务和聚合结果来处理一个请求。 它会为不同的协议提供翻译,比如HTTP,WebSocket和一些内部用的Web 非友好协议等等。

    API网关也提供给每个客户端一个定制的API。 它通常会提供给移动客户端一个粗粒度的API。 想想,比如,产品细节的情况。API网关能提供一个服务端点(/productdetail?productid=xxx),它使移动客户端仅调用一次就可以获取所有的产品细节信息。 这个API网关通过调用各种不同的服务(产品信息,推荐,评价等等)组合结果。

    一个很好的例子是Netflix的API网关。 Netflix的流服务被各种不同的设备使用,包括电视,各种盒子(set-top boxes),智能手机,游戏系统,平板电脑等等。起初,Netflix试着提供一组适用所有需求(one-size-fits-all)的流请求的API。然而,他们后来发现这是有问题的。 因为各种不同的设备会有他们独特的需求。现在他们使用API网关技术,它对每种设备通过适配器的方式提供API裁剪。 一个适配器(Adapter)通常是通过调用平均地6-7个后端服务来处理每个请求。 Netflix的API网关,一天大约处理10亿级别的请求。

    API网关的优缺点

    正如你所期待的,使用API网关技术有缺点也有优点。 一个主要的好处是它封装了应用的内部结构。 不是非得调用特定的服务,客户端简单地和API网关交互。 API网关为每类客户端提供一个特定的API 。 这减少了客户端和应用的服务的交互次数。 也简化了客户端代码。

    API网关也有些缺点。 它必须要有一个高可用(Highly Available)组件被开发,被部署和被管理。另一个风险是API网关变成开发的瓶颈。 开发者为了暴露每个微服务端点必须更新API网关。

    更新API网关的过程越轻量越好。否则,开发者会被迫排队等候更新网关。 尽管有这些缺点,但对现实世界的应用而言,使用API网关技术是非常合理的选择。

    实现API网关

    现在已经看到了使用API网关的动机和权衡它的优缺点。 让我们看看那些需要深思熟虑的设计问题。

    性能和扩展能力

    仅有少数的公司运营在像Netflix这样的规模,它需要处理每天10亿级别的请求。 尽管如此,对于大多数的应用来说,API网关的性能和扩展能力通常也是非常重要。 因此,构建一个在支持异步的,非阻塞I/O平台上的API网关是非常合理的。有很多不同的技术可以被用于实现这种可扩充的API网关。 在JVM上,你可以使用基于NIO的框架如Netty,Vertx,SpringReactor 或是JBoss Undertow. 一个流行的非JVM选择是Node.js ,它是构建在Chrome JavaScript引擎上的平台的。还有一个选择是NGINX Plus。

    NGINX Plus提供一个成熟的,可扩充的,高性能的Web服务器和反向代理(Reverse Proxy),它易于部署,配置,编程。 NGINX Plus可以管理认证,访问控制,负载均衡请求,缓存响应和提供application-aware的健康检查和监控。

    使用反应式编程模型(Reactive Programming Model)

    API网关通过简单地路由请求到合适的后端服务来处理一些请求。 它通过调用多个后端服务和聚集结果处理其他请求。 一些请求,比如产品细节请求,这些后端服务相互独立。 为了最小化响应时间,API网关应当并行地(Concurrently)执行这些独立的请求。

    然而,有时候,请求之间是有依赖的。API网关或许首先需要通过认证服务校验请求,然后路由给后端服务。 相似地,为了获取在客户愿望单里关于产品的信息,API网关必须首先获取用户的信息,然后获取每个产品的信息。 另一个有趣的组合的例子是Netflix Video Grid。

    用传统异步回调方法写API组合的代码会很快把你引导到回调函数的地狱中。 这种代码一般会纠结在一起,很难被理解,并且容易错误。一个更好的方法是采用声明式,反应式方法写代码。反应式抽象体(reactive abstractions)包括Scala中的Future,Java中的CompletableFuture和Javascript的Promise。 也有被微软发起和开发的反应式扩展库 Reactive Extensions 。 Netflix创建了在JVM中的RxJava,主要用于他们的API网关。 JavaScript有RxJS,它能运行在浏览器和Node.js环境中。 采用反应式方法将使你写简单但高效的网关代码。

    服务调用

    一个基于微服务的应用是一个分布式系统,它必须使用一个进程内部通讯机制。 有两种类型的进程间通讯机制。一种是使用异步的,基于消息机制方法。 一些实现采用消息代理器(Message Broker),比如JMS或是AMQP。其他的,像ZeroMQ,是无代理的并且服务是直接通讯的。

    其他风格的进程间通讯方法是同步机制比如HTTP或许Thrift。 一个系统通常同时采用这两种方法,异步的,或同步的机制。 它或许甚至采用每种风格的多个实现。API网关将需要支持多个不同的通讯机制。

    服务发现

    API网关需要知道每个服务与其通讯的地址(IP地址和端口)。在传统的应用中,你或许可以硬编码这些地址。但是在现代,基于云的微服务应用中,发现需要的地址并不那么简单。

    基础架构服务(Infrastructure Service),比如,消息代理器,通常会有一个静态的地址,它可以通过操作系统的环境变量指定。 然而,确定一个应用服务的地址并不那么容易。

    应用服务有一些动态分配的地址。 也有些服务的实例地址因为扩容和更新会动态更改。因此,API网关,像系统中的其他服务客户端一样需要采用系统的服务发现机制:要么是服务器端服务发现(Server-side discovery)或是客户端服务发现(Client-Side Discovery)。 第四章会详细地描述服务发现。现在,值得注意的是 ,如果系统采用客户端服务发现,那么API网关必须能查询服务注册表(Service Registry),服务注册表是个数据库,它记录了所有服务实例和他们的地址。

    处理局部故障

    当实现API网关时,另外一个你需要处理的问题是局部故障。 这种情况会发生在分布式系统中是指,无论何时一个服务调用其他服务时,这个服务要么反应很慢,要么就直接不可用。 API网关绝不应该无限期阻塞等待下游服务的响应。然而,如何处理故障依赖于特定的失败服务场景。 比如,如果推荐服务,在产品细节展示时,没有响应。这个API网关应当返回其他的产品细节信息。 因为这时的其他产品信息对用户依然有用。 这个推荐信息要么被置空,要么用其他信息替换掉。 比如,硬编码的前10个推荐列表。 然而,如果产品服务信息不可用,那么API网关应该返回错误信息给客户端。

    如果缓存数据可用,API网关也可以返回缓存的数据。 比如,因为产品价格频繁改变,如果价格服务不可用,这个API网关应当返回缓存的价格数据。数据可以被API网关缓存,或是它被保存在外部缓存系统中,比如,Redis或是Memcached。 通过返回要么是默认数据,要么是缓存数据,API网关确保系统故障尽量不影响用户体验。

    Netflix Hystrix是一个非常有用的库,用于代码中调用远程的服务。 Hystrix实现了超时机制。它实现了一种叫做“短路器模式”(Circuit Breaker Pattern)能避免等待没有响应的服务。 如果错误率超过了指定的门槛,Hystrix触发短路器并且所有的请求在一定时间内立即失败,比如从缓存中读数据或是返回一个默认值。 如果你使用JVM,你应当考虑使用Hystrix。 如果你应用运行在非JVM环境上,你应该用类似的库。

    总结

    对多数的基于微服务的应用,实现API网关是合理的选择,它作为唯一访问系统的入口。 API网关是负责路由请求,组合和协议转换。它为每个应用的客户端提供了一个定制的API。 API网关也能通过返回缓存的数据,或默认的数据屏蔽后端服务的故障。 下一章,我们将会看服务间通讯。

    进程间通讯 (三)

    这是本系列的第三章,关于用微服务架构构建应用。 第一章介绍了微服务架构模式,比较了它和单体架构模式的区别,并且讨论了使用它的优点和缺点。第二章描述了一个应用的客户端如何通过中间媒介API网关和微服务通讯。 本章,我们来看看系统内部的服务间如何通信。

    介绍

    在一个单体式应用中,组件间通讯通过语言级方法或功能函数调用实现。 基于微服务架构应用则不同,它是运行在多个机子上的分布式系统。每个服务通常是一个进程。

    因此,如图3-1,服务间通讯必定是采用进程间通讯IPC(Inter-process communication)机制。

    以后我们会研究特定的IPC技术,但是现在,让我们探索一下各种设计问题。

    图 3-1 微服务采用进程间通讯交互

    交互形式

    当为一个服务选择IPC机制时,非常有益的事情是先想想服务是如何交互的。 有很多的客户-服务交互方式。他们可以被按两个维度分类。 第一类是是否交互是一对一或是一对多的:

    一对一 : 每一个客户端请求是精确地被一个服务实例处理。

    一对多:一个客户端请求被多个服务实例处理。

    第二个维度是交互是异步的还是同步的:

    同步交互:客户端期望服务能及时响应请求,并且可能会等待中阻塞。

    异步交互:客户等待服务响应时不阻塞,并且如果有相应,响应也不必立即发送出去。

    下表3-1,展示了各种交互形式。

    表3-1 进程间通讯方式

    有各种类型的一对一交互,同步的(请求/响应)和异步的(通知和请求/异步响应):

    请求/ 响应(Request/Response)-一个客户端发出请求并且等待一个响应。这个客户端期待响应能及时返回(timely fashion)。 在一个基于线程的应用中,线程发起请求或许会在等待中阻塞。

    通知(Notification)(例如:单向请求)-客户端发送一个请求给服务但是不期望会有应答。

    请求/异步响应(Request/async response)-一个客户端发送一个请求给服务,服务的应答是异步的。客户端在等待时不阻断。客户端是以应对”响应不能及时到达“这种假设方式设计的。

    以下是各种类型的一对多的交互方式,两种都是异步的:

    发布/订阅(Publish/Subscribe) –一个客户端发布一个通知消息,这个消息被零个或多个对此消息感兴趣的消费。

    发布/异步响应(Publish/async response)-一个客户端发布一个请求消息,然后等待一段时间,感兴趣的服务会发出响应。

    每个服务通常会组合使用这些交互方式。 对一些服务,单个IPC机制时足够的,另一些服务或许需要采用组合的IPC机制。

    图3-2显示了出租车呼叫程序如何和请求一段行程的顾客交互。

    图3-2 在服务交互中采用多种进程间通讯机制

    服务采用通知,请求/ 响应,和发布/订阅机制。 比如,乘客的智能手机发出一个通知给行程管理服务要求接车。行程管理服务(Trip Management Service)通过调用乘客管理服务(Passenger Management Service)去验证乘客是否是激活状态。 行程管理服务然后创建行程,用发布/订阅机制通知其他服务,包括分配器(Dispatcher),它定位一个可用的司机。

    看完了交互形式,让我们看看如何定义API。

    定义API

    一个服务API是服务和其客户端的协议合同。 不管你的IPC机制的选择是啥,很重要的事是采用一些接口定义语言(Interface Definition Language)准确地定义接口。 有些很好关于API-first 定义API的讨论。 你通过先写API接口定义并且一起和客户端开发者讨论这些定义,然后才开始正式开发API。 经过几轮的API定义的讨论,然后去实现这个服务。这样设计优先(Design Up Front)的方式增加了构建服务并满足客户需要的机会。

    正如你将要在本书后面看到的,API定义的本质依赖于你使用哪种IPC机制。 如果你使用消息机制,API就应该包括消息的通道和消息类型。 如果你采用HTTP,API定义就要包含URL和请求响应格式。 以后我们会详细描述IDL。

    API演进

    一个服务的API会随时间持续改变。 在一个单体应用中,更改API定义通常是很直观的,也就是更改API定义并且更新所有的API调用者代码。在基于微服务的应用中,即使你的API的消费者调用的服务都在同一个应用中,这种更改也是很困难。通常来说,你不能强制所有的客户端和服务同步更新(lockstep)。同时,你或许会增量部署服务的新版本,比如 一个服务的新版和旧版同时运行。 这种情况下最好有个策略来处理这些问题。

    如何处理一个API的更新依赖于更改的大小。 一些更改很小和上一个版本是兼容的。 比如,你或许增加一个属性到请求或是响应中。 设计客户端和服务端要遵循鲁棒原则(robustness principle)。 使用老版本API的客户端应该继续能和新版本服务工作。服务提供缺失属性的默认值,客户端忽略任何额外的响应属性。 选择一个IPC机制和消息格式使你可以轻易地演进API是很重要的设计技巧。

    然而,有的时候,你必须对API做一些主要的,不兼容的更改。 由于你不能强制客户端立即更新,在一定时间内,一个服务必须要支持老版本的API。 如果你正在使用基于http的机制比如REST,一种方法是其服务URL中嵌入版本号。每个服务实例或许同时处理多个版本的服务。 另外一个选择是你可以部署不同的实例处理一个特定的版本服务。

    处理局部故障

    正如在第二章提到的关于API网关,在一个分布式系统中,会有经常存在的局部故障的风险。 因为客户端和服务端是分离的进程,一个服务或许不能及时响应客户端的请求。 一个服务或是因为局部错误或运维而挂机。或是一个服务过载而对请求响应出奇的慢。

    比如,考虑一下,第二章产品细节的场景。让我们想象推荐服务没有响应。客户端一个幼稚的实现是为等待响应会永久阻塞。这种做法不仅是用户体验很差,而且在很多应用中它会消耗掉宝贵的资源比如线程。 最终运行时态会消耗掉所有的线程而使系统变得没有响应。如图3-3

    图3-3 线程因无响应而阻塞

    为了防止这个问题出现,设计服务时,要慎重考虑处理局部故障。 一个好的途径可以参考是Netflx的设计策略。这些处理局部故障的策略包括:

    - 网络超时-绝不永久阻塞,并且总是在等待响应时采用超时机制。 采用超时机制确保了资源绝不会被永久绑定。

    - 限制特殊请求的数量-为客户端能访问特殊服务请求的数量设置上限,如果达到上限,或许再继续请求是没有意义的,对这些尝试的请求需要立即返回失败。

    - 断路器模式(Circuit Breaker Pattern)-记录成功和失败的次数,如果失败率超过配置的门槛值,触发断路器以便于使将来的尝试立即返回失败。 如果失败的次数很多,意味着服务不可用并且继续发送请求是无意义的。请求超时过后,客户端应该重试,并且如果成功,关闭断路器设置。

    - 回退-当请求失败时,执行回退逻辑。例如返回缓存的数据或是默认值,比如 置空推荐集。

    Netflix Hystrix 是一个开源的库,它实现了这些和那些模式。如果你在用JVM,你应该考虑采用Hystrix 。 如果你在用非JVM环境,你应该采用类似功能的库。

    IPC技术

    有很多不同的IPC技术可以选择。 服务可以是同步基于请求/响应通信机制,比如基于HTTP的REST或是Thrift。 另外的选择,他们可以用异步的,基于消息的通信机制,比如AMQP或是STOMP。

    消息的格式也有多挣选择。 服务可以用可读的,基于文本的格式,比如JSON或是XML。他们也可以采用二进制格式(更高效)比如Avro或是Protocol Buffer。 以后我们将会研究同步的IPC机制,但是首先让我们讨论异步IPC机制。

    异步的,基于消息的通信

    当采用消息机制,进程通过异步交换消息的方式通信。 一个客户端通过发送一个消息请求服务。如果这个服务被期待应答,它通过发送另外一个消息给客户端。 因为通信是异步的,客户端不会因为等待应答而阻塞。相反地,这个客户端被设计时会假设不会立即收到应答。

    一个消息包括头部信息(元数据,比如发送者信息)和一个消息体。消息通过信道(Channels)相互通信。 任意数量的生产者会发生消息到信道。 相似地,任何数量的消费者会从信道收到消息。 有两种类型的信道,点到点(Point-to-Point)和发布/订阅(Publish-subscribe)。

    - 点到点信道传递一个消息到一个消费者。服务采用点到点信道实现之前提到的一对一交互方式。

    - 一个发布-订阅信道传递每个消息到多个订阅的消费者。服务采用发布-订阅信道实现之前提到的一对多交互方式。

    图3-4 展示了出租车呼叫应用或许采用发布-订阅信道。

    图 3-4 在出车呼叫应用中采用发布-订阅信道

    行程管理服务通知感兴趣(subscribed)的服务,比如分配器(Dispatcher),关于一个新的行程,它通过写一个创建行程的消息到发布-订阅信道。 分配器通过写一个司机提议消息到一个发布-订阅信道找个一个可用的司机并且通知其他服务。

    有很多消息系统可以选择。 你应该选择一个支持很多语言的消息系统。

    一些消息系统支持标准的协议比如AMQP和STOMP。 其他一些消息系统有专用和文档的协议(proprietary and documented protocols)。

    有非常多的开源消息系统可以选择,包括RabbitMQ(http://www.rabbitmq.com),Apache

    KafkaApache ActiveMQNSQ。 更高的抽象级别,他们都以一定的形式支持消息和信道。 他们都力争可靠,高效和可扩容。然而,他们的每个代理器消息模型在细节上都有很大的区别。

    采用消息机制有很多的优点:

    -解耦服务和其客户端--一个客户端简单地通过发送一个消息到合适的信道发起请求。客户端完全意识不到服务实例的存在。它不需要任何的发现机制去确定服务实例地址。

    -消息缓存-同步的请求/响应协议,比如HTTP,客户端和服务端双方必须在消息交换时可用。相反地,一个消息代理器排队把消息写入信道直到消费者能处理他们。 比如说,这意味着,在线商店可以从消费者哪里接收消息,即使是订单完成系统缓慢或不可用。 订单仅仅是排队等候处理。

    - 灵活的客户端-服务交互-消息支持所有的前面提到的交互方式。

    -明确进程间通讯-基于RPC的通讯机制尝试使调用远程服务看起来像调用本地服务一样。然而,因为物理原因和可能的局部故障,事实上,他们非常的不同。消息机制使这些区别更明确,以此避免开发者被虚假的安全性麻痹。

    当然了,消息机制也有其缺点:

    -额外的运维复杂性-消息系统是另一个必须安装,配置,运维的系统组件,另外消息代理器必须高可用(HA-Highly Available),否则话,系统可靠性会受影响。

    -实现基于请求/响应的交互复杂性-请求/响应风格的交互必须要一定的工作量去实现。每个请求消息必须包含一个应答信道和一个相关的ID。服务响应消息内必须包含这个ID和应答信道。 客户端使用这个相关的ID去匹配请求响应。 通常更容易些,使用直接支持请求/响应的IPC机制。

    我们已经看到了使用基于消息的IPC机制。让我们看一下基于请求/响应的IPC机制。

    同步的,请求/响应IPC

    当使用一个同步的,基于请求/响应的IPC机制,一个客户端发送一个请求给服务。这个服务处理请求然后返回一个响应消息。

    在很多的客户端,发送请求的线程阻塞以等待一个响应。其他的客户端或许会用异步的,事件驱动的客户端代码,这些代码或许是用Future或是Rx Observable。 然而,不像采用消息,客户端期待响应将会及时返回。

    有很多的协议可以选择。 两个比较流行的协议是REST和Thrift。 让我们先来看REST。

    REST

    现在开发采用RESTful的方式开发API是很时尚的。 REST是一种IPC机制,它总是采用HTTP。

    REST的一个关键的概念是资源(Resource),它通常是指一个商业上的对象(Object)比如,客户(Customer)或产品,或是这些商业对象的集合。 REST采用HTTP的标准动词(Verbs)来操作资源,它们可以通过URL被引用。比如 用Get 请求返回一个资源的表述,这个返回的资源或许是xml,文档或是JSON对象。 POST请求创建一个新的资源,PUT请求更新一个资源。

    REST创建者Roy Fielding ,是这样描述REST的:

    “REST 提供了一组结构化的限制,当作为整体应用时,强调组件交互的库容能力,接口的泛化程度,组件的独立部署和中间组件的减少交互延迟,强化安全性和封装陈旧系统。(REST provides aset of architectural constraints that, when applied as a whole, emphasizesscalability of component interactions, generality of interfaces, independentdeployment of components, and intermediary components to reduce interactionlatency, enforce security, and encapsulate legacy systems.)” --Roy Fielding ”基于网络软件架构的架构风格和设计

    图3-5 展示了出租车呼叫系统应用或许可以采用REST的一种方法

    图3-5 出租车呼叫系统采用REST交互方式

    乘客的智能手机通过POST发送一个请求到行程管理服务/trips,这个服务通过GET请求从乘客管理服务获取关于乘客的信息,验证乘客有权创建行程后,行程管理服务创建一个行程并返回201的响应给智能手机。

    许多开发者声称基于HTTP的API是RESTful。 然而,按照Fielding在这个博客中的描述,实际上,并非如此。

    Leonard Richardson (作者强调,和他没有关系 ,注:和作者第二个名字相同 Chris RichardsonJ )定义了成熟度模型(Maturity Model for REST) ,它包括下面这些级别:

    - Level 0 –满足级别0 REST API客户端通过HTTP POST 请求它的专有的(Sole)服务端点(Endpoint)。 每个请求指定执行的动作,动作的目标(比如,商业对象)和任何的参数。

    - Level 1-满足级别1 REST API支持资源的概念,在资源上执行一个动作,一个客户端用POST请求指定一个执行的动作和任意的参数。

    - Level 2 –满足级别2REST API采用HTTP动词(Verbs)执行动作,GET是获取,POST是创建,PUT是更新。如果有,用查询参数(Query Parameters)和请求体(body)指定动作参数。 这样可以使服务利用Web基础架构,比如对GET请求缓存。

    - Level 3- 级别3 REST API的设计基于HATEOAS(Hypertext As The Engine Of Application State)命名规则。基本的概念是通过GET返回的资源包含可以执行的动作链接(Links)。比如,一个客户端能用从GET获取到的资源中的一个链接取消一个订单。一个HATEOAS的好处是硬编码URL到代码中。另外一个好处是,因为一个资源中包含可以执行的链接,客户端不必猜测对这个资源可以做哪些操作。

    用基于HTTP协议有很多的优点:

    - HTTP简单而熟悉。

    - 在浏览器中,你可以测试HTTP AP ,比如用Postman,或是用命令工具curl (假设用JSON或一些其他文本格式)。

    - 它直接支持请求/响应风格的通讯。

    - HTTP是防火墙有好点协议。

    - 它不需要中间代理器,简化了系统架构的设计。

    当然了,用HTTP也有其缺点:

    - HTTP仅仅支持请求/响应方式的交互。你能用HTTP作为通知(Notification),但是服务器必须总是发送一个HTTP响应。

    - 因为客户端和服务直接通讯(没有中介缓存消息),他们必须在交互消息时可用。

    - 客户端必须知道每个服务实例的地址,正如第二章描述的关于API网关那样,在现代应用中,这不是一个很小的问题。 客户端必须采用服务发现机制定位服务实例。

    开发社区最近重新发现了对RESTful API的接口定义语言的价值。有些选项包括RAMLSwagger工具。 一些接口定义语言IDLs(Interface Definition Language),比如Swagger,允许你去定义请求和响应的格式。 其他的,比如RAML,需要你使用另外的规范如JSON 模式(JSON Schema)。 关于描述API,IDLs通常会有些工具帮助生成客户端Stubs和服务器端结构代码。

    Thrift

    Apache Thrift 是有趣的REST替代工具。它是一个写跨语言RPC客户端和服务器端工具。Thrift提供了C语言风格的接口定义语言,用于定义你的API。你使用Thrift编译器生成客户端和服务器端结构代码。编译器可以产生很多语言的代码,包括C++,Java,Python,PHP,Ruby,Erlang和Node.js.

    一个Thrift结构包括一个或多个服务。一个服务定义类似于Java接口定义。它是请类型化方法的集合。

    Thrift方法既能返回(或空)一个值又能,如果他们被定义为单项,不返回值。返回值的方法实现了请求/响应风格的交互;客户端等待响应消息,或是抛出一个异常。单项方法类似于通知风格的交互; 服务器不用返回响应。

    Thrift支持很多的消息格式: JSON,二进制码或是压缩的二进制码。二进制码比JSON高效因为它更快解码。并且,正如名字暗示的,压缩的二进制码是空间高效(Space-efficient)格式。JSON,当然是,可读且浏览器友好的格式(Human-and-browser-friendly)。Thrift也给你选择选择传输协议的选项,协议包括纯TCP和HTTP。TCP可能比HTTP更高效,但是,HTTP是防火墙友好,浏览器友好和可读的协议。

    消息格式

    现在我们研究过了HTTP和Thrift,让我们来消息格式的一些问题。 如果你在使用消息系统或是REST,你要选择你的消息格式。 其他的IPC机制比如Thrift或许仅支持少量的消息格式,或是仅仅一种。 在任何一种选择下,支持跨语言的消息格式是很重要的。即使你现在正在用一种语言写微服务程序,在将来,你可能会其他的语言。

    主要有两种消息格式:文本和二进制码。文本格式的例子有JSON和XML。这些格式的优点是,它不仅可读,他们也是自我描述的。 在JSON中,一个对象的属性是用名值对(name-value pairs)的集合来表示的。这使一个消息的消费者可以选择自己感兴趣的而忽略其他的。 结果是,很小的消息格式的更改能向后兼容。

    XML文档的结构是通过XML 模式(XML Schema)。随着时间的推移,开发社区意识到JSON也需要类似的机制。一种选项是使用JSON模式(JSON Schema),或是独立使用,或是作为IDL的一部分使用比如Swagger。

    基于文本的消息格式的缺点是消息倾向于冗余。特别是XML。因为消息是自我描述的,每个消息包括属性的名字和他们的值。另外的缺点是分析文本的成本。由此,你或许想考虑使用二进制格式。

    有些二进制格式可选。如果你使用ThriftRPC,你能使用二进制码Thrift。 如果你需要选择消息格式,流行的选项包括Protocol BufferApache Avro 。这两种各种都提供了定义你的消息格式结构类型化的IDL。然而,一种区别,是Protocol Buffer采用加标签的字段(Field),而Avro的消费者需要知道其模式(Schema)用于解释消息。 这样的话,API演进采用Protocol

    Buffer比采用Avro容易。 这个博客有非常好的对Thrift,Protocol Buffer 和Avro的对比分析.

    总结

    微服务必须采用进程内通讯机制。 当设计你的服务如何通讯式,你需要考虑各种不同的问题:服务如何交互,如何为每个服务指定API,如何演进API,还有如何处理局部故障。有两种类型微服务可以使用的RPC机制:异步消息和同步的请求/响应。为了通讯,一个服务必须能发现其他的,下一章,我们看一下在微服务架构下,服务发现的问题。

    翻译自“Microservice -from design to deployment"  by  Chris Richardson with Floyd Smith.

    第四章待续 (服务发现-Service discovery)。。。

    相关文章

      网友评论

        本文标题:微服务(二 三)

        本文链接:https://www.haomeiwen.com/subject/rxfxnxtx.html