(三):深入微服务架构的进程间通信

最后更新于:2022-04-01 02:47:20

> 原文出处:http://www.dockone.io/article/549 【编者的话】这是采用微服务架构创建自己应用系列第三篇文章。[第一篇](http://dockone.io/article/394)介绍了微服务架构模式,和单体式模式进行了比较,并且讨论了使用微服务架构的优缺点。[第二篇](http://dockone.io/article/482)描述了采用微服务架构应用客户端之间如何采用API Gateway方式进行通信。在这篇文章中,我们将讨论系统服务之间如何通信。 [TOC=2] ## 简介 在单体式应用中,各个模块之间的调用是通过编程语言级别的方法或者函数来实现的。但是一个基于微服务的分布式应用是运行在多台机器上的。一般来说,每个服务实例都是一个进程。因此,如下图所示,服务之间的交互必须通过进程间通信(IPC)来实现。 [![Richardson-microservices-part3-monolith-vs-microservices-1024x518.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf78f341d.png "Richardson-microservices-part3-monolith-vs-microservices-1024x518.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf78f341d.png) 后面我们将会详细介绍IPC技术,现在我们先来看下设计相关的问题。 ## 交互模式 当为某一个服务选择IPC时,首先需要考虑服务之间如何交互。客户端和服务器之间有很多的交互模式,我们可以从两个维度进行归类。第一个维度是一对一还是一对多: • 一对一:每个客户端请求有一个服务实例来响应。 • 一对多:每个客户端请求有多个服务实例来响应 第二个维度是这些交互式同步还是异步: • 同步模式:客户端请求需要服务端即时响应,甚至可能由于等待而阻塞。 • 异步模式:客户端请求不会阻塞进程,服务端的响应可以是非即时的。 下表显示了不同交互模式: [![74.pic_.jpg](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf88e8fb5.jpg "74.pic_.jpg")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf88e8fb5.jpg) 一对一的交互模式有以下几种方式: • 请求/响应:一个客户端向服务器端发起请求,等待响应。客户端期望此响应即时到达。在一个基于线程的应用中,等待过程可能造成线程阻塞。 • 通知(也就是常说的单向请求):一个客户端请求发送到服务端,但是并不期望服务端响应。 • 请求/异步响应:客户端发送请求到服务端,服务端异步响应请求。客户端不会阻塞,而且被设计成默认响应不会立刻到达。 一对多的交互模式有以下几种方式: • 发布/ 订阅模式:客户端发布通知消息,被零个或者多个感兴趣的服务消费。 • 发布/异步响应模式:客户端发布请求消息,然后等待从感兴趣服务发回的响应。 每个服务都是以上这些模式的组合,对某些服务,一个IPC机制就足够了;而对另外一些服务则需要多种IPC机制组合。下图展示了在一个打车服务请求中服务之间是如何通信的。 [![Richardson-microservices-part3-taxi-service-1024x609.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf895ca0e.png "Richardson-microservices-part3-taxi-service-1024x609.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf895ca0e.png) 上图中的服务通信使用了通知、请求/响应、发布/订阅等方式。例如,乘客通过移动端给『行程管理服务』发送通知,希望申请一次出租服务。『行程管理服务』发送请求/响应消息给『乘客服务』以确认乘客账号是有效的。紧接着创建此次行程,并用发布/订阅交互模式通知其他服务,包括定位可用司机的调度服务。 现在我们了解了交互模式,接下来我们一起来看看如何定义API。 ## 定义API API是服务端和客户端之间的契约。不管选择了什么样的IPC机制,重要的是使用某种交互式定义语言(IDL)来精确定义一个服务的API。甚至有一些关于使用[API first的方法](http://www.programmableweb.com/news/how-to-design-great-apis-api-first-design-and-raml/how-to/2015/07/10)(API-first approach)来定义服务的很好的理由。在开发之前,你需要先定义服务的接口,并与客户端开发者详细讨论确认。这样的讨论和设计会大幅度提到API的可用度以及满意度。 在本文后半部分你将会看到,API定义实质上依赖于选择哪种IPC。如果使用消息机制,API则由消息频道(channel)和消息类型构成;如果选择使用HTTP机制,API则由URL和请求、响应格式构成。后面将会详细描述IDL。 ## API的演化 服务端API会不断变化。在一个单体式应用中经常会直接修改API,然后更新给所有的调用者。而在基于微服务架构应用中,这很困难,即使只有一个服务使用这个API,不可能强迫用户跟服务端保持同步更新。另外,开发者可能会尝试性的[部署新版本的服务](http://techblog.netflix.com/2013/08/deploying-netflix-api.html),这个时候,新旧服务就会同事运行。你需要知道如何处理这些问题。 你如何处理API变化,这依赖于这些变化有多大。某些改变是微小的,并且可以和之前版本兼容。比如,你可能只是为某个请求和响应添加了一个属性。设计客户端和服务端时候应该遵循[健壮性原理](https://en.wikipedia.org/wiki/Robustness_principle),这很重要。客户端使用旧版API应该也能和新版本一起工作。服务端仍然提供默认响应值,客户端忽略此版本不需要的响应。使用IPC机制和消息格式对于API演化很有帮助。 但是有时候,API需要进行大规模的改动,并且可能与之前版本不兼容。因为你不可能强制让所有的客户端立即升级,所以支持老版本客户端的服务还需要再运行一段时间。如果你正在使用基于基于HTTP机制的IPC,例如REST,一种解决方案是把版本号嵌入到URL中。每个服务都可能同时处理多个版本的API。或者,你可以部署多个实例,每个实例负责处理一个版本的请求。 ## 处理部分失败 在上一篇[关于API gateway](http://dockone.io/article/482)的文章中,我们了解到分布式系统中部分失败是普遍存在的问题。因为客户端和服务端是都是独立的进程,一个服务端有可能因为故障或者维护而停止服务,或者此服务因为过载停止或者反应很慢。 考虑这篇文章中描述的[部分失败的场景](http://dockone.io/article/482)。假设推荐服务无法响应请求,那客户端就会由于等待响应而阻塞,这不仅会给客户带来很差的体验,而且在很多应用中还会占用很多资源,比如线程,以至于到最后由于等待响应被阻塞的客户端越来越多,线程资源被耗费完了。如下图所示: [![Richardson-microservices-part3-threads-blocked-1024x383.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf8a0f921.png "Richardson-microservices-part3-threads-blocked-1024x383.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf8a0f921.png) 为了预防这种问题,设计服务时候必须要考虑部分失败的问题。 [Netfilix提供](http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html)了一个比较好的解决方案,具体的应对措施包括: • 网络超时:当等待响应时,不要无限期的阻塞,而是采用超时策略。使用超时策略可以确保资源不会无限期的占用。 • 限制请求的次数:可以为客户端对某特定服务的请求设置一个访问上限。如果请求已达上限,就要立刻终止请求服务。 • [断路器模式(Circuit Breaker Pattern)](http://martinfowler.com/bliki/CircuitBreaker.html):记录成功和失败请求的数量。如果失效率超过一个阈值,触发断路器使得后续的请求立刻失败。如果大量的请求失败,就可能是这个服务不可用,再发请求也无意义。在一个失效期后,客户端可以再试,如果成功,关闭此断路器。 • 提供回滚:当一个请求失败后可以进行回滚逻辑。例如,返回缓存数据或者一个系统默认值。 [Netflix Hystrix](https://github.com/Netflix/Hystrix)是一个实现相关模式的开源库。如果使用JVM,推荐考虑使用Hystrix。而如果使用非JVM环境,你可以使用类似功能的库。 ## IPC技术 现在有很多不同的IPC技术。服务之间的通信可以使用同步的请求/响应模式,比如基于HTTP的REST或者Thrift。另外,也可以选择异步的、基于消息的通信模式,比如AMQP或者STOMP。除以之外,还有其它的消息格式供选择,比如JSON和XML,它们都是可读的,基于文本的消息格式。当然,也还有二进制格式(效率更高)的,比如Avro和Protocol Buffer。接下来我们将会讨论异步的IPC模式和同步的IPC模式,首先来看异步的。 ### 异步的,基于消息通信 当使用基于异步交换消息的进程通信方式时,一个客户端通过向服务端发送消息提交请求。如果服务端需要回复,则会发送另外一个独立的消息给客户端。因为通信是异步的,客户端不会因为等待而阻塞,相反,客户端理所当然的认为响应不会立刻接收到。 一个[消息](http://www.enterpriseintegrationpatterns.com/Message.html)由头部(元数据例如发送方)和消息体构成。消息通过[channel](http://www.enterpriseintegrationpatterns.com/MessageChannel.html)发送,任何数量的生产者都可以发送消息到channel,同样的,任何数量的消费者都可以从渠道中接受数据。有两类channel,*[点对点](http://www.enterpriseintegrationpatterns.com/PointToPointChannel.html)*和*[发布/订阅](http://www.enterpriseintegrationpatterns.com/PublishSubscribeChannel.html)*。点对点channel会把消息准确的发送到某个从channel读取消息的消费者,服务端使用点对点来实现之前提到的一对一交互模式;而发布/订阅则把消息投送到所有从channel读取数据的消费者,服务端使用发布/订阅channel来实现上面提到的一对多交互模式。 下图展示了打车软件如何使用发布/订阅: [![Richardson-microservices-part3-pub-sub-channels-1024x639.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf8a71ec0.png "Richardson-microservices-part3-pub-sub-channels-1024x639.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf8a71ec0.png) 行程管理服务在发布-订阅channel内创建一个行程消息,并通知调度服务有一个新的行程请求,调度服务发现一个可用的司机然后向发布-订阅channel写入司机建议消息(Driver Proposed message)来通知其他服务。 有很多消息系统可以选择,最好选择一种支持多编程语言的。一些消息系统支持标准协议,例如AMQP和STOMP。其他消息系统则使用独有的协议,有大量开源消息系统可选,比如[RabbitMQ](https://www.rabbitmq.com/)、[Apache Kafka](http://kafka.apache.org/)、[Apache ActiveMQ](http://activemq.apache.org/)和[NSQ](https://github.com/bitly/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](https://en.wikipedia.org/wiki/Representational_state_transfer)风格的API。REST是基于HTTP协议的。另外,一个需要理解的比较重要的概念是,REST是一个资源,一般代表一个业务对象,比如一个客户或者一个产品,或者一组商业对象。REST使用HTTP语法协议来修改资源,一般通过URL来实现。举个例子,GET请求返回一个资源的简单信息,响应格式通常是XML或者JSON对象格式。POST请求会创建一个新资源,PUT请求更新一个资源。这里引用下REST之父Roy Fielding说的: > 当需要一个整体的、重视模块交互可扩展性、接口概括性、组件部署独立性和减小延迟、提供安全性和封装性的系统时,REST可以提供这样一组满足需求的架构。 下图展示了打车软件是如何使用REST的。 [![Richardson-microservices-part3-rest-1024x397.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf8acfeb4.png "Richardson-microservices-part3-rest-1024x397.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf8acfeb4.png) 乘客通过移动端向行程管理服务的`/trips`资源提交了一个POST请求。行程管理服务收到请求之后,会发送一个GET请求到乘客管理服务以获取乘客信息。当确认乘客信息之后,紧接着会创建一个行程,并向移动端返回201(译者注:状态码)响应。 很多开发者都表示他们基于HTTP的API是RESTful的。但是,如同Fielding在他的[博客](http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven)中所说,这些API可能并不都是RESTful的。Leonard Richardson为REST定义了一个[成熟度模型](http://martinfowler.com/articles/richardsonMaturityModel.html),具体包含以下4个层次(摘自[IBM](http://www.ibm.com/developerworks/cn/java/j-lo-SpringHATEOAS/)): * 第一个层次(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](https://www.getpostman.com/))或者curl之类的命令行来测试API。 • 内置支持请求/响应模式的通信。 • HTTP对防火墙友好的。 • 不需要中间代理,简化了系统架构。 不足之处包括: • 只支持请求/响应模式交互。可以使用HTTP通知,但是服务端必须一直发送HTTP响应才行。 • 因为客户端和服务端直接通信(没有代理或者buffer机制),在交互期间必须都在线。 • 客户端必须知道每个服务实例的URL。如之前那篇关于[API Gateway的文章](http://dockone.io/article/482)所述,这也是个烦人的问题。客户端必须使用服务实例发现机制。 开发者社区最近重新发现了RESTful API接口定义语言的价值。于是就有了一些RESTful风格的服务框架,包括[RAML](http://raml.org/)和[Swagger](http://swagger.io/)。一些IDL,例如Swagger允许定义请求和响应消息的格式。其它的,例如RAML,需要使用另外的标识,例如[JSON Schema](http://json-schema.org/)。对于描述API,IDL一般都有工具来定义客户端和服务端骨架接口。 Thrift [Apache Thrift](https://thrift.apache.org/)是一个很有趣的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](https://developers.google.com/protocol-buffers/docs/overview)和[Apache Avro](https://avro.apache.org/)。它们都提供典型的IDL来定义消息架构。一个不同点在于Protocol Buffers使用的是加标记(tag)的字段,而Avro消费者需要知道模式(schema)来解析消息。因此,使用前者,API更容易演进。这篇[博客](http://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html)很好的比较了Thrift、Protocol Buffers、Avro三者的区别。 ## 总结 微服务必须使用进程间通信机制来交互。当设计服务的通信模式时,你需要考虑几个问题:服务如何交互,每个服务如何标识API,如何升级API,以及如何处理部分失败。微服务架构有两类IPC机制可选,异步消息机制和同步请求/响应机制。在下一篇文章中,我们将会讨论微服务架构中的服务发现问题。 原文链接:[Building Microservices: Inter-Process Communication in a Microservices Architecture](https://www.nginx.com/blog/building-microservices-inter-process-communication/)(翻译:杨峰 校对:李颖杰)
';

(二):使用API Gateway

最后更新于:2022-04-01 02:47:18

> 原文出处:http://www.dockone.io/article/482 【编者的话】本系列的第一篇介绍了微服务架构模式。它讨论了采用微服务的优点和缺点,除了一些复杂的微服务,这种模式还是复杂应用的理想选择。 当你决定将应用作为一组微服务时,需要决定应用客户端如何与微服务交互。在单体式程序中,通常只有一组冗余的或者负载均衡的服务提供点。在微服务架构中,每一个微服务暴露一组细粒度的服务提供点。在本篇文章中,我们来看它如何影响客户端到服务端通信,同时提出一种API Gateway的方法。 [TOC=2] ## 介绍 假定你正在为在线购物应用开发一个原生手机客户端。你需要实现一个产品最终页来展示商品信息。 例如,下面的图展示了你在亚马逊Android客户端上滑动产品最终页时看到的信息。 [![01.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf357a198.png "01.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf357a198.png) 虽然这是一个智能手机应用,这个产品最终页展示了非常多的信息。例如,不仅这里有产品基本信息(名字、描述和价格),还有以下内容: * 购物车中的物品数 * 下单历史 * 用户评论 * 低库存警告 * 快递选项 * 各式各样的推荐,包括经常跟这个物品一起被购买的产品、购买该物品的其他顾客购买的产品以及购买该产品的顾客还浏览了哪些产品。 * 可选的购物选项 当采用一个单体式应用架构,一个移动客户端将会通过一个REST请求(GET api.company.com/productdetails/productId)来获取这些数据。一个负载均衡将请求分发到多个应用实例之一。应用将查询各种数据库并返回请求给客户端。 相对的,若是采用微服务架构,最终页上的数据会分布在不同的微服务上。下面列举了可能与产品最终页数据有关的一些微服务: * 购物车服务 -- 购物车中的物品数 * 下单服务 -- 下单历史 * 分类服务 -- 基本产品信息,如名字、图片和价格 * 评论服务 -- 用户评论 * 库存服务 -- 低库存警告 * 快递服务 -- 快递选项、截止时间、来自不同快递API的成本计算 * 推荐服务 -- 推荐产品 [![02.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf3fb0d9f.png "02.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf3fb0d9f.png) 我们需要决定移动客户端如何访问这些服务。请看下面这几种方式 ## 客户端到微服务直接通信 理论上说,一个客户端可以直接给多个微服务中的任何一个发起请求。每一个微服务都会有一个对外服务端([https://serviceName.api.company.nam](https://servicename.api.company.nam/)e)。这个URL可能会映射到微服务的负载均衡上,它再转发请求到具体节点上。为了搜索产品细节,移动端需要向上述微服务逐个发请求。 不幸的是,这个方案有很多困难和限制。其中一个问题是客户端的需求量与每个微服务暴露的细粒度API数量的不匹配。如图中,客户端需要7次单独请求。在更复杂的场景中,可能会需要更多次请求。例如,亚马逊的产品最终页要请求数百个微服务。虽然一个客户端可以通过LAN发起很多个请求,但是在公网上这样会很没有效率,这个问题在移动互联网上尤为突出。这个方案同时会导致客户端代码非常复杂。 另一个存在的问题是客户端直接请求微服务的协议可能并不是web友好型。一个服务可能是用Thrift的RPC协议,而另一个服务可能是用AMQP消息协议。它们都不是浏览或防火墙友好的,并且最好是内部使用。应用应该在防火墙外采用类似HTTP或者WEBSocket协议。 这个方案的另一个缺点是它很难重构微服务。随着时间的推移,我们可能需要改变系统微服务目前的切分方案。例如,我们可能需要将两个服务合并或者将一个服务拆分为多个。但是,如果客户端直接与微服务交互,那么这种重构就很难实施。 由于上述三种问题的原因,客户端直接与服务器端通信的方式很少在实际中使用。 ## 采用一个API Gateway 通常来说,一个更好的解决办法是采用API Gateway的方式。API Gateway是一个服务器,也可以说是进入系统的唯一节点。这跟面向对象设计模式中的Facade模式很像。API Gateway封装内部系统的架构,并且提供API给各个客户端。它还可能有其他功能,如授权、监控、负载均衡、缓存、请求分片和管理、静态响应处理等。下图展示了一个适应当前架构的API Gateway。 [![03.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf42a7e3c.png "03.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabf42a7e3c.png) API Gateway负责请求转发、合成和协议转换。所有来自客户端的请求都要先经过API Gateway,然后路由这些请求到对应的微服务。API Gateway将经常通过调用多个微服务来处理一个请求以及聚合多个服务的结果。它可以在web协议与内部使用的非Web友好型协议间进行转换,如HTTP协议、WebSocket协议。 API Gateway可以提供给客户端一个定制化的API。它暴露一个粗粒度API给移动客户端。以产品最终页这个使用场景为例。API Gateway提供一个服务提供点(/productdetails?productid=xxx)使得移动客户端可以在一个请求中检索到产品最终页的全部数据。API Gateway通过调用多个服务来处理这一个请求并返回结果,涉及产品信息、推荐、评论等。 一个很好的API Gateway例子是[Netfix API Gateway](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html)。Netflix流服务提供数百个不同的微服务,包括电视、机顶盒、智能手机、游戏系统、平板电脑等。起初,Netflix视图提供一个[适用全场景](http://www.programmableweb.com/news/why-rest-keeps-me-night/2012/05/15)的API。但是,他们发现这种形式不好用,因为涉及到各式各样的设备以及它们独特的需求。现在,他们采用一个API Gateway来提供容错性高的API,针对不同类型设备有相应代码。事实上,一个适配器处理一个请求平均要调用6到8个后端服务。Netflix API Gateway每天处理数十亿的请求。 ## API Gateway的优点和缺点 如你所料,采用API Gateway也是优缺点并存的。API Gateway的一个最大好处是封装应用内部结构。相比起来调用指定的服务,客户端直接跟gatway交互更简单点。API Gateway提供给每一个客户端一个特定API,这样减少了客户端与服务器端的通信次数,也简化了客户端代码。 API Gateway也有一些缺点。它是一个高可用的组件,必须要开发、部署和管理。还有一个问题,它可能成为开发的一个瓶颈。开发者必须更新API Gateway来提供新服务提供点来支持新暴露的微服务。更新API Gateway时必须越轻量级越好。否则,开发者将因为更新Gateway而排队列。但是,除了这些缺点,对于大部分的应用,采用API Gateway的方式都是有效的。 ## 实现一个API Gateway 既然我们已经知道了采用API Gateway的动机和优缺点,下面来看在设计它时需要考虑哪些事情。 ### 性能和可扩展性 只有少数公司需要处理像Netflix那样的规模,每天需要处理数十亿的请求。但是,对于大多数应用,API Gateway的性能和可扩展性也是非常重要的。因此,创建一个支持同步、非阻塞I/O的API Gateway是有意义的。已经有不同的技术可以用来实现一个可扩展的API Gateway。在JVM上,采用基于NIO技术的框架,如Netty,Vertx,Spring Reactor或者JBoss Undertow。Node.js是一个非JVM的流行平台,它是一个在Chrome的JavaScript引擎基础上建立的平台。一个可选的方案是[NGINX Plus](http://nginx.com/solutions/get-apis/)。NGINX Plus提供一个成熟的、可扩展的、高性能web服务器和反向代理,它们均容易部署、配置和二次开发。NGINX Plus可以管理授权、权限控制、负载均衡、缓存并提供应用健康检查和监控。 ### 采用反应性编程模型 对于有些请求,API Gateway可以通过直接路由请求到对应的后端服务上的方式来处理。对于另外一些请求,它需要调用多个后端服务并合并结果来处理。对于一些请求,例如产品最终页面请求,发给后端服务的请求是相互独立的。为了最小化响应时间,API Gateway应该并发的处理相互独立的请求。但是,有时候请求之间是有依赖的。API Gateway可能需要先通过授权服务来验证请求,然后在路由到后端服务。类似的,为了获得客户的产品愿望清单,需要先获取该用户的资料,然后返回清单上产品的信息。这样的一个API 组件是[Netflix Video Grid](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html)。 利用传统的同步回调方法来实现API合并的代码会使得你进入回调函数的噩梦中。这种代码将非常难度且难以维护。一个优雅的解决方案是采用反应性编程模式来实现。类似的反应抽象实现有Scala的[Future](http://docs.scala-lang.org/overviews/core/futures.html),Java8的[CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html)和JavaScript的[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。基于微软.Net平台的有[Reactive Extensions(Rx)](http://reactivex.io/)。Netflix为JVM环境创建了RxJava来使用他们的API Gateway。同样地,JavaScript平台有RxJS,可以在浏览器和Node.js平台上运行。采用反应编程方法可以帮助快速实现一个高效的API Gateway代码。 ### 服务调用 一个基于微服务的应用是一个分布式系统,并且必须采用线程间通信的机制。有两种线程间通信的方法。一种是采用异步机制,基于消息的方法。这类的实现方法有JMS和AMQP。另外的,例如Zeromq属于服务间直接通信。还有一种线程间通信采用同步机制,例如Thrift和HTTP。事实上一个系统会同时采用同步和异步两种机制。由于它的实现方式有很多种,因此API Gateway就需要支持多种通信方式。 ### 服务发现 API Gateway需要知道每一个微服务的IP和端口。在传统应用中,你可能会硬编码这些地址,但是在现在云基础的微服务应用中,这将是个简单的问题。基础服务通常会采用静态地址,可以采用操作系统环境变量来指定。但是,探测应用服务的地址就没那么容易了。应用服务通常动态分配地址和端口。同样的,由于扩展或者升级,服务的实例也会动态的改变。因此,API Gateway需要采用系统的服务发现机制,要么采用[服务端发现](http://microservices.io/patterns/server-side-discovery.html),要么是[客户端发现](http://microservices.io/patterns/client-side-discovery.html)。后续的一篇文章将会更详细的介绍这部分。如果采用客户端发现服务,API Gateway必须要去查询[服务注册处](http://microservices.io/patterns/service-registry.html),也就是微服务实例地址的数据库。 ### 处理部分失败 在实现API Gateway过程中,另外一个需要考虑的问题就是部分失败。这个问题发生在分布式系统中当一个服务调用另外一个服务超时或者不可用的情况。API Gateway不应该被阻断并处于无限期等待下游服务的状态。但是,如何处理这种失败依赖于特定的场景和具体服务。例如,如果是在产品详情页的推荐服务模块无响应,那么API Gateway应该返回剩下的其他信息给用户,因为这些信息也是有用的。推荐部分可以返回空,也可以返回固定的顶部10个给用户。但是,如果是产品信息服务无响应,那么API Gateway就应该给客户端返回一个错误。 在缓存有效的时候,API Gateway应该能够返回缓存。例如,由于产品价格变化并不频繁,API Gateway在价格服务不可用时应该返回缓存中的数值。这类数据可以由API Gateway自身来缓存,也可以由Redis或Memcached这类外部缓存实现。通过返回缓存数据或者默认数据,API Gateway来确保系统错误不影响到用户体验。 [Netflix Hystrix](https://github.com/Netflix/Hystrix)对于实现远程服务调用代码来说是一个非常好用的库。Hystrix记录那些超过预设定的极限值的调用。它实现了*circuit break*模式,使得可以将客户端从无响应服务的无尽等待中停止。如果一个服务的错误率超过预设值,Hystrix将中断服务,并且在一段时间内所有请求立刻失效。Hystrix可以为请求失败定义一个fallback操作,例如读取缓存或者返回默认值。如果你在用JVM,就应该考虑使用Hystrix。如果你采用的非JVM环境,那么应该考虑采用类似功能的库。 ## 总结 对于大多数微服务基础的应用,实现一个API Gateway都是有意义的,它就像是进入系统的一个服务提供点。API Gateway负责请求转发、请求合成和协议转换。它提供给应用客户端一个自定义的API。API Gateway可以通过返回缓存或者默认值的方式来掩盖后端服务的错误。在本系列的下一篇文章中,我们将讨论服务间的通信问题。 原文链接:[Building Microservices: Using an API Gateway](http://nginx.com/blog/building-microservices-using-an-api-gateway/) (翻译:陈杰;审校:杨峰) =============================================== 译者介绍 陈杰,北京理工大学计算机学院在读博士,研究方向是自然语言处理在企业网络信誉评价方面的应用,平时也乐于去实现一些突发的想法。在疲于配置系统环境时发现了Docker,跟大家一起学习、使用和研究Docker。
';

(一):微服务架构的优势与不足

最后更新于:2022-04-01 02:47:16

> 原文出处:http://www.dockone.io/article/394 【编者的话】本文来自Nginx官方博客,是微服务系列文章的第一篇,主要探讨了传统的单体式应用的不足,以及微服务架构的优势与挑战。正如作者所说,微服务架构更适合用于构建复杂的应用,尽管它也有自己的不足。 这篇文章作者是Chris Richardson,他是早期基于Java的Amazonite EC2 PaaS平台CloudFoundry.com的创始人。现在他为企业提供如何开发和部署应用的咨询服务。他也经常在[http://microservices.io](http://microservices.io/)上发表有关微服务的文章。 微服务正在博客、社交媒体讨论组和会议演讲中获得越来越多的关注,在Gartner的2014 Hype Cycle上它的排名非常靠前。同时,软件社区中也有不少持怀疑论者,认为微服务不是什么新东西。Naysayers认为这就是SOA架构的重新包装。然而,尽管存在着不同的争论,微服务架构模式却正在为敏捷部署以及复杂企业应用实施提供巨大的帮助。 这篇博客是关于如何设计、开发和部署微服务的七篇系列文章中的第一篇。读者将会从中学到方法,并且和[单体式架构模式](http://microservices.io/patterns/monolithic.html)(译者注:本文中会将 Monolithic翻译为单体)进行对比。这一系列文章将描述微服务架构中不同元素。你将了解到微服务架构模式的优缺点,以便决定是否更好的将微服务架构应用到自己的项目中,以及如何应用这一模式。 首先我们看看为什么要考虑使用微服务。 [TOC] ## 开发单体式应用 假设你正准备开发一款与Uber和Hailo竞争的出租车调度软件,经过初步会议和需求分析,你可能会手动或者使用基于Rails、Spring Boot、Play或者Maven的生成器开始这个新项目,它的六边形架构是模块化的 ,架构图如下: [![1.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabeac0f930.png "1.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabeac0f930.png) 应用核心是业务逻辑,由定义服务、域对象和事件的模块完成。围绕着核心的是与外界打交道的适配器。适配器包括数据库访问组件、生产和处理消息的消息组件,以及提供API或者UI访问支持的web模块等。 尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用。具体的格式依赖于应用语言和框架。例如,许多Java应用会被打包为WAR格式,部署在Tomcat或者Jetty上,而另外一些Java应用会被打包成自包含的JAR格式,同样,Rails和Node.js会被打包成层级目录。 这种应用开发风格很常见,因为IDE和其它工具都擅长开发一个简单应用,这类应用也很易于调试,只需要简单运行此应用,用Selenium链接UI就可以完成端到端测试。单体式应用也易于部署,只需要把打包应用拷贝到服务器端,通过在负载均衡器后端运行多个拷贝就可以轻松实现应用扩展。在早期这类应用运行的很好。 ## 单体式应用的不足 不幸的是,这种简单方法却有很大的局限性。一个简单的应用会随着时间推移逐渐变大。在每次的[sprint](http://techblog.youdao.com/?p=373)中,开发团队都会面对新“故事”,然后开发许多新代码。几年后,这个小而简单的应用会变成了一个巨大的怪物。这儿有一个例子,我最近和一个开发者讨论,他正在写一个工具,用来分析他们一个拥有数百万行代码的应用中JAR文件之间的依赖关系。我很确信这个代码正是很多开发者经过多年努力开发出来的一个怪物。 一旦你的应用变成一个又大又复杂的怪物,那开发团队肯定很痛苦。敏捷开发和部署举步维艰,其中最主要问题就是这个应用太复杂,以至于任何单个开发者都不可能搞懂它。因此,修正bug和正确的添加新功能变的非常困难,并且很耗时。另外,团队士气也会走下坡路。如果代码难于理解,就不可能被正确的修改。最终会走向巨大的、不可理解的泥潭。 单体式应用也会降低开发速度。应用越大,启动时间会越长。比如,最近的一个调查表明,有时候应用的启动时间居然超过了12分钟。我还听说某些应用需要40分钟启动时间。如果开发者需要经常重启应用,那么大部分时间就要在等待中渡过,生产效率受到极大影响。 另外,复杂而巨大的单体式应用也不利于持续性开发。今天,SaaS应用常态就是每天会改变很多次,而这对于单体式应用模式非常困难。另外,这种变化带来的影响并没有很好的被理解,所以不得不做很多手工测试。那么接下来,持续部署也会很艰难。 单体式应用在不同模块发生资源冲突时,扩展将会非常困难。比如,一个模块完成一个CPU敏感逻辑,应该部署在AWS EC2 Compute Optimized instances,而另外一个内存数据库模块更合适于EC2 Memory-optimized instances。然而,由于这些模块部署在一起,因此不得不在硬件选择上做一个妥协。 单体式应用另外一个问题是可靠性。因为所有模块都运行在一个进程中,任何一个模块中的一个bug,比如内存泄露,将会有可能弄垮整个进程。除此之外,因为所有应用实例都是唯一的,这个bug将会影响到整个应用的可靠性。 最后,单体式应用使得采用新架构和语言非常困难。比如,设想你有两百万行采用XYZ框架写的代码。如果想改成ABC框架,无论是时间还是成本都是非常昂贵的,即使ABC框架更好。因此,这是一个无法逾越的鸿沟。你不得不在最初选择面前低头。 总结一下:一开始你有一个很成功的关键业务应用,后来就变成了一个巨大的,无法理解的怪物。因为采用过时的,效率低的技术,使得雇佣有潜力的开发者很困难。应用无法扩展,可靠性很低,最终,敏捷性开发和部署变的无法完成。 那么如何应对呢? ## 微处理架构——处理复杂事物 许多公司,比如Amazon、eBay和NetFlix,通过采用微处理结构模式解决了上述问题。其思路不是开发一个巨大的单体式的应用,而是将应用分解为小的、互相连接的微服务。 一个微服务一般完成某个特定的功能,比如下单管理、客户管理等等。每一个微服务都是微型六角形应用,都有自己的业务逻辑和适配器。一些微服务还会发布API给其它微服务和应用客户端使用。其它微服务完成一个Web UI,运行时,每一个实例可能是一个云VM或者是Docker容器。 比如,一个前面描述系统可能的分解如下: [![2.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabeb6af02f.png "2.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabeb6af02f.png) 每一个应用功能区都使用微服务完成,另外,Web应用会被拆分成一系列简单的Web应用(比如一个对乘客,一个对出租车驾驶员)。这样的拆分对于不同用户、设备和特殊应用场景部署都更容易。 每一个后台服务开放一个REST API,许多服务本身也采用了其它服务提供的API。比如,驾驶员管理使用了告知驾驶员一个潜在需求的通知服务。UI服务激活其它服务来更新Web页面。所有服务都是采用异步的,基于消息的通讯。微服务内部机制将会在后续系列中讨论。 一些REST API也对乘客和驾驶员采用的移动应用开放。这些应用并不直接访问后台服务,而是通过API Gateway来传递中间消息。API Gateway负责负载均衡、缓存、访问控制、API 计费监控等等任务,可以通过NGINX方便实现,后续文章将会介绍到API Gateway。 [![3.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabeb77b1b2.png "3.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabeb77b1b2.png) 微服务架构模式在上图中对应于代表可扩展Scale Cube的Y轴,这是一个在《The Art of Scalability》书中描述过的三维扩展模型。另外两个可扩展轴,X轴由负载均衡器后端运行的多个应用副本组成,Z轴是将需求路由到相关服务。 应用基本可以用以上三个维度来表示,Y轴代表将应用分解为微服务。运行时,X轴代表运行多个隐藏在负载均衡器之后的实例,提供吞吐能力。一些应用可能还是用Z轴将服务分区。下面的图演示行程管理服务如何部署在运行于AWS EC2上的Docker上。 [![4.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabebcde2fc.png "4.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabebcde2fc.png) 运行时,行程管理服务由多个服务实例构成。每一个服务实例都是一个Docker容器。为了保证高可用,这些容器一般都运行在多个云VM上。服务实例前是一层诸如NGINX的负载均衡器,他们负责在各个实例间分发请求。负载均衡器也同时处理其它请求,例如缓存、权限控制、API统计和监控。 这种微服务架构模式深刻影响了应用和数据库之间的关系,不像传统多个服务共享一个数据库,微服务架构每个服务都有自己的数据库。另外,这种思路也影响到了企业级数据模式。同时,这种模式意味着多份数据,但是,如果你想获得微服务带来的好处,每个服务独有一个数据库是必须的,因为这种架构需要这种松耦合。下面的图演示示例应用数据库架构。 [![5.png](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabec6239ad.png "5.png")](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabec6239ad.png) 每种服务都有自己的数据库,另外,每种服务可以用更适合自己的数据库类型,也被称作多语言一致性架构。比如,驾驶员管理(发现哪个驾驶员更靠近乘客),必须使用支持地理信息查询的数据库。 表面上看来,微服务架构模式有点像SOA,他们都由多个服务构成。但是,可以从另外一个角度看此问题,微服务架构模式是一个不包含Web服务(WS-)和ESB服务的SOA。微服务应用乐于采用简单轻量级协议,比如REST,而不是WS-,在微服务内部避免使用ESB以及ESB类似功能。微服务架构模式也拒绝使用canonical schema等SOA概念。 ## 微服务架构的好处 微服务架构模式有很多好处。首先,通过分解巨大单体式应用为多个服务方法解决了复杂性问题。在功能不变的情况下,应用被分解为多个可管理的分支或服务。每个服务都有一个用RPC-或者消息驱动API定义清楚的边界。微服务架构模式给采用单体式编码方式很难实现的功能提供了模块化的解决方案,由此,单个服务很容易开发、理解和维护。 第二,这种架构使得每个服务都可以有专门开发团队来开发。开发者可以自由选择开发技术,提供API服务。当然,许多公司试图避免混乱,只提供某些技术选择。然后,这种自由意味着开发者不需要被迫使用某项目开始时采用的过时技术,他们可以选择现在的技术。甚至于,因为服务都是相对简单,即使用现在技术重写以前代码也不是很困难的事情。 第三,微服务架构模式是每个微服务独立的部署。开发者不再需要协调其它服务部署对本服务的影响。这种改变可以加快部署速度。UI团队可以采用AB测试,快速的部署变化。微服务架构模式使得持续化部署成为可能。 最后,微服务架构模式使得每个服务独立扩展。你可以根据每个服务的规模来部署满足需求的规模。甚至于,你可以使用更适合于服务资源需求的硬件。比如,你可以在EC2 Compute Optimized instances上部署CPU敏感的服务,而在EC2 memory-optimized instances上部署内存数据库。 ## 微服务架构的不足 Fred Brooks在30年前写道,“there are no silver bullets”,像任何其它科技一样,微服务架构也有不足。其中一个跟他的名字类似,『微服务』强调了服务大小,实际上,有一些开发者鼓吹建立稍微大一些的,10-100 LOC服务组。尽管小服务更乐于被采用,但是不要忘了这只是终端的选择而不是最终的目的。微服务的目的是有效的拆分应用,实现敏捷开发和部署。 另外一个主要的不足是,微服务应用是分布式系统,由此会带来固有的复杂性。开发者需要在RPC或者消息传递之间选择并完成进程间通讯机制。更甚于,他们必须写代码来处理消息传递中速度过慢或者不可用等局部失效问题。当然这并不是什么难事,但相对于单体式应用中通过语言层级的方法或者进程调用,微服务下这种技术显得更复杂一些。 另外一个关于微服务的挑战来自于分区的数据库架构。商业交易中同时给多个业务分主体更新消息很普遍。这种交易对于单体式应用来说很容易,因为只有一个数据库。在微服务架构应用中,需要更新不同服务所使用的不同的数据库。使用分布式交易并不一定是好的选择,不仅仅是因为CAP理论,还因为今天高扩展性的NoSQL数据库和消息传递中间件并不支持这一需求。最终你不得不使用一个最终一致性的方法,从而对开发者提出了更高的要求和挑战。 测试一个基于微服务架构的应用也是很复杂的任务。比如,采用流行的Spring Boot架构,对一个单体式web应用,测试它的REST API,是很容易的事情。反过来,同样的服务测试需要启动和它有关的所有服务(至少需要这些服务的stubs)。再重申一次,不能低估了采用微服务架构带来的复杂性。 另外一个挑战在于,微服务架构模式应用的改变将会波及多个服务。比如,假设你在完成一个案例,需要修改服务A、B、C,而A依赖B,B依赖C。在单体式应用中,你只需要改变相关模块,整合变化,部署就好了。对比之下,微服务架构模式就需要考虑相关改变对不同服务的影响。比如,你需要更新服务C,然后是B,最后才是A,幸运的是,许多改变一般只影响一个服务,而需要协调多服务的改变很少。 部署一个微服务应用也很复杂,一个分布式应用只需要简单在复杂均衡器后面部署各自的服务器就好了。每个应用实例是需要配置诸如数据库和消息中间件等基础服务。相对比,一个微服务应用一般由大批服务构成。例如,根据Adrian Cockcroft,[Hailo有160个不同服务构成](https://sudo.hailoapp.com/services/2015/03/09/journey-into-a-microservice-world-part-3/),NetFlix有大约600个服务。每个服务都有多个实例。这就造成许多需要配置、部署、扩展和监控的部分,除此之外,你还需要完成一个服务发现机制(后续文章中发表),以用来发现与它通讯服务的地址(包括服务器地址和端口)。传统的解决问题办法不能用于解决这么复杂的问题。接续而来,成功部署一个微服务应用需要开发者有足够的控制部署方法,并高度自动化。 一种自动化方法是使用PaaS服务,例如[Cloud Foundry](http://www.cloudfoundry.org/)。PaaS给开发者提供一个部署和管理微服务的简单方法,它把所有这些问题都打包内置解决了。同时,配置PaaS的系统和网络专家可以采用最佳实践和策略来简化这些问题。另外一个自动部署微服务应用的方法是开发对于你来说最基础的PaaS系统。一个典型的开始点是使用一个集群化方案,比如配合Docker使用Mesos或者Kubernetes。后面的系列我们会看看如何基于软件部署方法例如NGINX,可以方便的在微服务层面提供缓存、权限控制、API统计和监控。 ## 总结 构建复杂的应用真的是非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。 在后续的博客中,我会深入探索微服务架构模式,并讨论诸如服务发现、服务部署选择和如何分解一个分布式应用为多个服务的策略。
';

微服务实战

最后更新于:2022-04-01 02:47:13

';

微服务架构的设计模式

最后更新于:2022-04-01 02:47:11

> 原文出处:http://www.infoq.com/cn/news/2015/04/micro-service-architecture 作者:谢丽 前不久,Java Code Geeks发表了一篇[文章](http://www.javacodegeeks.com/2015/04/microservices-monoliths-and-noops.html),分析[单体应用与微服务的优缺点](http://www.infoq.com/cn/news/2015/04/single-app-micro-service)。近日,该网站又发表了一篇[文章](http://www.javacodegeeks.com/2015/04/microservice-design-patterns.html),提供了六种微服务架构的设计模式。 [TOC] ## 聚合器微服务设计模式 这是一种最常用也最简单的设计模式,如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabb24a1a81.png) 聚合器调用多个服务实现应用程序所需的功能。它可以是一个简单的Web页面,将检索到的数据进行处理展示。它也可以是一个更高层次的组合微服务,对检索到的数据增加业务逻辑后进一步发布成一个新的微服务,这符合DRY原则。另外,每个服务都有自己的缓存和数据库。如果聚合器是一个组合服务,那么它也有自己的缓存和数据库。聚合器可以沿X轴和Z轴独立扩展。 ## 代理微服务设计模式 这是聚合器模式的一个变种,如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabb2fc0f9a.png) 在这种情况下,客户端并不聚合数据,但会根据业务需求的差别调用不同的微服务。代理可以仅仅委派请求,也可以进行数据转换工作。 ## 链式微服务设计模式 这种模式在接收到请求后会产生一个经过合并的响应,如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabb3d0f34c.png) 在这种情况下,服务A接收到请求后会与服务B进行通信,类似地,服务B会同服务C进行通信。所有服务都使用同步消息传递。在整个链式调用完成之前,客户端会一直阻塞。因此,服务调用链不宜过长,以免客户端长时间等待。 ## 分支微服务设计模式 这种模式是聚合器模式的扩展,允许同时调用两个微服务链,如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabb45d8ed0.png) ## 数据共享微服务设计模式 自治是微服务的设计原则之一,就是说微服务是全栈式服务。但在重构现有的“单体应用(monolithic application)”时,SQL数据库反规范化可能会导致数据重复和不一致。因此,在单体应用到微服务架构的过渡阶段,可以使用这种设计模式,如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabb4f419ff.png) 在这种情况下,部分微服务可能会共享缓存和数据库存储。不过,这只有在两个服务之间存在强耦合关系时才可以。对于基于微服务的新建应用程序而言,这是一种反模式。 ## 异步消息传递微服务设计模式 虽然REST设计模式非常流行,但它是同步的,会造成阻塞。因此部分基于微服务的架构可能会选择使用消息队列代替REST请求/响应,如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabba2e5902.png) 感兴趣的读者可以参考《[微服务中的耦合与自治](https://www.voxxed.com/blog/2015/04/coupling-versus-autonomy-in-microservices/)》一文为自己的微服务选择合适的消息传递模式。
';

(二)微服务架构综述

最后更新于:2022-04-01 02:47:09

> 原文出处:http://www.infoq.com/cn/articles/analysis-the-architecture-of-microservice-part-02 作者 :王磊 在[解析微服务架构(一) 单块架构系统以及其面临的挑战](http://www.infoq.com/cn/articles/analysis-the-architecture-of-microservice-part-01)中,我们谈到了随着市场的快速发展,业务的不断扩大,单块架构应用面临着越来越多的挑战,其改造与重构势在必行。 [TOC=2] ## 微服务的诞生 微服务架构(Microservice Architect)是一种架构模式,它提倡将单块架构的应用划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。 微服务架构虽然诞生的时间并不长,但其在各种演讲、文章、书籍上所出现的频率已经让很多人意识到它对软件架构领域所带来的影响。 ### 背景 其实,微服务的诞生并非偶然。它是互联网高速发展,敏捷、精益、持续交付方法论的深入人心,虚拟化技术与DevOps文化的快速发展以及传统单块架构无法适应快速变化等多重因素的推动下所诞生的产物: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc822a28d.png) #### 1\. 互联网行业的快速发展 过去的十年中,互联网对我们的生活产生了翻天覆地的变化。购物、打车、订餐、支付,甚至美甲、洗车等,想到的,想不到的活动都可以通过互联网完成,越来越多的传统行业公司也开始依赖互联网技术打造其核心竞争优势。互联网时代的产品通常有两类特点:需求变化快和用户群体庞大。在这种情况下,如何从系统架构的角度出发,构建灵活、易扩展的系统,快速应对需求的变化;同时,随着用户量的增加,如何保证系统的可伸缩性、高可用性,成为系统架构面临的挑战。 #### 2\. 敏捷、精益方法论的深入人心 纵观IT行业过去的十年,敏捷、精益、持续交付等价值观、方法论的提出以及实践,让很多组织意识到应变市场变化、提高响应力的重要性。精益创业(Lean Startup)帮助组织分析并建立最小可实行产品(Minimum Viable Product),通过迭代持续改进;敏捷方法帮助组织消除浪费,通过反馈不断找到正确的方向;持续交付帮助组织构建更快、更可靠、可频繁发布的交付机制。经过这些方法论以及实践的推行和尝试后,从宏观上而言,大部分组织已经基本上形成了一套可遵循、可参考、可实施的交付体系。这时候,逐渐完善并改进各个细节的需求就会更加强烈。所谓细节,就是类似如何找到灵活性高、扩展性好的架构方式、如何用更有效的技术、工具解决业务问题等。 #### 3\. 虚拟化技术与DevOps文化的快速发展 虚拟化技术和基础设施自动化(Infrastructure As Code)的快速发展极大的简化了基础设施的创建、配置以及系统的安装和部署。譬如云平台的成熟以及像[Chef](https://www.chef.io/)、[Puppet](https://puppetlabs.com/)、[Ansible](http://www.ansible.com/)等工具的使用,让更多的基础设施能够通过自动化的方式动态创建。同时,容器化技术的发展以及[Docker](https://www.docker.com/)的出现,更是将虚拟化技术推向了一个史无前例的高潮。另外,DevOps文化的推行打破了传统开发与运维之间的壁垒,帮助组织形成更高效的、开发与运维高度协作的交付团队。这些技术与文化的快速发展,极大程度上解决了传统环境创建难、配置难以及‘最后一公里’的部署难、交付难等问题,成为推动微服务诞生、发展的重要因素之一。 #### 4\. 单块架构系统面临的挑战 几年前我们熟悉的传统IT系统,也可以称之为单块架构系统,是以技术分层,譬如逻辑层、数据层等。但随着用户需求个性化、产品生命周期变短、市场需求不稳定等因素的出现,单块架构系统面临着越来越多的挑战。因此,如何找到一种更有效的、更灵活、更适应当前互联网时代需求的系统架构方式,成为大家关注的焦点。 所以说,微服务的诞生决不是偶然,是多重因素推动下的必然产物。 ## 微服务与SOA ### SOA简述 早在1996年,Gartner就提出面向服务架构(SOA)。SOA阐述了“对于复杂的企业IT系统,应按照不同的、可重用的粒度划分,将功能相关的一组功能提供者组织在一起为消费者提供服务”,其目的是为了解决企业内部不同IT资源之间无法互联而导致的信息孤岛问题。 2002年,SOA被称作"现代应用开发领域最重要的课题之一",其正在帮助企业从资源利用的角度出发,将IT资源整合成可操作的、基于标准的服务,使其能被重新组合和应用。 但是,由于SOA本身的广义性以及抽象性,在其诞生的相当长一段时间内,人们对SOA存在着不同的认知和理解。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc83a6a70.png) 直到2000年左右,[ESB(Enterprise Service Bus)](https://en.wikipedia.org/wiki/Enterprise_service_bus)、[WebService](https://en.wikipedia.org/wiki/Web_service)、[SOAP](https://en.wikipedia.org/wiki/Soap)等这类技术的出现,才使得SOA渐渐落地。同时,更多的厂商像IBM、Oracle等也分别提出基于SOA的解决方案或者产品。 ### 微服务与SOA 实际上,微服务架构并不是一个全新的概念。仔细分析SOA的概念,就会发现,其和我们今天所谈到的微服务思想几乎一致。那在SOA诞生这么多年后,为什么又提出了微服务架构呢? 鉴于过去十几年互联网行业的高速发展,以及敏捷、持续集成、持续交付、DevOps,云技术等的深入人心,服务架构的开发、测试、部署以及监控等,相比我们提到的传统的SOA实现,已经大相径庭,主要区别如下表所示: | SOA实现 | 微服务架构实现 | | --- | --- | | 企业级,自顶向下开展实施 | 团队级,自底向上开展实施 | | 服务由多个子系统组成,粒度大 | 一个系统被拆分成多个服务,粒度细 | | 企业服务总线,集中式的服务架构 | 无集中式总线,松散的服务架构 | | 集成方式复杂(ESB/WS/SOAP) | 集成方式简单(HTTP/REST/JSON) | | 单块架构系统,相互依赖,部署复杂 | 服务都能独立部署 | 相比传统SOA的服务实现方式,微服务更具有灵活性、可实施性以及可扩展性,其强调的是一种独立测试、独立部署、独立运行的软件架构模式。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc87aa621.png) ### 微服务架构的定义 其实,即便了解了上面的介绍,也很难对微服务下一个准确的定义。就像NoSQL,我们谈论了好几年的NoSQL,知道NoSQL代表着什么样的含义,也可以根据不同的应用场景选择不同的NoSQL数据库,但是我们还是很难对它下一个准确的定义。类似的,关于什么是‘函数式编程’,也或多或少存在同样的窘境。我们可以轻松的选择不同的函数式编程语言,可以轻松的写出函数式编程风格的代码,但很难对什么是函数式编程下一个准确的定义。 实际上,从业界的讨论来看,微服务本身并没有一个严格的定义。不过,ThoughtWorks的首席科学家,马丁 -福勒先生对微服务的这段描述,似乎更加具体、贴切,通俗易懂: > Microservice > > The microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies. > 微服务架构 > > 微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。 总结下来,微服务架构中的核心部分包括以下几点: * 小, 且专注于做⼀件事情 * 独立的进程中 * 轻量级的通信机制 * 松耦合、独立部署 ## 总结 随着市场的快速发展,业务的不断扩大,单块架构应用面临着越来越多的挑战,其改造与重构势在必行。而微服务架构的诞生,是互联网高速发展,虚拟化技术应用以及持续交付、DevOps深入人心的综合产物。随着用户需求个性化、产品生命周期变短,微服务架构是未来软件软件架构朝着灵活性、扩展性、伸缩性以及高可用性发展的必然方向。同时,以Docker为代表的容器虚拟化技术的盛行,将大大降低微服务实施的成本,为微服务落地以及大规模使用提供了坚实的基础和保障。 ## 作者简介 王磊,ThoughtWorks公司首席咨询师。开源软件的爱好者和贡献者,社区活动的参与者,Practical RubyGems的译者, [GDCR](http://gdcr.coderetreat.org/)西安的组织者。于2012年加入ThoughtWorks,为国内外诸多客户提供项目交付和咨询服务;在加入ThoughtWorks之前,曾就职过多家知名外企,具有丰富的敏捷项目实战经验。目前致力于微服务架构、虚拟化容器、持续交付、以及DevOps的研究与实践。 ## 参考文献 http://microservices.io/ htto://martinfowler.com/articles/microservices.html
';

(一)单块架构系统以及其面临的挑战

最后更新于:2022-04-01 02:47:06

> 原文出处:http://www.infoq.com/cn/articles/analysis-the-architecture-of-microservice-part-01 作者 :王磊 [TOC=2] ## 概述 多年来,我们一直在技术的浪潮中乘风破浪,扬帆奋进,寻找更优秀的方法来构建IT系统,也一直在积极的学习并观察先进的公司如何以不同的架构方式构建或者优化其IT系统,来积极应对市场的变化,迅速做出响应,从而为客户提供更多的价值。 微服务架构模式(Microservice Architect Pattern)是近两年在软件架构模式领域里出现的一个新名词。虽然其诞生的时间不长,但其在各种演讲、文章、书籍上所出现的频率已经让很多人意识到它对软件领域所带来的影响。那到底什么是微服务,当我们谈论微服务时,它代表着一种什么样的含义?微服务适合应用在什么场景下,以及它有什么样的优缺点?微服务和SOA到底有没有区别?在接下来的几部分里,我将为大家揭开微服务的神秘面纱。 不过,在我们开始探讨微服务架构之前,让我们先回顾一下三层应用架构的发展历程并认识一下什么是单块架构应用。 ## 三层应用架构的发展 对于任何一个软件应用系统而言,其构建目标都是为了满足某类用户的需求,即为用户传递价值。一直以来,软件的架构设计是决定应用系统是否能够被正确、有效实现的关键要素之一。架构设计描述了在应用系统的内部,如何根据业务、技术、组织,以及灵活性、可扩展性、可维护性等多种因素,将应用系统划分成不同的部分,并使这些部分彼此之间相互分工、相互协作,从而为用户提供某种特定价值的方式。 ## 应用的三层架构 现实生活中,“层”这个字的含义,对大家一点都不陌生。我们经常说楼房高多少层,蛋糕有几层等。通常来说,层有好几种定义,但其中最耳熟能详的,莫过于“层”能帮助我们划分出构成某整体事物的,上下相互支撑的的不同部分。譬如说,我们喜欢吃的蛋糕,一般是由三层组成:第一层的蛋糕体、第二层的奶油,和第三层的水果。从顶部至底部,每一层依赖于下一层,从底部到顶部,每一层又支撑着上一层。 在软件架构模式的领域,经过多年的发展,也有了层的概念: * 层能够被单独构造; * 层具有区别于其他层的显著特点; * 层与层之间能够互相连接、互相支撑、互相作用,相互协作构成一个整体。 * 层的内部,可以被替换成其他可工作的部分,但对整体的影响不大。 以WEB应用程序为例,在WEB应用程序开发的早期,由于受到面向过程的思维及设计方式的影响,所有的逻辑代码并没有明显的区分,因此代码之间的调用相互交错,错综复杂。譬如,我们早期使用的ASP、JSP以及PHP,都是将所有的页面逻辑、业务逻辑以及数据库访问逻辑放在一起,这是我们通常提到的一层架构。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc169472a.png) 随着JAVA,.NET等高级语言的快速发展,这些语言为开发者提供了越来越方便的的数据访问机制,如Java语言的JDBC、IO流,或者.NET的ADO.NET等。这时候,数据访问部分的代码逐渐有了清晰的结构,但表示逻辑和业务逻辑依然交织在一起,我们称这个阶段为二层架构阶段。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc3302505.png) 随着面向对象分析、面向对象设计、面向对象原则、设计模式、企业架构模式等理念以及方法论的不断发展,从为用户提供功能、以及有效组织软件结构的角度考虑,WEB 应用中不同职责的部分逐渐被定义在了不同的层次,每一层负责的部分更趋向于具体化,细致化,于是软件的三层架构逐渐出现了。三层架构通常包括表示层、业务逻辑层以及数据访问层。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc34a8d06.png) * 表示层 表示层部分通常指当用户使用应用程序时,看见的、听见的、输入的或者交互的部分。譬如,有可能是信息的显示,音乐的的播放或者可以输入的文本框,单选按钮以及可点击的按钮等。通过这些元素,用户同软件进行交互并获取期望的价值。目前的用户接口大部分情况下为WEB方式,当然也可以是桌面软件的形式,例如. NET的WINFORM或者Java的SWING。 * 业务逻辑层 业务逻辑部分是根据用户输入的信息,进行逻辑计算或者业务处理的部分。业务逻辑层则主要聚焦应用程序对业务问题的逻辑处理,以及业务流程的操作,它是大部分软件系统区别与其他系统的核心。譬如,当用户点击一个按钮后,它可能会触发业务逻辑部分的代码进行运算,生成用户期望的结果。举例来说,在一个电子商务平台中,作为用户,当我们下单购买某个商品后,应用程序的业务逻辑层会对订单如何进行处理,如何计算折扣、如何配送等进行处理。 * 数据访问层 在用户同应用程序交互的过程中,会产生数据。这类数据需要通过某种机制被有效的保存,并在将来能够被重复使用,或者提供给其他系统。这种机制或者方法就是数据访问层最关注的部分。也就是说,它关注的是应用程序是如何有效的将数据存储到数据库、文件系统或者其他存储介质中。有一点要注意的是,它关心的是对原始数据的操作(数据库或者文本文件等存放数据的形式),而非原始数据的存储介质本身。譬如,在一个电子商务平台中,商品的信息是如何存储,图片的信息是如何获取的等。 三层架构的出现,一方面是为了解决应用程序中代码间调用复杂、代码职责不清的问题。其通过在各层间定义接口,并将接口与实现分离,可以很容易的用不同的实现来替换原有层次的实现,从而有效降低层与层之间的依赖。这种方式不仅有利于帮助团队理解整个应用架构,降低后期维护成本,同时也有利于制定整个应用程序架构的标准。 另一方面,三层结构的出现从某种程度上也解决了企业内部如何有效的根据技能调配人员,提高生产效率的问题。在大环境下,有效的分层能使不同职责的人员各司其职,更聚焦与个人专业技能的发展和培养。三层结构的出现不仅标准化了复杂系统的逻辑划分,更帮助企业解决了如何有效形成技术人员组织结构的问题,因此在很长的一段时间里,它一直是软件架构的经典模式之一。 ## 非三层架构 有些人认为,对于一个WEB应用程序,其被自动地分成了三层架构,因为它有三个分离的部件,如图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc3b7d0c8.png) 这三个部分看起来虽然满足“层”的概念,但它并不是我们所说的软件架构的层。就像我们所说的奶油蛋糕,我们可以在蛋糕的底部加上稳固的底座,在蛋糕的外部加上漂亮的包装盒,但仔细想想,底座可以用不同品牌的底座,可以用纸质材料的,也可以用树脂材料的;包装盒可以用红色的一次性纸袋,也可以用蓝色的带着花纹的铁盒,它们并不是我们蛋糕组成的必须一部分。 浏览器可以独立存在与WEB应用程序之外,WEB应用程序也可以被不同的浏览器访问,因此浏览器不是WEB应用程序的部分。虽然最近几年,在浏览器端,我们可以使用很多JavaScript库或者框架独立开发前端应用,但它的范畴超出了我们目前讨论的三层架构,更多的属于富客户端以及前后端分离的应用。 类似的,数据库服务器也可以独立存在于应用程序之外,因此它也不是应用程序的一部分。虽然有些应用程序的逻辑代码,被设计成必须是在数据库中运行,例如存储过程或者触发器,但这种用法并不推荐,原因是将业务逻辑放在了数据库本身,大大增加了后期维护的复杂度和数据迁移的成本。 ## 单块架构应用 ### 什么是单块架构应用 虽然软件的三层架构帮助我们将应用在逻辑上分成了三层,但它并不是物理上的分层。这也就意味着,即便我们将应用架构分成了 所谓的三层,经过开发团队对不同层的代码实现,经历过编译(如果非静态语言,可以跳过编译阶段)、打包、部署后,不考虑负载均衡以及水平扩展的情况,最终还是运行在同一个机器的同一个进程中。对于这种功能集中、代码和数据中心化、一个发布包、部署后运行在同一进程的应用程序,我们通常称之为单块架构应用。典型的单块架构应用,莫过于传统的J2EE项目所构建的产品或者项目,它们存在的形态一般是WAR包或者EAR包。当部署这类应用时,通常是将整个一块都作为一个整体,部署在同一个WEB容器,如Tomcat或者Jetty中。当这类应用运行起来后,所有的功能也都运行在同一个进程中。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc41845a5.jpg) 类似的,基于Ruby On Rails的单块架构应用,一般逻辑上分为控制器层、模型层以及视图层,同时代码存放在遵循一定层级结构 的目录中。当部署这类应用的时候,通常是使用SSH或者其他一些工具,如[Capistrano](https://github.com/capistrano/capistrano)将整个目录部署在[Passenger](https://www.phusionpassenger.com/)或者其他WEB容器中。当这类应用运行起来后,所有的功能也都运行在同一个进程中。 因此,对于单块架构应用的定义,其实是基于分层软件架构设计的系统基础之上,从部署模式、运行模式角度去考虑的一种定义方式。 ## 单块架构应用的优势 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc4cc1da9.jpg) * 易于开发 对单块架构的应用程序而言,开发方式相对较简单。首先从概念上,现有的大部分工具、应用服务器、框架都是这类单块架构应用程序,容易理解而且为人所熟知。如果从实践角度出发,现有的集成开发工具比较适合单块架构的应用程序,像NetBeans、Eclipse、IDEA等,它们都能够有效加载并配置整个应用程序的依赖,方便开发人员开发、运行、调试等。 * 易于测试 单块架构应用程序也非常容易被测试,因为所有的功能都运行在一个进程中,启动集成开发环境或者将发布包部署到某一环境,一旦启动该进程,就可以立即开始系统测试或者功能测试。 * 易于部署 对单块架构的应用程序而言,部署也比较容易。实际上,由于所有的功能最终都会打成一个包,因此只需复制该软件包到服务器相应的位置即可。当然,部署的方式可以有很多种,最简单的可以使用SCP远程拷贝到指定的目录下,当然也可以使用某些自动化的工具来完成。 * 易于水平伸缩 对单块架构的应用程序而言,水平伸缩也比较容易。实际上,由于所有的功能最终都会打成一个包,且只能运行在一个进程中,因此单块架构的水平伸缩,更确切的理解其实是克隆,即新建一个服务器节点,配置好该节点的运行环境,复制软件包到相应的位置,运行改应用程序。当然,必须要确保负载均衡器能采取某种分发策略,有效的将请求分发到新创建的节点。 ## 单块架构面临的挑战 随着最近几年互联网行业的迅猛发展,随着公司或者组织业务的不断扩张,需求不断的增加以及用户量的不断增加,单块架构的优势已逐渐无法适应互联网时代的快速变化,面临着越来越多的挑战。譬如说,一方面,随着业务的扩大,如何为用户提供可靠的服务,如何有效处理用户增多后导致并发请求数增多,导致的响应慢的问题,以及如何有效解决用户增多后带来的大数据量的问题等。另外一方面,随着公司或者组织业务的不断扩张,需求不断的增加,越来越多的人加入开发团队,代码库也在急剧膨胀。在这种情况下,单块架构的可维护性、灵活性在降低,而测试成本、构建成本以及维护成本却在显著增加。 ### 1.维护成本增加 随着应用程序的功能越来越多,团队越来越大,相应的沟通成本、管理成本、人员协调成本必然会显著增加。譬如说,对于使用Java编写的中型应用而言,当代码量为几万行时,可能只需要几人左右的团队维护。当代码量上升到几十万行级别时,可能需要几十人甚至是上百人的团队。 另外,随着应用程序功能的增多,当出现缺陷时,有可能引起缺陷的原因组合就会比较多,这也会导致分析缺陷、定位缺陷、修复缺陷的成本相应增高,也就意味着缺陷的平均修复周期可能会花费更长时间。 另外,随着代码量的增大,在开发人员对全局功能缺乏深度理解的情况下,修复一个缺陷,还有可能引入其他的缺陷,在自动化测试机制不完善的情况下,很可能导致该过程陷入“修复越多,缺陷越多”的恶性循环。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc5094bfc.jpg) ### 2.持续交付周期长 随着应用程序的功能越来越多,代码越来越复杂,构建和部署时间也会相应的增长。在现有部署流水线稳定工作的情况下,对单块架构应用程序做任何细微的修改以及代码提交,都会触发部署流水线,对整个应用程序进行代码编译、运行单元测试、代码检查、构建并生成部署包、验证功能等,这也就意味着流水线的反馈周期变长,单位时间内构建的效率变低了。 另一方面,团队人员的增多,部署流水线运行的时间增加,开发人员能够提交代码的时间窗口就相应减少,(因为流水线运行的过程中,是禁止提交代码的),可能出现长时间等待代码提交,却无法提交的情况,极大破坏了团队的灵活性并降低了团队工作效率。几年前,我曾经工作在一个50万代码行的单块架构应用上,整个应用由一个50人左右的分布式团队负责。通常情况下,从开发人员提交代码到运行单元测试、构建发布包、运行功能测试、标记为可发布状态大概需要40分钟,时间稍微有点长,但团还能忍受。关键的问题是开发人员通常都是集中在下午3点左右,完成一定功能的情况下提交代码,结果就导致3点至5点那个时间段,成了代码提交的瓶颈,极大影响了该应用的持续集成和构建的效率。 ### 3.新人培养周期长 随着应用程序的功能越来越多,代码变得越来越复杂的同时,对于新加入团队的成员而言,了解行业背景、熟悉应用程序业务、配置本地开发环境,这些看似简单的任务,将会花费更长的时间。我曾经有个朋友,在加入一家世界500强的知名IT公司后,被安排到了一个百万级代码的产品组里。他花了将近1个月的时间来熟悉产品文档、配置开发环境后,才在本地成功的运行起了这个应用。在他从事这份新工作的头一个月里,我们好几次聊到他的新工作,得到的答案都是一样,“看文档,装环境”。对个人而言,花一个月时间来配置本地开发环境,其中的滋味和感受大家可想而知,我估计人世间比这更痛苦的事情也没几件了。而对公司或者部门而言,本期望员工花费数天就能配置好的环境,却花了一个月才能完成,这更是极大的浪费。更有甚者,在第一次配置完开发环境后,好几年都不愿意再升级或者重装系统,真是一招被蛇咬,十年怕井绳。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc51b47b8.jpg) ### 4.技术选型成本高 传统的单块架构系统倾向采用统一的技术平台或方案来解决所有问题。通常,技术栈的决策是在团队开发之前经过架构师、技术经理慎重评估后选定的,每个团队成员都必须使用相同的开发语言、持久化存储及消息系统,而且要使用类似的工具。随着应用程序的复杂性逐渐增加以及功能越来越多,如果团队希望尝试引入新的框架、技术,或者对现有技术栈升级,通常会面临不小的风险。 另一方面,互联网行业不仅市场变化快,而且技术变化也快。譬如,短短几年几年时间,光前端JavaScript的框架,就出现了好几十个,从早一点的[Backbone](http://backbonejs.org/)、[Ember](http://emberjs.com/)到[AngularJS](https://angularjs.org/)、[Ractive](http://www.ractivejs.org/)等等。类似的,后端的框架、工具等也是层出不穷,有兴趣的朋友可以参考下[ThoughtWorks的技术雷达](http://www.thoughtworks.com/radar)(该技术雷达是ThoughtWorks对业界技术、工具、语言等发展趋势的分析以及预测报告)。因此,对单块架构的应用而言,初始的技术选型严重限制了其将来采用不同语言或框架的能力。如果想尝试新的编程语言或者框架,没有完备的功能测试集,很难平滑的完成替换,而且系统规模越大,风险越高。 ### 5.可伸缩性差 如果应用程序的所有功能代码都运行在同一个服务器上,将会导致应用程序的扩展非常困难。如果迫切的需要扩展,那么垂直扩展可能是最容易的(钱不是问题)。在大多数情况下,如果舍得砸钱上IBM的服务器、Oracle的数据库或者来自EMC的存储设备,不用改变一行代码,整个世界都变好了。不幸的是,伴随着业务的增长,数据的增长,垂直扩展会变得越来越吃力,成本越来越高。这也是为什么在业很多公司开始尝试使用开源,放弃这些昂贵的IOE产品的原因。这下明白为什么近几年去IOE的呼声越来越高了吧。 当考虑水平扩展时,通常的做法是建立一个集群,通过在集群中不断的添加新节点,然后借助前端的负载均衡器,将用户的请求按照某种算法,譬如轮转法、散列法或者最小连接法等合理的将请求分配到不同的节点上。但是,由于所有程序代码都运行在服务器上的同一个进程中,会导致应用程序的水平扩展成本非常高。譬如说,如果应用程序某部分的功能是内存密集型的,如需要缓存大量数据,而另外一部分功能是CPU密集型的,如需要进行大量的运算,那么每次实施水平扩展,运行该应用的服务器都必须有足够的内存和强劲的CPU来满足需求。因此,鉴于每个服务器都要提供该应用系统所需要的各种资源,基础设施的整体花费可能会非常高。当然,如果某些节点保持状态,如用户登陆后的会话信息等,更增加了水平扩展的难度。 ### 6.构建全功能团队难 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-24_55dabc66b9233.jpg) 最后,非常微妙的是,随着应用程序的功能越来越多,代码变得越来越复杂,其应用程序的复杂结构也会逐渐映射到研发团队的结构上。康威定律指出:一个组织的设计成果,其结构往往对应于这个组织中的沟通结构。单块架构的开发模式在分工时往往以技能为单位,比如UX团队、服务端团队和数据库团队,这样的分工可能会导致任何功能上的改变都需要跨团队沟通和协调。譬如说,用户体验工程师(UX)更专注负责用户接口部分,业务层开发者则负责建立服务器后端的业务逻辑,数据库工程师和DBA们更关注数据访问组件和数据库。鉴于这些问题,随着时间的推移,不仅代码越来越难以管理,其对团队结构的影响也越来越明显。 综上所述,随着业务的不断扩大,需求功能的持续增加,单块架构已经很难满足业务快速变化的需要。一方面,代码的可维护性、扩展性、灵活性在降低;而另一方面,系统的测试成本、构建成本以及维护成本却在显著增加。因此,随着项目或者产品规模的不断扩大,单块架构应用的改造与重构势在必行。 ## 总结 互联网时代的产品通常有几类特点:创新成本低、需求变化快,用户群体庞大,它和几年前我们熟悉的单块架构应用有着本质的不同。随着市场变化快、用户需求变化快、用户访问量增加的同时,单块架构应用的维护成本、人员的培养成本、缺陷修复成本、技术架构演进的成本、系统扩展成本等都在增加,因此单块架构曾经的优势已逐渐无法适应互联网时代的快速变化,面临着越来越多的挑战。 ## 作者简介 **王磊**,ThoughtWorks公司首席咨询师。开源软件的爱好者和贡献者,社区活动的参与者,Practical RubyGems的译者, GDCR西安的组织者。于2012年加入ThoughtWorks,为国内外诸多客户提供项目交付和咨询服务;在加入ThoughtWorks之前,曾就职过多家知名外企,具有丰富的敏捷项目实战经验。目前致力于微服务架构、虚拟化容器、持续交付、以及Devops的研究与实践。
';

解析微服务架构

最后更新于:2022-04-01 02:47:04

';

前言

最后更新于:2022-04-01 02:47:02

本书中的内容均来自于互联网,聚合了微服务架构相关的知识和实战,希望对大家有所帮助。 > 本书内容不断更新中
';