从单体架构到微服务架构

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 从单体架构到微服务架构

我在Martin Fowler网站上读到一篇名为How to break a Monolith into Microservices的微服务文章,作者为ThoughtWorks的咨询师Zhamak Dehghani,介绍了如何从单体架构演进到微服务架构。

微服务生态系统


在讲解如何拆分之前,Dehghani首先介绍了微服务生态系统(microservices ecosystem),她认为微服务生态系统是“封装了业务能力的服务平台”。Martin Fowler与James Lewis总结的微服务特征中,也提及“通过业务能力组织服务”。认识到这一点,对于微服务拆分而言非常重要。微服务首先是业务手段,然后才是技术手段。Dehghani定义了“业务能力”:

业务能力


A business capability represents what a business does in a particular domain to fulfill its objectives and responsibilities.

业务能力体现为在特定领域为达成其目标与职责的相关业务。

微服务生态系统如下图所示:

image.png

该生态系统牵涉到微服务的特征,团队的职责和组织结构,开发实践等,这些因素横跨业务、组织与技术诸方面,说明系统的微服务架构迁移不仅仅需要在技术层面做好准备,还需要在整个企业或团队层面做好充分准备,否则就可能“出师未捷身先死”!

旅程的开始


从单体架构到微服务架构是一个漫长的旅程。在开始演进之前,Dehghani建议最好结合Martin Fowler给出的微服务前提条件对系统和团队进行评估。在开始拆分第一个服务之前,开发与运维团队应该事先准备好系统的基础设施、持续交付管道以及API的管理系统。这样可以尽可能地保证单体系统的客户端不会受到服务拆分的影响。

在开始拆分单体架构的服务时,需得小心行事。如果从一开始就选择拆分核心领域的微服务,风险就太大了。Dehghani建议从一些边界服务(edge services)开始着手,例如认证服务。拆分这样的服务难度低,对整个系统的影响也较小,方便团队快速上手,算是拆分服务的一次“热身”。一旦拆分出一个微服务,就开始了架构风格的转换,与此同时,就可以测试微服务的整体架构是否正确。这就好像在单元测试中运行第一个测试一般,哪怕测试变红了,它也是有价值的,因为通过它的成功运行可以确认测试环境是正确的。这个演进过程如下图所示:

image.png

降低对单体系统的依赖


在拆分微服务时,处理依赖是至关重要的一点。我们必须确保一个微服务具有快速而独立的发布周期。由于单体架构与微服务架构粒度的不同,必然会导致二者在相当长的一段时间内存在依赖关系。要注意彼此之间的依赖方向!Dehghani认为应该减少微服务对单体系统的依赖。换言之,对付依赖的原则是:

  • 首先尽可能去掉二者之间不必要的依赖关系
  • 如果需要依赖,应优先考虑单体系统依赖微服务,而不是微服务依赖单体系统

例如针对一个零售在线系统,“购买”和“促销”都是系统的核心业务能力,在顾客付款的过程中,“购买”会调用“促销”以获得最佳的促销优惠。如果我们需要将这两个核心能力作为微服务从单体系统中拆分出来,那么拆分的顺序应该怎样?Dehghani认为应该先拆分“促销”,此时“购买”服务仍然在单体系统中,且依赖于新分离出来的“促销”服务。这样的拆分顺序可以减少对单体系统的依赖。

为何要遵循这样的原则?正如文中所说:“微服务需要具有快速而独立的发布周期”,如果让微服务依赖单体系统,就会让微服务变得很笨重。这就好似一只本该灵活飞翔的蝴蝶,正摇摇欲坠地拉着一头笨重的大象。

倘若无法避免新的微服务对单体系统的依赖呢?这就需要引入领域驱动设计介绍的防腐层(anti-corruption layer),即在单体系统中定义一个新的API,然后让新的微服务通过这个新API访问单体系统。新的API应遵循微服务API的设计原则,体现该服务的领域概念和结构。之所以引入防腐层,目标有二:

  • 避免单体系统的代码实现直接泄漏给微服务
  • 便于在未来用新的微服务实现替换单体系统的实现

这两种依赖方向如下图所示:

image.png

优先分离黏合逻辑


单体架构为人诟病的一点在于它太容易随着时间推移而变成一个高度耦合的“毛线球”。各个功能纠缠在一起,没有体现出清晰的领域逻辑,这会给微服务拆分带来很大的障碍。因此,Dehghani建议优先识别出单体系统中的这些黏合逻辑(Sticky Capabilities),分解它们,并定义出良好的领域概念,然后再将这些领域概念分解到各自独立的微服务中。

如何来理解“黏合逻辑(Sticky Capabilities)”?我个人认为文中提到的“黏合逻辑”,并非系统的核心业务能力,而是一种近乎于横切关注点的功能,又或者说是一个全局的数据结构(变量)。文中以session为例,单体系统中的客户属性、期望列表、支付偏好等信息都放在session之中。这时,我们就应该从具有session的这段黏合逻辑中识别领域逻辑,然后依次将这些领域逻辑分离出来形成独立的微服务:

image.png

注意:这块黏合逻辑并不需要分解为专门的微服务,例如分解为会话服务。

我在为客户做的一次微服务迁移时,也遭遇了同等问题。在原有的单体架构中,定义了诸多全局变量,并被系统中的大多数业务功能使用。这些全局变量就像一个公共澡堂,谁都可以进来泡澡。微服务是独立而私有的,当然应该在自家宅里修建自己的浴室了。所以这一原则的确立,其实是从解耦的角度思考的。

解耦的一个前提是识别依赖,Dehghani推荐诸如Structure101这样的依赖分析工具来识别单体系统中耦合最为紧密的代码。

纵向解耦并尽早发布数据


这里所谓的“纵向(Vertically)”解耦,就是从客户端发起调用的服务API到数据库进行“一刀切”。这一原则颇让我出乎意料,因为我个人认为:数据库共享架构可以作为从单体架构到微服务架构的一个过渡;但是,Dehghani认为从微服务的“去中心化数据管理(Decentralized Data Management)”特征看,应尽早解耦数据库。

在解耦数据库时,若单体系统中的多个功能都需要访问一些共享数据,这部分功能就会制造障碍。在拆分之前,与这些功能相关的所有团队需要讨论确定数据迁移的策略,然后再对服务加数据进行拆分:

image.png

注意,在拆分服务和数据时,需要确定数据访问的边界。一个原则是数据库中的数据只能被一个微服务调用,如果其他微服务也需要访问这些数据,则应该通过拥有这些数据的微服务进行访问。因此,我们需要在拆分时弄清楚数据的归属权。

解耦核心领域和变化频繁的领域


从单体系统演进到微服务时,需要叩问自身:为什么要演进到微服务?不能为了拆分而拆分,而应该在拆分微服务时,随时权衡拆分的成本与收益。这就充分地解答了本原则的缘由。为何要重点解耦核心领域以及变化频繁的领域?因为核心领域的价值要远超其他子领域,因为变化频繁的领域在单体架构中的维护成本太高。前者是因为拆分后的收益高,后者是因为不拆分带来的成本高。

要应用这一原则,可以引入领域驱动设计的战略设计,通过识别系统的核心领域与子领域,由此确定提取微服务的边界和目标。领域是否为核心领域,与痛点和价值有关,这需要结合客户的愿景、目标和经营模式等因素综合考量。例如在文中,Dehghani认为“客户个性化”是核心领域,因为它能够为客户提供更好的用户体验,有助于提高客户的黏度。

依据能力而非代码去解耦


当开发人员从已有系统中提取服务时,无非两种形式:提取代码或重写能力。多数情况下,技术人员往往采用提取方式来重用现有实现。这其实是一种感知偏见(cognitive bias),对于自己设计和编写的代码抱有偏执的热爱。这种感知偏见在心理学中被称之为“宜家效应(IKEA Effect)”。

我们应该抛开这种偏见,重新审视这二者带来的成本和价值。重写未必代价高昂,毕竟我们可以抛开过去的技术债务轻装前行,正所谓“无债一身轻”嘛。所以Dehghani给出了一个原则:重用和提取高价值低毒性的代码,重写和废弃低价值高毒性的代码。什么是代码毒性(code toxicity)?我想,应该就是Martin Fowler在《重构》一书中提及的代码坏味道。若需评估代码毒性的高低,可以使用CheckStyle等工具。

先宏观再微观


识别微服务边界的常见方法是运用领域驱动设计的限界上下文(Bounded Context)。虽然名为微服务,但服务的粒度不能没有原则的追求“微小”。过于微小的服务既会带来服务的“大爆炸”,还会出现大量只有CRUD操作的贫血服务(anemic service)。

微服务到底有多“微”,这是一个问题。衡量的指标包括团队的规模、重写一个服务花费的时间、服务封装了多少行为。但这些指标并无客观的量化值,同时,还得取决于这个系统自身的规模与业务复杂性。例如,我们自己开发一个学习型电商系统,它所拆分的微服务粒度与数量显然不能与天猫、京东这样的大型电商系统相提并论。在没有量化指标的指导下,若需要凭经验设计,就应该遵循Dehghani给出的建议:先从更大的服务开始,直到微服务的基础条件都满足了,再寻求对大的服务进行拆分。这些基本条件包括团队对业务的理解、对单体系统的理解、持续交付环境的准备等。

例如,在解耦零售系统时,团队最初提取的“购买”服务包含了购物车与结账功能。随着团队的微服务成熟度越来越高,也能够轻易地分解团队,形成小快灵的微服务团队,这时就可以考虑将购物车与结账功能分解出来,形成专门的微服务:

image.png

架构演化的原子步伐迁移


重构教导我们要“小步前行”,但真正的意图是要保持重构步伐的原子性(atomic),这样就能保证进可攻退可守的态势。单体架构向微服务架构的演进也需要遵循这一原则。若前进的步伐是正确的,且演进的内容是完整的,则意味着我们向着演进目标又靠近了一步;若前进的步伐出现了偏差,我们也能够轻易回滚。

Dehghani在文中给出了一个案例来阐释这一理念。假定微服务架构的目标是提高开发人员修改整个系统的速度,快速交付价值。团队决定将用户认证分解为一个服务,并基于OAuth 2.0协议来实现。该服务的目的是替换单体系统中的认证功能,那么演进的步骤就分为:

  • 构建一个Auth服务,采用OAuth 2.0来实现
  • 在单体系统中添加一个新的认证路径,然后调用新实现的认证服务

如果演进到这一步,团队的工作暂时停止,那么这个演进步伐就不是原子的,因为它使得系统处于一种不稳定的“中间状态”,单体系统的开发人员需要同时维护两条认证路径,增加了开发、测试和维护的成本。因此,我们不应该停下演进的步伐,而应继续前行:

  • 用新服务替换单体系统中旧有的基于用户名/密码的认证
  • 从单体系统中去掉旧有的认证实现代码

文中总结了单体系统分解的原子单元:

  • 解耦新服务
  • 将所有消费者指向新服务
  • 废弃单体系统中的旧代码

2013年,我在Scrum Gathering大会上的演讲《引入敏捷实践完成技术栈迁移》讲述了类似步骤,遵循的原则其实就是“抽象分支(Branch of Abstraction)”,在针对遗留系统进行解耦或旧代码替换时,往往会采用这种手法。故而步骤不是关键的,关键的是如何认识“原子性”。我认为,原子性就应该保证演进的功能是最小粒度的完整,且不允许新旧代码同时存在。

相关文章
|
18天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
16天前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
17天前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
37 1
服务架构的演进:从单体到微服务的探索之旅
|
5天前
|
消息中间件 运维 Kubernetes
后端架构演进:从单体到微服务####
本文将探讨后端架构的演变过程,重点分析从传统的单体架构向现代微服务架构的转变。通过实际案例和理论解析,揭示这一转变背后的技术驱动力、挑战及最佳实践。文章还将讨论在采用微服务架构时需考虑的关键因素,包括服务划分、通信机制、数据管理以及部署策略,旨在为读者提供一个全面的架构转型视角。 ####
20 1
|
15天前
|
消息中间件 监控 安全
后端架构演进:从单体到微服务####
在数字化转型的浪潮中,企业应用的后端架构经历了从传统单体架构到现代微服务架构的深刻变革。本文探讨了这一演进过程的背景、驱动力、关键技术及面临的挑战,揭示了如何通过微服务化实现系统的高可用性、扩展性和敏捷开发,同时指出了转型过程中需克服的服务拆分、数据管理、通信机制等难题,为读者提供了一个全面理解后端架构演变路径的视角。 ####
38 8
|
16天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
42 5
|
17天前
|
Kubernetes 负载均衡 Cloud Native
云原生架构下的微服务治理策略
随着云原生技术的不断成熟,微服务架构已成为现代应用开发的主流选择。本文探讨了在云原生环境下实施微服务治理的策略和方法,重点分析了服务发现、负载均衡、故障恢复和配置管理等关键技术点,以及如何利用Kubernetes等容器编排工具来优化微服务的部署和管理。文章旨在为开发者提供一套实用的微服务治理框架,帮助其在复杂的云环境中构建高效、可靠的分布式系统。
32 5
|
17天前
|
负载均衡 监控 Cloud Native
云原生架构下的微服务治理策略与实践####
在数字化转型浪潮中,企业纷纷拥抱云计算,而云原生架构作为其核心技术支撑,正引领着一场深刻的技术变革。本文聚焦于云原生环境下微服务架构的治理策略与实践,探讨如何通过精细化的服务管理、动态的流量调度、高效的故障恢复机制以及持续的监控优化,构建弹性、可靠且易于维护的分布式系统。我们将深入剖析微服务治理的核心要素,结合具体案例,揭示其在提升系统稳定性、扩展性和敏捷性方面的关键作用,为读者提供一套切实可行的云原生微服务治理指南。 ####
|
18天前
|
监控 持续交付 Docker
Docker 容器化部署在微服务架构中的应用有哪些?
Docker 容器化部署在微服务架构中的应用有哪些?
|
18天前
|
安全 持续交付 Docker
微服务架构和 Docker 容器化部署的优点是什么?
微服务架构和 Docker 容器化部署的优点是什么?