微服务架构:拆分单体应用的难点

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 微服务架构:拆分单体应用的难点

5 拆分单体应用为服务的难点


从表面上看,通过定义与业务能力或子域相对应的服务来创建微服务架构的策略看起来很简单。但是,你可能会遇到几个障碍:

  • 网络延迟。
  • 同步进程间通信导致可用性降低。
  • 在服务之间维持数据一致性。
  • 获取一致的数据视图。
  • 上帝类阻碍了拆分。

让我们来看看每个问题,先从网络延迟开始。

 

网络延迟

 

网络延迟是分布式系统中一直存在的问题。你可能会发现,对服务的特定分解会导致两个服务之间的大量往返调用。有时,你可以通过实施批处理API在一次往返中获取多个对象,从而将延迟减少到可接受的数量。但在其他情况下,解决方案是把多个相关的服务组合在一起,用编程语言的函数调用替换昂贵的进程间通信。

 

同步进程间通信导致可用性降低

 

另一个需要考虑的问题是如何处理进程间通信而不降低系统的可用性。例如,实现createOrder()操作最常见的方式是让Order Service使用REST同步调用其他服务。这样做的弊端是REST这样的协议会降低Order Service的可用性。如果任何一个被调用的服务处在不可用的状态,那么订单就无法创建了。有时候这可能是一个不得已的折中,但是在第3章中学习异步消息之后,你就会发现其实有更好的办法来消除这类同步调用产生的紧耦合并提升可用性。

 

在服务之间维持数据一致性

 

另一个挑战是如何在某些系统操作需要更新多个服务中的数据时,仍旧维护服务之间的数据一致性。例如,当餐馆接受订单时,必须在Kitchen Service和Delivery Service中同时进行更新。Kitchen Service会更改Ticket的状态。Delivery Service安排订单的交付。这些更新都必须以原子化的方式完成。

 

传统的解决方案是使用基于两阶段提交(two phase commit)的分布式事务管理机制。但正如你将在第4章中看到的那样,对于现今的应用程序而言,这不是一个好的选择,你必须使用一种非常不同的方法来处理事务管理,这就是Saga。Saga是一系列使用消息协作的本地事务。Saga比传统的ACID事务更复杂,但它们在许多情况下都能工作得很好。Saga的一个限制是它们最终是一致的。如果你需要以原子方式更新某些数据,那么它必须位于单个服务中,这可能是分解的障碍。

 

获取一致的数据视图

 

分解的另一个障碍是无法跨多个数据库获得真正一致的数据视图。在单体应用程序中,ACID事务的属性保证查询将返回数据库的一致视图。相反,在微服务架构中,即使每个服务的数据库是一致的,你也无法获得全局一致的数据视图。如果你需要一些数据的一致视图,那么它必须驻留在单个服务中,这也是服务分解所面临的问题。幸运的是,在实践中这很少带来真正的问题。

 

上帝类阻碍了拆分

 

分解的另一个障碍是存在所谓的上帝类。上帝类是在整个应用程序中使用的全局类。上帝类通常为应用程序的许多不同方面实现业务逻辑。它有大量字段映射到具有许多列的数据库表。大多数应用程序至少有一个这样的上帝类,每个类代表一个对领域至关重要的概念:银行账户、电子商务订单、保险政策,等等。因为上帝类将应用程序的许多不同方面的状态和行为捆绑在一起,所以将使用它的任何业务逻辑拆分为服务往往都是一个不可逾越的障碍。


Order类是FTGO应用程序中上帝类的一个很好的例子。这并不奇怪:毕竟FTGO的目的是向客户提供食品订单。系统的大多数部分都涉及订单。如果FTGO应用程序具有单个领域模型,则Order类将是一个非常大的类。它将具有与应用程序的许多不同部分相对应的状态和行为。图6显示了使用传统建模技术创建的Order类的结构。


image.png


如你所见,Order类具有与订单处理、餐馆订单管理、送餐和付款相对应的字段及方法。由于一个模型必须描述来自应用程序的不同部分的状态转换,因此该类还具有复杂的状态模型。在目前情况下,这个类的存在使得将代码分割成服务变得极其困难。

 

一种解决方案是将Order类打包到库中并创建一个中央Order数据库。处理订单的所有服务都使用此库并访问访问数据库。这种方法的问题在于它违反了微服务架构的一个关键原则,并导致我们特别不愿意看到的紧耦合。例如,对Order模式的任何更改都要求其他开发团队同步更新和重新编译他们的代码。

 

另一种解决方案是将Order数据库封装在Order Service中,该服务由其他服务调用以检索和更新订单。该设计的问题在于这样的一个Order Service将成为一个纯数据服务,成为包含很少或没有业务逻辑的贫血领域模型(anemic domain model)。这两种解决方案都没有吸引力,但幸运的是,DDD提供了一个好的解决方案。

 

更好的方法是应用DDD并将每个服务视为具有自己的领域模型的单独子域。这意味着FTGO应用程序中与订单有关的每个服务都有自己的领域模型及其对应的Order类的版本。Delivery Service是多领域模型的一个很好的例子。如图7所示为Order,它非常简单:取餐地址、取餐时间、送餐地址和送餐时间。此外,DeliveryService使用更合适的Delivery名称,而不是称之为Order。


image.png


Order Service具有最复杂的订单视图,如图9所示。即使它有相当多的字段和方法,它仍然比原始版本的那个Order上帝类简单得多。


image.png


每个领域模型中的Order类表示同一Order业务实体的不同方面。FTGO应用程序必须维持不同服务中这些不同对象之间的一致性。例如,一旦OrderService授权消费者的信用卡,它必须触发在Kitchen Service中创建Ticket。同样,如果Kitchen Service拒绝订单,则必须在Order Service中取消订单,并且为客户退款。在第4章中,我们将学习如何使用前面提到的事件驱动机制Saga来维护服务之间的一致性。

 

除了造成一些技术挑战以外,拥有多个领域模型还会影响用户体验。应用程序必须在用户体验(即其自己的领域模型)与每个服务的领域模型之间进行转换。例如,在FTGO应用程序中,向消费者显示的Order状态来自存储在多个服务中的Order信息。这种转换通常由API Gateway处理,将在第8章中讨论。尽管存在这些挑战,但在定义微服务架构时,必须识别并消除上帝类。

 

我们现在来看看如何定义服务API。

 

6 定义服务API


到目前为止,我们有一个系统操作列表和一个潜在服务列表。下一步是定义每个服务的API:也就是服务的操作和事件。存在服务API操作有以下两个原因:首先,某些操作对应于系统操作。它们由外部客户端调用,也可能由其他服务调用。另次,存在一些其他操作用以支持服务之间的协作。这些操作仅由其他服务调用。

 

服务通过对外发布事件,使其能够与其他服务协作。第4章将描述如何使用事件来实现Saga,这些Saga可以维护服务之间的数据一致性。第7章将讨论如何使用事件来更新CQRS视图,这些视图支持有效的查询。应用程序还可以使用事件来通知外部客户端。例如,可以使用WebSockets将事件传递给浏览器。

 

定义服务API的起点是将每个系统操作映射到服务。之后确定服务是否需要与其他服务协作以实现系统操作。如果需要协作,我们将确定其他服务必须提供哪些API才能支持协作。首先来看一下如何将系统操作分配给服务。

 

把系统操作分配给服务

 

第一步是确定哪个服务是请求的初始入口点。许多系统操作可以清晰地映射到服务,但有时映射会不太明显。例如,考虑使用noteUpdatedLocation()操作来更新送餐员的位置。一方面,因为它与送餐员有关,所以应该将此操作分配给Courier Service。另一方面,它是需要送餐地点的DeliveryService。在这种情况下,将操作分配给需要操作所提供信息的服务是更好的选择。在其他情况下,将操作分配给具有处理它所需信息的服务可能是有意义的。表4显示了FTGO应用程序中的哪些服务负责哪些操作。


表4 FTGO应用程序的系统操作映射到具体的服务


image.png


把操作分配给服务后,下一步是确定在处理每一个系统操作时,服务之间如何交互。

确定支持服务协作所需要的API

 

某些系统操作完全由单个服务处理。例如,在FTGO应用程序中,Consumer Service完全独立地处理createConsumer()操作。但是其他系统操作跨越多个服务。处理这些请求之一所需的数据可能分散在多个服务周围。例如,为了实现createOrder()操作,Order Service必须调用以下服务以验证其前置条件并使后置条件成立:

 

  • Consumer Service:验证消费者是否可以下订单并获取其付款信息。
  • Restaurant Service:验证订单行项目,验证送货地址和时间是否在餐厅的服务区域内,验证订单最低要求,并获得订单行项目的价格。
  • Kitchen Service:创建Ticket(后厨工单)。
  • Accounting Service:授权消费者的信用卡。


同样,为了实现acceptOrder()系统操作,Kitchen Service必须调用Delivery Service来安排送餐员交付订单。表2-3显示了服务、修订后的API及协作者。为了完整定义服务API,你需要分析每个系统操作并确定所需的协作。


表5 服务、修订后的API及协作者


image.png


总结


  • 微服务中的服务是根据业务需求进行组织的,按照业务能力或者子域,而不是技术上的考量。

  • 有两种分解模式:

       按业务能力分解,其起源于业务架构。

       基于领域驱动设计的概念,通过子域进行分解。

 

  • 可以通过应用DDD并为每个服务定义单独的领域模型来消除上帝类,正是上帝类引起了阻碍分解的交织依赖项。
相关文章
|
1天前
|
弹性计算 负载均衡 容灾
应用阿里云弹性计算:打造高可用性云服务器ECS架构
阿里云弹性计算助力构建高可用云服务器ECS架构,通过实例分布、负载均衡、弹性IP、数据备份及多可用区部署,确保业务连续稳定。自动容错和迁移功能进一步增强容灾能力,提供全方位高可用保障。
5 0
|
1天前
|
监控 持续交付 Docker
使用Docker进行微服务架构的最佳实践
【5月更文挑战第10天】本文探讨了使用Docker实施微服务架构的最佳实践。首先,理解微服务架构是拆分小型独立服务的模式,借助Docker实现快速部署、高可移植性和环境一致性。Docker的优势在于服务扩展、容器编排、自动化构建与部署。最佳实践包括:定义清晰服务边界,使用Dockerfile和Docker Compose自动化构建,利用Docker Swarm或Kubernetes编排,实施服务发现和负载均衡,监控与日志记录,以及持续集成和持续部署。Docker虽重要,但需与其他技术结合以确保系统整体稳定性。
|
1天前
|
缓存 负载均衡 API
微服务架构下的API网关性能优化实践
【5月更文挑战第10天】在微服务架构中,API网关作为前端和后端服务之间的关键枢纽,其性能直接影响到整个系统的响应速度和稳定性。本文将探讨在高并发场景下,如何通过缓存策略、负载均衡、异步处理等技术手段对API网关进行性能优化,以确保用户体验和服务的可靠性。
|
1天前
|
监控 持续交付 开发者
构建高效微服务架构:后端开发的新范式
【5月更文挑战第10天】在现代软件开发领域,微服务架构已经成为一种流行的设计模式,它通过将大型应用程序拆分为一组小型、独立和松散耦合的服务来提供更高的可伸缩性和灵活性。本文深入探讨了微服务架构的设计理念、实施步骤以及面临的挑战,并提出了一套实用的策略和最佳实践,帮助后端开发者构建和维护高效的微服务系统。
|
2天前
|
存储 监控 API
构建高效微服务架构:后端开发的现代实践
【5月更文挑战第9天】 在本文中,我们将深入探讨如何在后端开发中构建一个高效的微服务架构。通过分析不同的设计模式和最佳实践,我们将展示如何提升系统的可扩展性、弹性和维护性。我们还将讨论微服务架构在处理复杂业务逻辑和高并发场景下的优势。最后,我们将分享一些实用的工具和技术,以帮助开发者实现这一目标。
|
2天前
|
负载均衡 算法 NoSQL
探索微服务架构下的服务发现与治理
【5月更文挑战第9天】 在当今的软件开发领域,微服务架构已成为构建可伸缩、灵活且容错的系统的首选模式。随着服务的增多,如何有效地进行服务发现与治理成为了关键的挑战。本文将深入探讨微服务环境中服务发现的机制和治理策略,分析不同服务发现工具的优缺点,并提出一种基于一致性哈希和健康检查相结合的服务治理方案,旨在提高系统的可用性和性能。
|
2天前
|
运维 Cloud Native 持续交付
构建未来:云原生架构在现代企业中的应用与挑战
【5月更文挑战第9天】 随着数字化转型的浪潮席卷全球,企业正迅速采纳云原生技术以实现敏捷性、可扩展性和弹性。本文深入探讨了云原生架构的关键组件,包括容器化、微服务、持续集成/持续部署(CI/CD)和DevOps文化,并分析了这些技术如何帮助企业加速产品上市时间,提高运营效率,并最终实现业务目标。同时,文章也识别了企业在采纳云原生实践中可能面临的挑战,如安全性考量、团队技能提升和复杂的网络管理,并提出了相应的解决方案和最佳实践。
|
3天前
|
监控 API 持续交付
构建高效可靠的微服务架构:策略与实践
【5月更文挑战第8天】在当今快速演进的软件开发领域,微服务架构已经成为实现敏捷开发、持续交付和系统弹性的关键模式。本文将探讨构建一个高效且可靠的微服务系统所必须的策略和最佳实践。我们将从服务的划分与设计原则出发,讨论如何通过容器化、服务发现、API网关以及断路器模式来优化系统的可伸缩性和鲁棒性。此外,我们还将涉及监控、日志管理以及CI/CD流程在确保微服务架构稳定运行中的作用。
|
3天前
|
API 持续交付 开发者
构建高效微服务架构:后端开发的新视角
【5月更文挑战第8天】 随着现代软件开发的演变,微服务架构已经成为了企业追求敏捷、可扩展和灵活部署的重要解决方案。本文将深入探讨如何构建一个高效的微服务架构,包括关键的设计原则、技术栈选择以及持续集成与部署的最佳实践。我们还将讨论微服务带来的挑战,如数据一致性、服务发现和网络延迟,并提出相应的解决策略。通过本文,后端开发者将获得构建和维护微服务系统所需的深度知识,并了解如何在不断变化的技术环境中保持系统的健壮性和可维护性。
35 8
|
3天前
|
消息中间件 Java 微服务
Java微服务架构实践指南
Java微服务架构实践指南
12 0