服务部署的垂直伸缩_微服务:分解应用程序以实现可部署性和可伸缩性
服务部署的垂直伸缩
本文介绍了日益流行的微服务体系结构模式 。 微服务背后的一个大想法是将大型,复杂和长期存在的应用程序设计为随时间变化的一组内聚服务。 术语微服务强烈建议服务应该很小。
社区中有些人甚至主张建立10-100 LOC服务。 但是,虽然希望有小型服务,但这不应成为主要目标。 相反,您应该旨在将系统分解为服务,以解决下面讨论的各种开发和部署问题。 某些服务确实很小,而其他服务可能很大。
微服务架构的本质并不是什么新鲜事物。 分布式系统的概念非常古老。 微服务架构也类似于SOA。
它甚至被称为轻量级或细粒度的SOA。 确实,考虑微服务架构的一种方法是它是SOA,而没有WS *和ESB的商业化和负担。 尽管不是一个新颖的主意,但是微服务架构与传统的SOA不同,因此仍然值得讨论,而且更重要的是,它解决了许多组织当前遭受的许多问题。
在本文中,您将了解使用微服务架构的动机以及如何将其与更传统的整体架构进行比较。 我们讨论了微服务的优缺点。 您将学习如何使用微服务架构解决一些关键的技术挑战,包括服务间通信和分布式数据管理。
(有时是邪恶的)巨石
自从最早开发Web应用程序以来,使用最广泛的企业应用程序体系结构就是将所有应用程序服务器端组件打包到一个单元中的体系结构。 许多企业Java应用程序由单个WAR或EAR文件组成。 对于用其他语言(例如Ruby甚至C ++)编写的其他应用程序也是如此。
例如,假设您正在建立一个在线商店,该商店从客户那里接单,验证库存和可用信贷,然后发货。 您很有可能会构建如图1所示的应用程序。
图1-整体架构
该应用程序由几个组件组成,包括实现用户界面的StoreFront UI以及用于管理产品目录,处理订单和管理客户帐户的服务。 这些服务共享一个域模型,该域模型包含诸如产品,订单和客户之类的实体。
尽管具有逻辑模块化设计,但该应用程序还是作为整体部署。 例如,如果您使用Java,则该应用程序将由运行在Web容器(例如Tomcat)上的单个WAR文件组成。 该应用程序的Rails版本将由单个目录层次结构组成,该目录层次结构使用例如Apache / Nginx上的Phusion Passenger或Tomcat上的JRuby进行部署。
这种所谓的整体架构具有许多好处。 整体应用程序易于开发,因为IDE和其他开发工具都是围绕开发单个应用程序而设计的。 它们很容易测试,因为您只需要启动一个应用程序即可。 整体应用程序也易于部署,因为您只需将部署单元(文件或目录)复制到运行适当类型服务器的计算机上即可。
对于相对较小的应用程序,此方法效果很好。 但是,对于复杂的应用程序,单片架构变得笨拙。 对于开发人员而言,大型的整体应用程序可能难以理解和维护。 这也是频繁部署的障碍。 要将更改部署到一个应用程序组件,您必须构建和部署整个整体,这可能是复杂,冒险,耗时的,需要许多开发人员的配合并导致较长的测试周期。
单片架构也使尝试和采用新技术变得困难。 例如,很难在不重写整个应用程序的情况下尝试新的基础架构框架,这是冒险且不切实际的。 因此,您常常会在项目开始时就选择了技术选择。 换句话说,单片架构无法扩展以支持大型,长期存在的应用程序。
将应用程序分解为服务
幸运的是,还有其他可扩展的建筑风格。 这本书, 可扩展性的艺术 ,描述了一个真正有用的,三维的可扩展性模型: 规模立方体 ,它如图2所示。
图2-比例尺立方体
在此模型中,通过在负载均衡器后面运行应用程序的多个相同副本来缩放应用程序的常用方法称为X轴缩放。 这是提高应用程序容量和可用性的好方法。
使用Z轴缩放时,每个服务器都运行相同的代码副本。 在这方面,它类似于X轴缩放。 最大的区别在于,每个服务器仅负责数据的一部分。 系统的某些组件负责将每个请求路由到适当的服务器。 一种常用的路由选择标准是请求的属性,例如正在访问的实体的主键,即分片。 另一个常见的路由标准是客户类型。 例如,一个应用程序可以通过将他们的请求路由到具有更大容量的另一组服务器来为付费客户提供比免费客户更高的SLA。
Z轴缩放与X轴缩放一样,可以提高应用程序的容量和可用性。 但是,这两种方法都不能解决开发和应用程序复杂性增加的问题。 为了解决这些问题,我们需要应用Y轴缩放。
缩放的第三个维度是Y轴缩放或功能分解。 Z轴缩放可拆分相似的事物,而Y轴缩放可拆分不同的事物。 在应用程序层,Y轴缩放将单个应用程序拆分为一组服务。 每个服务都实现了一组相关功能,例如订单管理,客户管理等。
确定如何将系统划分为一组服务是一项很复杂的技术,但是有许多策略可以提供帮助。 一种方法是按动词或用例对服务进行分区。 例如,稍后您将看到分区的在线商店具有Checkout UI服务,该服务实现了Checkout用例的UI。
另一种分区方法是按名词或资源对系统进行分区。 这种服务负责在给定类型的实体/资源上进行的所有操作。 例如,稍后您将看到在线商店拥有管理产品目录的目录服务的意义。
理想情况下,每个服务应只承担一小部分职责。 (叔叔)鲍勃·马丁(Bob Martin) 谈论[PDF]使用单一负责任原则 (SRP) 设计课程 。 SRP将班级的责任定义为变更的原因,而班级仅应具有变更的理由。 将SRP应用于服务设计也很有意义。
有助于服务设计的另一个类比是Unix实用程序的设计。 Unix提供了大量的实用程序,例如grep,cat和find。 每个实用程序只能做一件事情,通常做得非常好,并且可以与其他实用程序结合使用shell脚本来执行复杂的任务。 在Unix实用程序上对服务建模并创建单功能服务是有意义的。
重要的是要注意,分解的目的不是仅仅为了它而提供微小的服务(例如,有人认为10-100 LOC )。 相反,目标是解决上述整体架构的问题和局限性。 有些服务可能很小,但是其他的则可能更大。
如果将Y轴分解应用于示例应用程序,则会得到如图3所示的体系结构。
图3-微服务架构
分解后的应用程序由实现用户界面不同部分的各种前端服务和多个后端服务组成。 前台服务包括实现产品搜索和浏览的目录UI,以及实现购物车和结账流程的Checkout UI。 后端服务包括与本文开头所述相同的逻辑服务。 我们已经将应用程序的每个主要逻辑组件变成了独立服务。 让我们看看这样做的后果。
微服务架构的优缺点
这种体系结构具有许多好处。 首先,每个微服务都相对较小。 该代码使开发人员更容易理解。 较小的代码库不会降低IDE的速度,从而使开发人员的工作效率更高。 而且,每项服务的启动速度通常都比大型整体启动快得多,这又使开发人员的工作效率更高,并加快了部署速度
其次,每个服务可以独立于其他服务部署。 如果负责服务的开发人员需要部署该服务本地的更改,则无需与其他开发人员进行协调。 他们只需部署更改即可。 微服务架构使连续部署变得可行。
第三,使用X轴克隆和Z轴分区,可以独立于其他服务扩展每个服务。 而且,每种服务都可以部署在最适合其资源要求的硬件上。 这与使用整体架构(必须将具有大量不同资源需求(例如,CPU密集型与内存密集型))的组件一起部署的整体架构完全不同。
微服务架构使扩展开发变得更加容易。 您可以围绕多个小型(例如,两个披萨)团队来组织开发工作。 每个团队全权负责单个服务或一组相关服务的开发和部署。 每个团队可以独立于所有其他团队开发,部署和扩展服务。
微服务架构还改善了故障隔离。 例如,一项服务中的内存泄漏仅影响该服务。 其他服务将继续正常处理请求。 相比之下,整体架构的一个行为异常的组件将导致整个系统瘫痪。
最后但并非最不重要的一点是,微服务架构消除了对技术堆栈的任何长期承诺。 原则上,在开发新服务时,开发人员可以自由选择最适合该服务的任何语言和框架。 当然,在许多组织中,限制选择是有道理的,但关键是您不受过去决定的束缚。
此外,由于服务很小,因此使用更好的语言和技术重写它们变得可行。 这也意味着,如果新技术的试用失败,您可以放弃这项工作而不会冒整个项目的风险。 这与使用整体架构时完全不同,在整体架构中,您最初的技术选择会严重限制您将来使用不同语言和框架的能力。
缺点
当然,没有什么技术是灵丹妙药,并且微服务架构具有许多明显的缺点和问题 。 首先,开发人员必须应对创建分布式系统的额外复杂性。 开发人员必须实现进程间通信机制。 很难在不使用分布式事务的情况下实现跨越多个服务的用例。 IDE和其他开发工具专注于构建整体应用程序,不为开发分布式应用程序提供明确支持。 编写涉及多种服务的自动化测试具有挑战性。 这些都是您无需在整体架构中处理的问题。
微服务架构还引入了显着的操作复杂性。 在生产中必须管理更多的活动部件–不同类型服务的多个实例。 为了成功实现这一目标,您需要高水平的自动化,包括本地代码或类似PaaS的技术(例如Netflix Asgard和相关组件),或现成的PaaS(例如Pivotal Cloud Foundry)。
此外,部署跨多个服务的功能需要各个开发团队之间的仔细协调。 您必须创建一个推出计划,该计划根据服务之间的依赖关系对服务部署进行排序。 这与使用单片式体系结构时非常不同,在单片式体系结构中,您可以轻松地将更新原子地部署到多个组件。
使用微服务架构的另一个挑战是决定在应用程序生命周期中的什么时候应该使用该架构。 在开发应用程序的第一个版本时,通常不会遇到该体系结构解决的问题。 此外,使用精心设计的分布式体系结构将减慢开发速度。
对于初创企业而言,这可能是一个主要的难题,其最大的挑战通常是如何快速发展业务模型和随附的应用程序。 使用Y轴分割可能使快速迭代变得更加困难。 但是,稍后,当挑战在于如何扩展并且您需要使用功能分解时,那么纠结的依赖关系可能会使将单片应用程序分解为一组服务变得困难。
由于这些问题,不应轻易采用微服务架构。 但是,对于需要扩展的应用程序,例如面向消费者的Web应用程序或SaaS应用程序,通常是正确的选择。 知名网站,例如eBay [PDF], Amazon.com , Groupon和Gilt都已从单片架构演变为微服务架构。
现在,我们已经研究了优缺点,现在让我们看一下微服务体系结构中的几个关键设计问题,首先是应用程序内部以及应用程序与其客户端之间的通信机制。
微服务架构中的通信机制
在微服务体系结构中,客户端与应用程序之间以及应用程序组件之间的通信模式与单片应用程序中的通信模式不同。 首先让我们看一下应用程序的客户端如何与微服务交互的问题。 之后,我们将研究应用程序内的通信机制。
API网关模式
在单片架构中,应用程序的客户端(例如Web浏览器和本机应用程序)通过负载平衡器向该应用程序的N个相同实例之一发出HTTP请求。 但是在微服务架构中,整体已被一组服务取代。 因此,我们需要回答的一个关键问题是客户如何与之互动?
应用程序客户端(例如本机移动应用程序)可以向各个服务发出RESTful HTTP请求,如图4所示。
图4-直接调用服务
从表面上看,这似乎很有吸引力。 但是,单个服务的API与客户端所需的数据之间的粒度可能存在严重的不匹配。 例如,显示一个网页可能潜在地要求调用大量服务。 例如,Amazon.com 描述了某些页面如何要求调用100多种服务。 即使通过高速Internet连接发出这么多请求,更不用说带宽较低,延迟较高的移动网络了,效率也很低,并且会导致不良的用户体验。
更好的方法是让客户端每页通过互联网向前端服务器(称为API网关)发出少量请求,也许只有一个请求,如图5所示。
图5-API网关
API网关位于应用程序的客户端和微服务之间。 它提供了专门针对客户端的API。 API网关为移动客户端提供了粗粒度的API,为使用高性能网络的桌面客户端提供了更细粒度的API。 在此示例中,桌面客户端发出多个请求以检索有关产品的信息,而移动客户端则发出单个请求。
API网关通过在高性能LAN上向一些微服务发出请求来处理传入的请求。 例如,Netflix 描述了每个请求平均如何分散到六个后端服务。 在此示例中,将来自桌面客户端的细粒度请求简单地代理到相应的服务,而来自移动客户端的每个粗粒度请求则通过汇总调用多个服务的结果来处理。
API网关不仅可以优化客户端与应用程序之间的通信,还可以封装微服务的详细信息。 这使微服务得以发展而不会影响客户端。 例如,两个微服务可能会合并。 另一个微服务可能被划分为两个或多个服务。 仅API网关需要更新以反映这些更改。 客户不受影响。
现在,我们已经研究了API网关如何在应用程序及其客户端之间进行中介,现在让我们研究如何在微服务之间实现通信。
服务间通信机制
微服务体系结构的另一个主要区别是应用程序的不同组件如何交互。 在整体应用程序中,组件通过常规方法调用相互调用。 但是在微服务架构中,不同的服务在不同的流程中运行。 因此,服务必须使用进程间通信(IPC)进行通信。
同步HTTP
微服务体系结构中有两种主要的进程间通信方法。 一种选择是采用基于HTTP的同步机制,例如REST或SOAP。 这是一种简单且熟悉的IPC机制。 它具有防火墙友好性,因此它可以在Internet上运行,并且实现请求/答复通信方式很容易。 HTTP的缺点是它不支持其他通信模式,例如发布-订阅。
另一个限制是客户端和服务器都必须同时可用,但并非总是如此,因为分布式系统容易出现部分故障。 另外,HTTP客户端需要知道主机和服务器的端口。 虽然这听起来很简单,但并非完全简单,尤其是在使用自动扩展的云部署中,服务实例是短暂的。 应用程序需要使用服务发现机制。 一些应用程序使用服务注册表,例如Apache ZooKeeper或Netflix Eureka 。 在其他应用程序中,服务必须注册到负载均衡器,例如Amazon VPC中的内部ELB 。
异步消息传递
同步HTTP的替代方法是基于异步消息的机制,例如基于AMQP的消息代理。 这种方法有很多好处。 它使消息生产者与消息使用者分离。 消息代理将缓冲消息,直到使用者能够处理它们为止。 生产者完全不了解消费者。 生产者只需与消息代理对话,而无需使用服务发现机制。 基于消息的通信还支持多种通信模式,包括单向请求和发布-订阅。 使用消息传递的一个缺点是需要消息代理,这是增加系统复杂性的又一动态部分。 另一个缺点是,请求/回复方式的通信不自然。
两种方法都有优点和缺点。 应用程序很可能会同时使用两者。 例如,在下一节讨论如何解决分区体系结构中出现的数据管理问题,您将看到如何同时使用HTTP和消息传递。
分散数据管理
将应用程序分解为服务的结果是数据库也已分区。 为了确保松耦合,每个服务都有自己的数据库(模式)。 此外,不同的服务可能使用不同类型的数据库-所谓的多语言持久性体系结构。 例如,需要ACID交易的服务可能使用关系数据库,而操纵社交网络的服务可能使用图形数据库。 对数据库进行分区是必不可少的,但是现在我们要解决一个新问题:如何处理那些访问多个服务拥有的数据的请求。 首先让我们看一下如何处理读取请求,然后看一下更新请求。
处理读取
例如,考虑一个每个客户都有信用额度的在线商店。 当客户尝试下订单时,系统必须验证所有未结订单的总和不会超过其信用额度。 在单片应用程序中实现此业务规则将是微不足道的。 但是,在由CustomerService管理客户,由OrderService管理订单的系统中实施此检查要困难得多。 OrderService必须以某种方式必须访问CustomerService维护的信用额度。
一种解决方案是让OrderService通过对CustomerService进行RPC调用来检索信用额度。 这种方法易于实现,并确保OrderService始终具有最新的信用额度。 缺点是它降低了可用性,因为必须运行CustomerService才能下订单。 由于额外的RPC调用,它还增加了响应时间。
另一种方法是让OrderService存储信用额度的副本。 这样就无需向CustomerService发出请求,从而提高了可用性并减少了响应时间。 但是,这的确意味着我们必须实现一种机制,以便在CustomerService中更改OrderService的信用额度副本。
处理更新请求
在OrderService中保持信用额度为最新的问题是处理更新多个服务拥有的数据的请求的更一般问题的一个示例。
分布式交易
一种解决方案,当然是使用分布式事务。 例如,在更新客户的信用额度时,CustomerService可以使用分布式事务来更新其信用额度和由OrderService维护的相应信用额度。 使用分布式事务将确保数据始终保持一致。 使用它们的缺点是,由于所有参与者都必须可用才能提交事务,因此降低了系统可用性。 而且,分布式事务确实已不受欢迎,并且通常不受到现代软件堆栈(例如REST,NoSQL数据库等)的支持。
事件驱动的异步更新
另一种方法是使用事件驱动的异步复制。 服务发布事件,宣布某些数据已更改。 其他服务订阅这些事件并更新其数据。 例如,当CustomerService更新客户的信用额度时,它将发布CustomerCreditLimitUpdatedEvent,其中包含客户ID和新的信用额度。 OrderService订阅这些事件并更新其信用额度副本。 事件的流程如图6所示。
图6-使用事件复制信用额度
这种方法的主要好处是事件的生产者和消费者是分离的。 这不仅简化了开发,而且与分布式事务相比,它提高了可用性。 如果使用者无法处理事件,则消息代理将事件排队,直到可以处理为止。 这种方法的主要缺点是,为了获得可用性,要交换一致性。 必须以可以容忍最终一致数据的方式编写应用程序。 开发人员可能还需要实施补偿事务以执行逻辑回滚。 尽管有这些缺点,但是对于许多应用程序,这是首选方法。
重构整体
不幸的是,我们并非总是拥有从事全新的未开发项目的奢侈。 您很有可能是负责巨大,令人恐惧的整体应用程序的团队的成员。 而且,每天您都在处理本文开头所述的问题。 好消息是,您可以使用一些技术将整体式应用程序分解为一组服务。
首先,停止使问题恶化。 不要通过在整体中添加代码来继续实现重要的新功能。 相反,您应该找到一种将新功能实现为独立服务的方法,如图7所示。这可能并不容易。 您将不得不编写混乱,复杂的粘合代码,以将服务与整体集成在一起。 但这是打破整体结构的良好第一步。
图7-提取服务
其次,确定整体组件的组成部分,以使其成为具有凝聚力的独立服务。 提取的良好候选对象包括不断变化的组件或具有冲突资源需求的组件,例如大型内存缓存或CPU密集型操作。 表现层也是另一个很好的候选人。 然后,您将组件变成服务,并编写粘合代码以与应用程序的其余部分集成。 再次,这可能会很痛苦,但是它使您能够逐步迁移到微服务体系结构。
摘要
整体架构模式是用于构建企业应用程序的常用模式。 对于小型应用程序,它工作得很好:开发,测试和部署小型整体应用程序相对简单。 但是,对于大型,复杂的应用程序,单片架构成为开发和部署的障碍。 连续交付很困难,而且您经常被永久地锁定在最初的技术选择中。 对于大型应用程序,使用微服务体系结构将应用程序分解为一组服务更有意义。
微服务架构具有许多优势。 例如,单个服务更易于理解,并且可以独立于其他服务进行开发和部署。 使用新的语言和框架也容易得多,因为您可以一次尝试一项服务中的新技术。 微服务架构也有一些明显的缺点。 特别是,应用程序要复杂得多,并且具有更多的活动部件。 您需要高水平的自动化(例如PaaS)才能有效使用微服务。 开发微服务时,您还需要处理一些复杂的分布式数据管理问题。 尽管存在这些缺点,微服务体系结构仍适用于快速发展的大型复杂应用程序,尤其是对于SaaS风格的应用程序。
有多种策略可以将现有的单片应用程序逐步发展为微服务体系结构。 开发人员应将新功能实现为独立服务,并编写粘合代码以将服务与整体集成。 迭代地标识要从整体中提取并转化为服务的组件也很有意义。 尽管发展起来并不容易,但它比尝试开发和维护笨拙的整体应用程序要好。
关于作者
是一名开发人员和架构师。 他是Java冠军,JavaOne摇滚明星和POJO in Action的作者,他描述了如何使用POJO和诸如Spring和Hibernate之类的框架构建企业Java应用程序。 克里斯还是原始Cloud Foundry的创始人,Cloud Foundry是Amazon EC2的早期Java PaaS。 他向组织提供咨询,以改善他们使用云计算,微服务和NoSQL等技术开发和部署应用程序的方式。 Twitter @crichardson 。
服务部署的垂直伸缩