您还未登录! 登录 | 注册 | 帮助  

您的位置: 首页 > 软件开发专栏 > 开发技术 > 正文

如何从复杂单体应用快速迁移到微服务?

发表于:2019-01-07 作者:沈建苗 来源:51CTO技术栈

想必你已知道了微服务及其工作原理,现在是时候探讨如向微服务转变这个关键话题了。

为什么要向微服务转变


整体式(monolithic)应用程序很庞大(代码行数方面)、很复杂(功能依赖和数据等方面),为跨地区的成千上万用户提供服务,需要多个开发人员和 IT 工程师。

整体式应用程序可能类似下图:

图 1:整体式应用程序的基本结构

有时,即使具有所有这些特点,应用程序最初也可能顺畅运行,可能在应用程序可扩展性或性能方面不会遇到挑战。但用着用着会出现问题,问题因应用程序而异。

比如对于云或 Web 应用程序而言,由于更多用户使用服务,你可能遇到可扩展性问题,或者由于更长的构建时间和回归测试,定期发布新的更新可能变得成本高、难度大。

如图 2 所示,整体式应用程序的用户或开发人员可能遇到右边列出的一个或多个问题。

图 2:整体式应用程序的潜在问题

这时迁移到微服务可能听起来不仅仅是时髦的想法,更像是大救星。应用程序的迁移会类似图 3 所示:

图 3:从整体式应用程序向微服务转变

那么,如何进行这种转变?有两种可能的场景:

  • 创建全新的应用程序。
  • 转换或迁移已经存在的整体式应用程序。

后一种场景的可能性大得多,但无论目前的情况如何,都有必要了解这两种场景的来龙去脉。

使用微服务创建新应用程序


我还没有看到很多从头开始构建基于微服务的应用程序的真实场景。通常,应用程序已部署到位,我搞过的大多数应用程序更多是从整体式架构向微服务架构转变。

在这种情况下,架构师和开发人员的意图一直是重用一些现有的实现。但由于技能在市场上非常普遍、一些成功的实现发布,我们会看到从头开始构建基于微服务的应用程序的更多例子,因此当然有必要探究这种场景。

假设你已摸清了所有需求,准备好处理要构建的应用程序的架构设计。你在入手时需要考虑许多常见的最佳实践,这些实践在下面各节中有介绍。

组织准备


你要问自己的第一个问题是,贵组织是否准备好向微服务转变。

这意味着贵组织的各个部门现在需要从以下方面对软件的构建和发布进行不同的思考:

  • 团队结构。整体式应用程序团队(如果有的话)需要分解成几个知道微服务最佳实践或受到培训的小规模高绩效团队。

如图 3 所示,新系统将包含一组独立服务,每个服务负责提供特定服务。这是微服务模式的一大优势:减少了通信开销,包括多场不间断会议。

团队应按照所要解决的业务问题或领域加以组织。然后,沟通变为敲定要遵循的一套标准/协议,那样这些微服务就能彼此协作。

  • 每个团队必须准备独立于其他团队运作。它们的规模应相当于标准的 Scrum 团队,否则沟通会再次成为问题。执行是关键,每个团队都应能够满足不断变化的业务要求。
  • 工具和培训。一个关键要求是组织准备投入于新工具和人员培训的情况。在大多数情况下,现有的工具和流程需要停用,采用一套新的。

这需要投入大量资本,致力于招聘拥有新技能的人员,并重新培训现有工作人员。从长远来看,如果采用微服务的决定是正确的,组织会看到成本节省,因而收回投入。

基于服务的方法


与整体式应用程序不同,若是微服务,你需要采用自我维持的基于服务的方法。

应用程序好比是一组松散耦合的服务,这些服务相互通信以提供完整的应用程序功能。

应将每项服务视为有其生命周期的独立服务,可由独立团队开发和维护。这些团队要从各种技术中进行选择,包括最适合其服务要求的语言或数据库。

比如针对电子商务站点,团队要编写一个完全独立的使用内存数据库的服务,比如购物车微服务,以及使用关系数据库的另一项服务,比如订购微服务。

实际应用程序可能将微服务用于基本功能,比如身份验证、帐户、用户注册和通知,业务逻辑封装在 API 网关中,API 网关基于客户端和外部请求调用这些微服务。

提醒一下:微服务可能是一个开发人员实现的小服务,也可能是需要多个开发人员的复杂服务。就微服务而言,大小无关紧要;它完全依赖服务要提供的一项功能。

此时必须考虑的其他方面是扩展、性能和安全。扩展要求可能不一样,应在每个微服务层面根据需要来提供。应在所有层面考虑安全,包括静态数据、进程间通信和传输中数据等。

进程间(服务到服务)通信


必须考虑的关键方面是安全和通信协议。异步通信是最佳选择,因为它可确保所有请求正常运行,不会长时间占用资源。

使用 RabbitMQ 等消息总线可能有利于这种通信。它很简单,可以扩展到每秒数十万条消息。

为防止消息传递系统在发生故障后成为单一故障点,必须正确设计消息传递总线以实现高可用性。其他选项包括另一种轻量级消息传递平台 ActiveMQ。

安全是该阶段的关键。除了选择正确的通信协议外,可使用 AppDynamics 之类的行业标准工具来监控和衡量进程间通信。须自动向安全团队报告任何异常情况。

若有数千个微服务,处理一切确实变得复杂起来。后面会解释如何借助发现服务和 API 网关解决此类问题。

技术选择


向微服务转变的最大优势是让你可以选择。每个团队可以独立选择最适合特定微服务的语言、技术和数据库等。

若采用整体式方法,团队通常没有这样的灵活性,因此确保你没有忽视并错过该机会。

即使团队在处理多个微服务,也要将每个微服务视为独立的服务并进行分析。

为每个微服务选择技术时,必须牢记可扩展性、部署、构建时间、集成和插件可操作性等。

如果是数据较少但访问较快的微服务,内存数据库可能最合适,而其他微服务可能使用同样的关系数据库或 NoSQL 数据库。

实现


实现是关键阶段,这时候所有培训和最佳实践知识派得上用场。

要记住的几个关键方面包括:

  • 独立性。每个微服务都应高度自主,有自己的生命周期并以此进行处理。它的开发和维护不需要依赖其他微服务。
  • 源代码控制。必须部署适当的版本控制系统,每个微服务要遵循标准。统一代码库也很有帮助,因为它可以确保所有团队使用相同的源代码控制。

它有助于代码审查等各个方面,便于在一个地方访问所有代码。长远来看,有必要对所有服务实行同样的源代码控制。

  • 环境。所有不同的环境(如开发、测试、模拟和生产等阶段)必须得到适当的保护和自动化。这里的自动化包括构建过程。

那样,可以根据需要集成代码,大多每天进行。有几种工具可用,不过 Jenkins 广泛使用。Jenkins 是一种开源工具,有助于软件构建和发布过程实现自动化,包括持续集成和持续交付(CI/CD)。

  • 故障安全。软件故障不可避免。须在微服务开发中解决好下游服务的故障处理问题。其他服务的故障必须是隐形的,好让用户看不到故障。

这包括管理服务响应时间(超时)、处理下游服务的 API 更改以及限制自动重试次数。

使用微服务时,别害怕通过使用复制粘贴来重用代码,但这么做要有限制。

这可能导致代码重复,但这胜过使用最终耦合服务的共享代码。微服务中,你需要的是去耦,不是紧耦合。

比如说,你将编写代码以使用服务的输出响应。每次从任何客户端调用相同的服务时,你可以复制此代码。

重用代码的另一种方法是创建公共库。多个客户端可以使用相同的库,但随后每个客户端应负责维护其库。

如果你创建太多的库,每个客户端维护不同的版本,有时变得困难重重。这种情况下,你要包含相同库的多个版本,由于向后兼容性和类似问题,构建过程可能变得困难。

只要你可以控制客户端的库和版本数量,并对它们实行严格的流程,可以采用任何一种方式,这就看你的需要了。这肯定有助于避免大量的代码重复。

鉴于微服务数量庞大,调试问题可能会变得困难,因此你需要在此阶段进行某种检测。

最佳实践之一是使用唯一的请求 ID 标记每个请求,并记录每个请求。这个唯一的 ID 标识始发请求,应由每个服务传递给任何下游请求。

看到问题后,你可以通过日志清楚地回溯并找出有问题的服务。如果你建立集中式日志记录系统,该解决方案最有效。

所有服务都应以标准化格式将所有消息记录到此共享系统,以便团队可以根据需要从一个地方(从基础设施到应用程序)重放事件。用于集中式日志的共享库值得研究。

市面上有几种很理想的日志管理和聚合工具,比如 ELK(Elasticsearch、Logstas和Kibana)以及 Splunk。

部署


自动化是部署过程中的关键。没有它,微服务模式几乎不可能成功。可能有成百上千的微服务,对于敏捷交付而言,自动化必不可少。

设想部署数千个微服务并维护。其中一个微服务发生故障后会发生什么?怎么知道哪台机器有足够的资源来运行你的微服务?

若没有落实自动化,应对这种情况变得非常复杂。Kubernetes 和 Docker Swarm 等各种工具可用于自动化部署过程。

操作


整个过程的操作部分也需要自动化。这里谈论的同样是成百上千的微服务,组织能力需要足够成熟才能处理这种复杂程度。

你需要一个支持系统,包括以下方面:

  • 从基础设施、应用程序 API 到最后一英里性能,全部都要加以监控,并实施具有适当阈值的自动警报。考虑构建问题出现后弹出数据和警报的实时仪表板。
  • 按需可扩展性。若使用微服务,扩展是最简单的任务。配置你想要扩展的微服务的另一个实例,将它放在现有的负载均衡器后面就行。

但在规模化环境中,这也需要实现自动化。只需设置一个整数值,告诉想要为特定微服务运行的实例数量。

  • API 公开。在大多数情况下应该对外公开 API 以供外部用户使用。最好使用边缘服务器来完成这项任务,该服务器可以处理所有外部请求。

它可以使用 API 网关和发现服务来完成任务,你可以针对每种设备类型(比如移动设备或浏览器)或用例使用一台边缘服务器。Netflix 开发的一款开源应用软件 Zuul 可用于此功能及其他功能。

  • 断路器。向故障服务发送请求毫无意义。因此可以构建断路器(circuit breaker),跟踪针对每个服务的每个请求的成功或故障。若出现多个故障,应阻止对该特定服务的所有请求一段指定的时间(即断开电路)。

指定时间过后,应进行另一次尝试,依此类推。一旦响应成功,重新连接电路。这应该在服务实例层面进行。Netflix 的 Hystrix 提供了开源断路器实现。

将整体式应用程序迁移到微服务


虽然构建基于微服务的新应用程序的大多数最佳实践也适用于迁离现有的整体式应用程序,但如果遵循另外一些准则,可使迁移更简单、更高效。

虽然将整个整体式应用程序转换成完全基于微服务的应用程序听起来可能很正确,但将每项功能转换成微服务可能并不高效,在一些情况下可能成本很高。

毕竟,你到头来要从头开始编写应用程序。正确的迁移方式可能需要逐步进行,如图 4 所示:

图 4:基本的迁移步骤,从整体式应用程序到微服务

下一个问题是:目前的整体式应用程序从何处入手?如果应用程序确实很旧,进行分解很耗时、难度大,从头开始可能更好。

在可以快速禁用部分代码、技术架构并不完全过时的其他情况下,最好先将组件重新构建为微服务,并换掉旧代码。

微服务标准


那么问题变成了哪些组件应该先迁移或甚至要不要迁移。这让我想到了所谓的“微服务标准”,这概述了选择应迁移到微服务的功能并确定优先级的可行方法之一。

它们是你制定的一套规则,根据组织当时的要求,决定将现有整体式应用程序的组件转换成微服务是否适合。

这时机在这里很重要,因为组织的要求可能不断变化,你可能要回过头来,将更多组件转换成微服务。

换句话说,由于要求变化,整体式应用程序的额外组件可能适合转换。以下是转换过程中被视为微服务标准的几个最佳实践:

①你需要确定哪些功能频繁使用

先将频繁使用的服务或应用程序功能转换成微服务。记住:微服务只执行一个明确定义的服务。牢记这个原则,相应地划分应用程序。

②可能存在性能不佳的组件,其他替代方案随时可用

可能有开源插件,或者你可能想从头开始构建服务。应牢记的要点之一是微服务的边界。

只要你设计的微服务只做一件事,就很好。确定边界常常很难,你会发现实践一下会更容易。

查看微服务边界的另一种方法是,应该几周内就能重写整个微服务,而不是花几个月来重写服务。

③更好的技术替代方案或多语言编程

针对特定领域的语言可用于帮助解决问题域(problem domain)。这尤其适用于过去你收到许多改进请求,预计将来会继续如此的组件。

如果你不仅认为可以使用市面上的新语言或功能简化这种组件的实现,将来的维护和更新还会变得更容易,现在正是应对这种变化的时候。

在其他情况下,你可能发现另一种语言提供的并发抽象比目前使用的语言更容易。

可以将新语言用于特定的微服务,而应用程序的其余部分仍然使用不同的语言。

同样,你可能希望一些微服务非常快,可能决定用 C 语言编写以获得最大效益,而不是用另一种高级语言编写。说到底是要利用这种灵活性。

④存储替代方案或多语言持久性

大数据大行其道,如果使用 NoSQL 数据库而不是关系数据库,应用程序的一些组件可能会提供价值。

如果应用程序中的任何此类组件可得益于该替代方案,可能正是改用 NoSQL 的时候。

这些是你应该为整体式应用程序中的每个服务或功能而考虑的关键方面,你需要先注重这几项的转换。一旦你从高优先级的部分获得了价值,随后可以运用其他规则。

⑤修改请求

任何软件生命周期中要跟踪的一个重要方面是新的改进请求或更改。由于构建和部署时间,更改请求数量更多的功能可能适用于微服务。

分离这类服务可以缩短构建和部署时间,因为你不必构建整个应用程序,只需更改微服务,这还可以为应用程序的其余部分提高可用性时间。

⑥应用程序的某些部分总是增加部署的复杂性

在整体式应用程序中,即使某项功能未加变动,你仍得完成整个构建和部署过程。

如果存在这种情况,划分这些组件并用微服务取代大有助益,这样可以为整体式应用程序的其余部分缩短总的部署时间。

⑦辅助服务

在大多数应用程序中,核心或主要的服务依赖一些辅助服务。没有这类辅助功能,可能会影响核心服务的可用性。

比如在求助台应用程序中,工单依赖产品目录服务。如果产品目录服务不可用,用户无法提交工单。

如果存在这种情况,应将辅助服务转换成微服务,并确保高可用性,以便它们可以更好地服务于核心服务。(这些又叫断路器服务。)

视应用程序而定,这些标准可能需要将大多数服务转换成微服务,目的是简化转换过程,那样你可以确定优先级,并为迁移到基于微服务的架构制定路线图。

为服务重新设计架构


一旦确定了要迁移的转换成微服务的功能,可以遵循前面所述的最佳实践,开始为已选择的服务重新设计架构。

以下是需要牢记的几个方面:

  • 微服务定义。为每个功能定义适当的微服务,应包括通信机制(API)和技术定义等。

考虑现有功能使用的数据,或针对微服务相应地创建和规划数据策略。如果该功能在 Oracle 之类的密集数据库上,迁移到 MySQL 是否有意义?

确定你将如何管理数据关系。最后,将每个微服务作为单独的应用程序来运行。

  • 重构代码。如果你未改变编程语言,可以重用一些代码。考虑存储/数据库层:共享 vs 专用,内存中 vs 外部。

目的在于除非需要,否则不添加新功能,而是重新打包现有代码并公开所需的 API。

  • 开始编码之前,确定源代码控制和版本控制机制,并确保遵循这些标准。每个微服务都是单独的一项,作为单独的应用程序来部署。
  • 数据迁移。如果你决定创建新数据库,还要迁移旧数据。这通常通过编写简单的 SQL 脚本来完成,具体取决于你的源代码和目的地。
  • 整体式代码。最初将现有代码留在整体式应用程序中,以防万一要回滚。你可以更新其余代码以使用新的微服务,或者划分应用程序流量(如果可能),同时使用整体式版本和微服务版本。

这让你有机会测试和关注性能。一旦有信心,你可以将所有流量迁移到微服务,禁用或删除旧代码。

  • 独立地构建、部署和管理。要独立构建和部署每个微服务。推出新版本的微服务时,可以再次划分旧版本和新版本之间的流量。

这意味着你可能在生产环境中运行相同微服务的两个或更多版本。一些用户流量可以路由到新的微服务版本,确保服务正常运行、性能良好。

如果新版本未在最佳状态下运行,很容易将所有流量回滚到先前版本,并将新版本退回给开发团队。这里的关键是建立可重复的自动化部署流程,竭力实现持续交付。

  • 删除旧代码。只有在确认一切已正确迁移并按预期运行后,才能删除临时代码,并从旧存储位置删除数据。务必在此过程中做好备份。

微服务的混合方法


开发人员编写全新的应用程序时,可以直接遵循微服务架构原则和蓝图来构建应用软件。开发人员有时遵循微服务和整体式应用程序的混合方法。

在这种情况下,他们可以将应用程序的部分组件开发成微服务,其余部分基于某些标准遵循标准的 SOA/MVC 实践。

其想法是,并非应用程序的所有组件都可以转换成微服务。微服务提供了很大的灵活性,但这种灵活性要付出一些代价。

混合方法旨在兼顾灵活性和成本这两方面,以后可以根据需要从整体式应用程序获取组件、转换成微服务。关键是在这个转变期间牢记这两种方法以及微服务标准。