为什么通过微服务的方法构建应用程序?

作为软件开发人员,我们已知道思考如何将应用程序分解成数个组件。这是以对象为导向、软件抽象和组件化编程的核心方法。 现在,这种分解的结果,往往以共享库和高层模块的类与接口的形式呈现。一般的,最常见的分层的模式,就是有后端存储、中间层业务逻辑和前端用户界面这种分层方法。而最近几年我们作为开发人员的变化,就是我们在更多的构建云业务驱动的、基于云的分布式应用程序。

不断变化的业务需求包括:

  • 为吸引新地理区域的客户而大规模构建和操作的一项服务。
  • 更快速地提供特性与功能,灵活应对客户的需求。
  • 提高资源利用率以降低成本。

而这些业务要求,则在改变着我们构建应用程序的方法。

单一式设计方法与微服务设计方法

所有的应用程序都会随着时间的推移而发展。成功的应用之所以成功,源自于它对用户的实用价值。而失败的应用,大多都是由于无法自身的发展而被其它应用取代。问题在于,我们对当下的需求了解了多少,以及这些需求在未来又会有怎样的变化。举例来说,假若我们需要为某一个部门创建报表应用程序,可以确定应用程序仅仅在公司内部使用,而且报表的生命周期非常短暂。那么我们所选择的构建方法,肯定不同于构建一个能够向千万用户播放视频内容的应用程序的构建方式。

在应用程序可以在之后再设计的情况下,有的时候向外寻求一些可靠的理念和信息才是直接驱动的因素。因为过度设计一个永远不会被使用的功能并没有太多的意义。这就是所谓的工程取舍。另一方面来看,当企业的决策者在谈论构建云时,其实都在期待着应用的增长和使用。问题在于,增长量的规模都非常难以预测。我们想要能够快速的构建一个产品原型,而与此同时我们还要能够时刻明确当前的方向是否是正确的,这就是一个扩展思路的概述:构建、衡量、学习、迭代。

在“客户端-服务器”模式的开发时代,我们都倾向专注于构建分层的应用程序,而每一层都采用不同的技术来开发。针对这种构建方法,已经派生出了“单一式”这个词。在各个层次之间往往存在大量接口,而在一层之内则采用非常紧密耦合的设计。开发人员可以分解一些已编译的库或链接一些其它的库来对其进行扩展。

这类单一式应用程序的设计方法有一些优点,就是设计非常简便,不同组件直接可以通过共享内存或进程间通信的方式来进行调用,因此调用速度很快。此外,每个人单独只测试一个模块,人力资源的运用也更有效率。缺点也非常明显,每层直接高度紧密的耦合,无法单独扩展单个组件。如果需要进行一部分的修复或升级,那么必须要让所有人都进行测试,这导致了它非常难以发挥灵活性。

微服务设计方法解决了上述这些缺点,更密切的配合上述业务要求,但它本身也是优点与缺陷并存。微服务的优点是,每个应用通常封装了较为简单的业务功能,可以独立增加、减少、测试、部署、管理。微服务方法的一个重要优点就是团队倾向于以业务方案为导向,而不是以分层方法建议的技术设计为导向。在实际开发过程中,每一个小团队都可以根据实际需要,采用任意技术来开发微服务。

换句话说,组织是不需要为了维护各个微服务应用程序而将技术标准化的。负责某个服务的团队可以根据团队的专业知识情况和实际需求,各自发挥所长。实际上,各个团队应当给出一个推荐方案,例如特定的NoSQL存储或Web应用程序的框架。

微服务的缺点也非常多。它需要管理越来越多的独立实体、处理更复杂的部署和版本控制。微服务之间的通信会导致网络流量也延迟的增加。根据经验,衡量颗粒化的服务的性能是非常难以实现的,如果没有工具能够帮助我们查看到服务之间的依赖关系,我们非常难以看清整个系统。

在通信方式上,我们可以设定标准、达成共识。在设计微服务方法时,我们可以只关注“获得什么结果”,而不是只遵循僵化的约定。必须在系统设计的初期定义这些约定,因为之后的服务都会由独立的团队来进行更新。在设计微服务方法时其实有另一种描述,名为“面向服务的架构”,也就是SOA。

简而言之,微服务设计方法,指的是将多个分离的服务联合,各自独立更新,并达成一致的通信标准。

随着越来越多基于云的应用的生成,人们发现,从长远来看,这种将整体应用分解成独立、注重业务的服务的做法是非常优秀的方法。

应用程序开发方法的比较

Monolithic and MSA

  1. 单一式应用特定模块包含特定的功能,通常按照功能层来划分,例如 Web、业务和数据。
  2. 单一式应用一般可通过复制到多个服务器/虚拟机/容器上进行扩展。
  3. 微服务应用将单个功能分隔成许多单个较小的服务。
  4. 微服务应用可以可通过独立部署每个服务而扩展,跨服务器/虚拟机/容器创建这些服务的实例。

使用微服务方法进行设计并非所有项目的银弹,但确实更符合之前所述的业务目标。如果项目是一个标准的单一式应用程序并且有计划重构,那么将其调整为微服务架构是非常可行的。而更常见的一种情形,就是从单一式应用程序入手,从需要提高可扩展性或敏捷性的功能区域开始,分阶段慢慢分解它。

总而言之,微服务方法是以许多小服务来组成一个应用程序,这些服务在部署于一组计算机上或容器中运行,分别由较小的团队针对方案来开发服务,且每个服务独立进行测试、版本控制、部署和扩展,由此推动整个应用程序的开发进程。

什么是微服务?

微服务存在多种定义。 如果搜索 Internet,会发现许多有用的资源,这些资源提供了自己的观点和定义。 但在微服务的以下大部分特性上,已广泛达成共识:

  • 封装客户方案或业务方案。 要解决什么问题?
  • 由小型工程团队开发。
  • 使用任意编程语言编写并使用任意框架。
  • 由独立控制版本、部署及扩展的代码和(可选)状态组成。
  • 通过定义完善的接口和协议来与其他微服务交互。
  • 具有用来解析位置的唯一名称 (URL)。
  • 在出现故障时可保持一致且可用。

一言以蔽之:

微服务应用程序,是由独立控制版本和可扩展的、以客户为中心的服务组成,这些服务通过标准协议和定义完善的接口彼此通信。

我们在上一部分已介绍了前两点,接下来进一步澄清其他各要点。

使用任意编程语言编写并使用任意框架

作为开发人员,我们应该根据本身的技能或服务需求,自由选择所需的语言或框架。 在某些服务中,可能认为C++的性能优点更加重要, 而在其他服务中,C# 或 Java 的更简易的管理开发可能才是最重要的。 在某些情况下,可能需要使用特定合作伙伴库、数据存储技术,或向客户端公开服务的方式。

选择技术之后,接下来的课题就是服务的操作或生命周期管理和扩展。

允许独立控制版本、部署及扩展的代码和状态

无论选择何种方式来编写微服务,代码和状态都应该独立部署、升级和扩展。 这确实是难以解决的问题之一,因为这涉及到所选的技术。 在扩展方面,难以了解如何分区代码和状态。 当代码和状态使用不同的技术时(目前的普遍情况),微服务的部署脚本必须能够妥善扩展两者。 这也关乎到灵活性和弹性,以便可以升级某些微服务,而无需一次性全部升级。

暂时回到单一式方法和微服务方法的比较,下图显示了状态存储方法的差异。

应用程序样式之间的状态存储

Service Fabric 平台状态存储

左侧的单一式方法具有单一数据库和多层的特定技术。

右侧的微服务方法显示互连的微服务图,其中状态通常以微服务为范围,并使用各种技术。

在单一式方法中,应用程序通常使用单一数据库。 优点是这是单一位置,很容易部署。 每个组件可以通过单个表来存储其状态。 困难之处在于团队必须严格区分状态。 无可避免地就想将新的列添加到现有客户表、在表之间执行联接,并且对存储层形成依赖性。 发生这种情况后,无法扩展各个组件。

在微服务方法中,每个服务都管理并存储自己的状态。 每个服务将负责同时扩展代码和状态,以满足服务的需求。 不足的是,需要创建应用程序数据的视图或查询时,必须跨不同的状态存储进行查询。 为了解决此问题,通常由一个独立的微服务构建一个跨许多微服务的视图。 如果需要对数据执行多个即席查询,每个微服务应该考虑将其数据写入数据仓库服务以供脱机分析。

版本控制特定于部署的微服务版本,以便能够部署和并行运行多个不同的版本。 当较新版的微服务在升级期间失败,因而需要回滚到旧版时,版本控制可以解决这种情况。 版本控制的另一种情况是执行 A/B 式测试,其中不同的用户将体验到不同版本的服务。 例如,在更广泛推出新功能之前,通常先对一组特定的客户升级微服务以测试新功能。 在微服务的生命周期管理之后,便可以在微服务之间的通信。

通过定义完善的接口和协议来与其他微服务交互

本主题无需花费太多时间,因为过去 10 年来发布的大量关于面向服务的体系结构的文献对通信模式进行了介绍。 一般而言,服务通信使用 REST 方法,并配合 HTTP 与 TCP 协议及 XML 或 JSON 作为序列化格式。 从接口观点来看,这涉及到采用 Web 设计方法。 但仍可以使用二进制协议或自己的数据格式。 如果这些协议和格式非公开可用,微服务使用起来就很难,因此要有心理准备。

具有用来解析位置的唯一名称 (URL)

记得我们一直在说,微服务与 Web 有点类似吗? 就像 Web 一样,微服务无论在何处运行,都必须可寻址。 若要在计算机上运行特定微服务,很快就会陷入困境。

就像 DNS 解析特定计算机的特定 URL 一样,微服务需要有唯一的名称来发现它目前所在的位置。 微服务需要有可寻址的名称才能独立于它们运行所在的基础结构之外。 这意味着服务的部署和发现方式之间互相影响,因为需要有服务注册表。 同样地,当计算机发生故障时,注册服务必须指出服务现在的运行位置。

接下来的主题:复原能力和一致性。

在出现故障时可保持一致且可用

处理意外发生的故障是非常难以解决的问题之一,尤其是在分布式系统中。不但开发人员需要花费大量代码来处理各类异常,测试人员也需要花费大量时间。我们面对的问题比使用代码来处理故障更加复杂:当运行的微服务发生故障时该怎么办?不仅需要检测到这种故障,这已经非常棘手,还需要设法修正这些故障。

微服务必须能够从故障恢复。出于对可用性的考虑,通常还要求必须能够在另一台计算机上重新启动。这也涉及到了微服务存储的状态、微服务可以从何处恢复状态以及微服务能否成功恢复该状态。换句话说,必须能够在发生故障时恢复计算任务、状态以及数据。

在其他情况下,复原能力的问题更难处理,例如应用程序升级期间失败。 在配合部署系统一起运行时,微服务不仅需要恢复。 它还需要确定是要继续升级到更新版本,还是回滚到旧版以维持一致的状态。 需要考虑一些问题,例如,是否有足够的计算机可用于继续升级,以及如何恢复旧版的微服务。 需要微服务发出运行状况信息才能做出这些决定。

报告运行状况和诊断

微服务必须报告其运行状况和诊断,这一点看似明显,但却经常被忽视。 否则,难以从操作观点上深入了解。 面临的难题是关联一组独立服务的诊断事件并修正计算机时钟偏差以识别事件顺序。 同样地,通过议定的协议和数据格式来与微服务交互时,需要将运行状况和诊断事件的记录方式标准化,这些事件最终将写入可供查询和查看的事件存储。 在微服务方法中,关键在于不同团队同意采用单一记录格式。 需要有一致的方法来查看整个应用程序中的诊断事件。

运行状况与诊断不同。 运行状况是指微服务报告其当前状态,以便采取适当的措施。 一个很好的例子便是使用升级和部署机制来保持可用性。 虽然当前服务可能由于进程崩溃或计算机重新启动而状况不正常,但服务可能仍可运行。 不应该执行升级而让情况恶化。 最好是先进行调查,或让微服务有时间恢复。 微服务中的运行状况事件可以帮助我们制定明智的决策,并且实际上也有助于创建自愈服务。