惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - Zhang_Xiang

代码是 AI 写的,生产事故谁背锅? AI Agent 走出 Demo 幻觉的唯一解药:Harness Engineering 从 page、page_size 到游标:深入解析C端产品的两种主流分页技术 Apache Kafka 的基本概念 Apache Kafka 移除 ZK Proposals webRTC demo Spring Authorization Server(AS)从 Mysql 中读取客户端、用户 Java 对象实现 Serializable 的原因 Spring Data JPA 使用 Spring Authorization Server 实现授权中心 OAuth 2.1 框架 Spring Security dapr 本地环境升级 BuildPack 打包 spring-boot 2.5.4,nacos 作为配置、服务发现中心,Cloud Native Buildpacks 打包镜像,GitLab CI/CD 高可用 Keycloak,K8s Keycloak 13 自定义用户身份认证流程(User Storage SPI) - Zhang_Xiang OAuth 2.0、OIDC 讲不清楚? Mokito 单元测试与 Spring-Boot 集成测试 关于 JMeter 5.4.1 的一点记录
如何拆分大型单体系统为微服务
Zhang_Xiang · 2021-06-12 · via 博客园 - Zhang_Xiang

单体系统如何拆分为微服务

当单体系统越来越大,并难于维护时,很多企业开始有意把单体系统拆分为微服务架构。这么做很有意义,但不容易。要做好这件事情,我们需要学习一些方法,我们从一个简单的服务开始,另一方面拉出以垂直功能为基础的服务,这些功能对业务来说很重要并且经常变更。这些服务首先要很大,并且最好不要依赖剩余的单体系统。我们应该确保每一步迁移对于整体架构而言,是一个原子变更。

迁移巨型单体系统到微服务生态系统是一个史诗任务。从事这项任务的人拥有增加经营规模、促进变化、避免变更带来的高开销的意愿。他们想要增加他们的团队规模同时让团队以并行的方式传输价值并彼此独立。他们想要快速实验他们的业务核心功能并且更快的传递价值。他们同时要避免变更现存单体系统带来的高昂开销。

决定解耦何种功能、何时、如何逐步迁移以分解单体系统到微服务生态是架构的挑战。在这篇文章里,我将分享一些技术,引导交付团队(开发者、架构师、技术经理)在拆分过程中使用这些技术做出拆分决定。

微服务生态系统目标

在开始之前,大家对微服务生态系统达成共识是关键。微服务生态系统是一个服务平台,每一个服务封装一个业务功能。一个业务功能代表业务在特殊领域可以做什么(实现目标和责任)。每个微服务暴露一个 API,以便开发者在自托管模式中发现。微服务拥有独立的生命周期。开发者可以独立开发、构建、测试和发布。微服务生态系统实施长期自治的团队组织架构,每个团队负责一个或多个服务。与大多数看法相反,微服务中的“微”和服务大小几乎没有关系,它依赖运营成熟的组织架构(运营成熟的组织架构决定微服务)。

过程介绍

在深入介绍之前,了解现有系统迁移到微服务架构将产生很高的总体成本(并且可能产生很多迭代)很重要。对于开发者和架构师来说,需要密切评估是否要分解现有系统,以及微服务是否是正确的选择。

解耦一个简单的功能来热身

开始微服务需要准备底层程序。它需要访问部署环境,构建新的 CD 管道,以此实现独立构建、测试、部署,以及安全性、调试、监控分布式架构。准备基础程序是必须的,无论我们构建新服务还是分解已有系统。

我的建议是开发和运维团队构建底层基础设施、持续交付管道和 API 管理系统,并且分解或构建第一个和第二个服务。从分解单体系统的某个功能开始,这个功能相对目前的单体系统来说不需要变更很多客户端接口,也不需要存储数据。交付团队的优化点是验证他们的交付渠道,升级团队的技能,构建最小基础设施以交付部署安全服务以暴露自托管服务。我们使用一个在线零售应用做为示例,第一个服务是“终端用户认证”服务,单体服务发起请求来认证终端用户,第二个服务是“客户档案”服务,使用外观服务为客户提供更好的客户视图。

首先推荐解耦简单的边缘服务。下一步我们采用不同的方式解耦深度嵌入的单体系统。我建议先做边缘服务是因为在开始之初,交付团队最大的风险是合理的运维微服务。所以使用边缘服务体验运维很有好处。一旦他们定位到问题,他们就可以定位到分离单体服务的关键问题。

最小化对单体的依赖

交付团队的基本原则是,新的微服务对单体系统的依赖最小化。微服务的主要好处是快速独立的发布体系。依赖单体系统的数据、逻辑、API,耦合服务到单体系统的发布体系,禁止使用单体系统的好处。通常从单体系统架构跑路的主要动机是由于高昂的代价和封装在单体系统中的功能更新过于缓慢,所以我们要缓缓的朝单体系统解耦核心功能的方向移动。如果团队按照这篇文章的指导来为他们的微服务增加功能,那么他们会发现从单体系统到服务的替换、依赖的反转。这是理想的依赖方向,因为它不会放慢变更服务的步伐。

考虑一个在线零售系统,核心功能是“购买”和“促销活动”。在结账过程中,“购买”使用“促销活动”给顾客提供最好的促销活动。如果要决定下一步解耦这两个功能里的哪一个,我建议先开始解耦“促销活动”然后才是“购买”。因为在这个顺序下我们减少了对单体系统的依赖。在这个顺序下,“购买”继续锁在单体系统中,依赖新的“促销活动”微服务。

下一步本文将使用其它方式来决定解耦的服务。这意味着这些服务不是总能避开对单体系统的依赖。如果一个新的服务最终回调单体服务,我建议从单体系统中暴露一个新的 API,新的服务通过反腐层访问 API,以此确保单体系统的概念不泄漏。力争定义的 API 对于领域概念和结构有良好的反映,即便单体系统的内部实现不是那样的。在这个不幸的案例中,交付团队将承担改变单体系统的开销和困难,即测试、发布新的服务和单体系统发布耦合。

尽早分离黏性功能

假设交付团队已经开始构建微服务并且准备进攻黏性问题。然而他们可能会发现他们能力有限,使下一个解耦的功能不依赖于单体系统。根本原因通常是由于单体系统的功能泄漏,定义的领域概念不好,有很多单体系统功能依赖于它。为了能处理这个问题,开发者需要辨别黏性功能,把它解构为定义良好的领域模型然后把这些领域概念实现到隔离的服务中。

例如 Web 单体系统,“session” 是最为常见的耦合因素之一。在在线零售示例中,session 通常会封装很多特性,从用户的偏好(不同的领域边界,比如:配送和支付偏好)到用户的意图和交互(比如:最近访问的页面、点击的产品和购买清单)。若非我们处理解耦、解构和具体化当前 session 的概念,我们将陷入解耦功能(这些功能通过泄漏的 session 概念缠住单体系统)的竞争中。同时我也不鼓励在单体系统外创建 session 服务,因为它会导致和单体系统进程中类似的紧耦合,更糟糕的是,在进程外和跨网络。

开发者可以逐步从黏性功能中抽取微服务,每次一个服务。例如,先重构“顾客愿望清单”并抽取到一个新的服务中,然后重构“顾客支付偏好”到另一个服务中。

垂直解耦,尽早释放数据

从单体系统中解耦功能的主要驱动是可以独立发布它们。这是开发者在解耦过程中做每一个决定的首要原则。一个单体系统通常由紧密集成层,甚至几个系统组成(需要发布在一起并且有脆弱的相互依赖关系)。例如,在一个在线零售系统中,单体系统由一个或几个面向顾客的在线购物应用程序组成,一个后端系统实现很多业务功能(包含一个集中的数据存储)。

大多数解耦尝试从抽取面向用户组件、几个外观服务为 UI 提供友好的开发 API开始,同时数据仍然锁在同一个 schema 中。虽然这种方式在一些方面立竿见影,比如更加频繁的变更 UI,当涉及到核心功能时,交付团队只能按照最慢的部分步伐,单体系统和它的巨大数据存储。简单的说,不解耦数据,架构就不是微服务。所有数据在同一个存储中与微服务去中心化数据管理的特征背道而驰。

策略是垂直移除功能,解耦核心功能和它的数据,并重定向所有前端应用程序到新的 API。

有多个应用程序从中心共享数据读写是服务解耦数据的主要障碍。交付团队需要纳入一个数据迁移策略,这个策略适配他们的环境依赖,无论他们是否同时重定向和迁移所有数据读写者。四段数据迁移策略是其中一种适应很多环境(需要逐步迁移集成数据库的应用程序,同时所有系统在变更下需要继续运行)的策略。

解耦对业务重要和频繁变更的部分

从单体系统中解耦功能不容易。在在线零售应用程序中,抽取一个功能需要仔细抽取功能的数据、逻辑、面向用户的组件然后重定向它们到新的服务。因为这是一堆重要的工作,开发者需要持续评估解耦得到的好处,比如:跑的更快或者增加规模。例如,如果交付团队的目标是加速修改已经锁在单体系统中的功能,那么他们必须确定修改最多的功能。解耦代码中持续经受修改的部分(这部分代码持续得到开发者的关注,并最大限度限制了开发者快速交付成功)。交付团队可以分析代码提交模型找出历史上变化最大的内容,并将其与产品路线图和产品组合进行叠加,以了解在不久的将来会受到关注的最需要的功能。他们需要和业务、产品经理沟通以了解对他们来说重要的功能差异。

例如在一个在线零售系统中,“顾客个性化”是一个功能,该功能要进行大量的实验以为顾客提供最好的体验,并且也是一个好的解耦候选项。它是一个对业务很重要的功能,用户体验,并且频繁被修改。

解耦功能,不是代码

无论何时,开发者们要从一个现存系统中抽出一个服务,他们有两种方式:抽取代码或者重写功能。

通常情况下,服务抽取或者单体系统解构,默认假设重用已有的实现,原样抽取到一个分离的服务中。部分原因是我们对我们设计、编写的代码有一个认知偏见。建筑(没错,这里就是建筑,这里借助 IKEA Effect 理论)过程让我们对它产生热爱,无论这个过程多么痛苦,结果多么不完美。不幸的是这种偏见将阻碍单体系统解构。它引发开发者们和更多的重要技术管理者不理会高开销和低价值的抽取和重用代码。

交付团队可以选择重写功能然后淘汰老代码。重写给了他们重新访问业务功能的机会,和业务展开一轮新的谈话,简化遗留的过程和挑战,随着时间推移建在系统中老的假设和限制。它同样提供了一个刷新技术的机会,使用最合适的一门编程语言和技术栈实现一个新的服务。

例如在零售系统中,“定价和促销活动”功能是一段逻辑复杂的代码。它启用动态配置和应用程序定价、促销活动规则,提供折扣(在各种参数的基础上,比如:客户行为、忠诚度、产品包等)。

这个功能可以说是一个很好的重用、抽取的候选项。相反,“顾客文档”是一个简单的 CRUD 功能,通常由样板代码组成(序列化、处理存储和配置),因此,它是重写、淘汰代码的候选项。

在我看来,在大多数解构场景中,团队最好重写功能到一个新的服务中,并且淘汰老代码。这里考虑高开销、低价值的重用,有以下几个原因:

  • 有大量的模版代码要处理环境依赖,比如在运行时访问应用程序配置、访问数据存储、缓存并且在老框架的基础上构建。大多数模版代码需要重写。新的基础设施要托管一个微服务和几十年应用程序运行时有很大的不同,并且需要不同种类的模版代码。
  • 很有可能存在的功能的领域概念不清晰。导致传输或者存储数据结构不能反映新的领域模型并且需要忍受巨大的重组。
  • 一个长时间存在的遗留代码经历过很多迭代,导致很高的代码毒性级别和重用价值低。

除非能力相关,与清晰的领域概念保持一致并且有很高的知识产权,否则我强烈建议重写和淘汰旧代码。

先微服务,然后再划分的更小

在遗留单体系统中寻找领域边界既是艺术也是科学。通常应用领域驱动设计技术查找边界上下文定义微服务边界是一个好的开始。我承认,我经常看到从巨大的单体系统到真正的小服务的过度修正,真正的小服务的设计是由于受到存在规范化的数据视图的鼓励和驱动。这种方式确认服务边界导致寒武纪爆发大量的贫血服务(CRUD 资源)。对于微服务架构新手来说,这会创建一个高摩擦环境,最终无法通过独立发布,执行服务的测试。它创建了一个难以调试的分布式系统,一个分布式系统打碎了事务边界,因此难以保持一致性,对于组织的运营成熟度而言过于复杂的系统。虽然有一些如何“微”微服务的启发式:团队大小、重写服务的时间、要封装多少行为等等。我的建议是大小依赖于有多少服务交付,多少服务运维团队可以独立发布、监控和操作。从围绕逻辑领域概念的大型服务开始,并在团队准备就绪时将服务分解为多个服务。

例如,在解耦零售系统的过程中,开发者可能开始于服务“购买”,这个服务封装了“购物袋”,功能是购物和购物袋(也就是买单)。随着他们组建更小团队和发布更多服务的能力的增长,他们可以将购物袋与结帐分离成单独的服务。

以原子进化步骤迁移

通过将一个遗留的单体系统解耦成设计精致的微服务,然后让单体系统消失的想法在某种程度上是一个神话,可以说是不可取。任何经验丰富的工程师都可以分享迁移、尝试现代化的故事,这些尝试是在对完全完成过于乐观的情况下计划和启动的,但充其量在足够好的时间点被放弃。由于宏观条件发生变化,此类努力的长期计划被放弃:该计划的资金耗尽,组织将重点转向其他事物或支持它的领导层离开。所以这个现实应该被设计成团队如何处理单体应用到微服务之旅。我称这种方法为“架构演化的原子步骤中的迁移”,其中迁移的每一步都应该使架构更接近其目标状态。每个进化单元可能是一小步或一大步,但都是原子的,要么完成,要么恢复。这一点特别重要,因为我们正在采用迭代和增量方法来改进整体架构和解耦服务。每个增量都必须让我们更接近架构目标。使用进化架构适应度函数比喻,迁移的每个原子步骤之后的架构适应度函数应该产生更接近架构目标的价值。

让我通过一个例子图解这个观点。假设微服务架构目的是增加开发者修改整个系统的速度交付价值。团队决定解耦用户认证到一个隔离的服务中,以 OAuth 2.0 协议为基础。这个服务想要同时替换已有客户端应用程序认证终端用户,而且新的架构微服务验证终端用户。让我们将这个进化过程中的增量称为“Auth 服务介绍”。一个方法介绍新的服务是先通过以下步骤:
(1)构建 Auth 服务,实现 OAuth 2.0 协议。
(2)在单体系统中添加一个新的认证路径并调用 Auth 服务认证终端用户。

如果团队在这里停下来并转向构建一些其他服务或功能,他们会使整体架构处于熵增加的状态。在这种状态下,有两种验证用户的方法,新的 OAuth 2.0 基本路径和旧客户端的基于密码/会话的路径。在这一点上,团队实际上离他们更快地做出改变的总体目标更远了。单体代码的任何新开发人员都需要处理两条代码路径,增加理解代码的认知负担,以及更慢的更改和测试过程。

团队可以包含以下步骤到我们的原子进化单元中:
(1)通过 OAuth 2.0 替换老客户端的密码/session
(2)从单体系统中淘汰老的认证代码

这时候我们可以说团队已经接近目标架构了。

单体系统原子单元解构包括:

  • 解耦新服务
  • 重定向消费者到新的服务
  • 从单体系统中淘汰来的代码

反模式:解耦新服务用于新的消费者并且从不淘汰老的服务。

我经常发现团队终止从单体系统中迁移功能,新的功能开发出来以后马上就宣布胜利了,也不淘汰老的代码路径,正如上面描述的反模式。主要原因是:(a)聚焦于引入新功能的短期利益(b)淘汰老的代码会和构建新功能形成竞争优先级。为了做正确的事,我们应该尽可能的做原子步骤。

引用

How to break a Monolith into Microservices