微服务架构的 10个 最佳实践 !
共 6400字,需浏览 13分钟
·
2023-04-18 00:00
以下为译文:
微服务架构到底是什么呢?以下是我给出的定义:
微服务架构是将软件系统分解为自主模块,自主模块可以独立部署,通过轻量级的、与语言无关的方式进行通信,共同实现业务目标。
软件系统通常非常复杂。由于人脑只能处理一定程度的复杂性,因此大型软件系统的高复杂性会带来很多问题。一个大型的、复杂的软件系统通常很难开发、增强、维护、难以实现现代化和规模化。许多年来,人们作了很多尝试,以解决软件系统的复杂性难题。
20世纪70年代,David Parnas和Edsger W. Dijkstra引入了模块化软件开发,来解决这一复杂性问题。20世纪90年代,为了解决业务应用的复杂性,引入了分层软件架构。
到了21世纪初,面向服务的体系结构(Service-Oriented Architecture,SOA)成为开发复杂业务应用程序的主流。而微服务架构则是处理现代软件应用复杂性的最新方法。那么可能有人会问:为什么我们突然需要一种新的软件开发方法呢?
简单来说,这是因为在过去的十年里,软件开发所处的生态系统发生了巨大的变化。现在,软件通常是使用敏捷方法开发,使用CI/CD方法部署在容器 + 云(Container+Cloud)上,数据保存在NoSQL数据库上,最后呈现在现代浏览器或智能手机上,并且这些机器通过高速网络连接在一起。
基于这些因素,微服务架构于2012年应运而生。
微服务vs.单一体系架构
对于微服务架构与单一体系架构,两类人的观点截然相反。一类人认为,微服务架构是一种盲目模仿(Cargo-Cult)、或者趋势驱动(Hype Driven)式的开发方式,对于技术上瘾的开发人员来说,它就像他们的游乐场。而对于另一类人来说,微服务架构是“一个架构搞定一切”,它可以消除任何软件系统的复杂性。而笔者认为,微服务架构和单一体系架构可以互为补充。对于长期而言都很精简的应用程序,单一体系架构可能更为合适。而另一方面,对于大型而且复杂的应用程序,或者有可能变得大而复杂的应用程序,微服务架构这一解决方案更加有效。现今的软件开发通常是十分庞大的工程,以至于微服务架构和单一体系架构可以实现共存,就像SQL和NoSQL数据库的共存一样。
10个最佳实践
正确地设计微服务架构是一项非常困难和具有挑战性的工作。和为所有问题提供一劳永逸解决方案的单一体系架构相反,微服务体系架构针对不同的问题提供了不同的解决方案。如果选择了错误的解决方案,那么微服务架构就将是一颗定时炸弹,注定要引爆。一个设计有缺陷的微服务架构比单一体系架构更加糟糕。为微服务架构定义一组最佳实践也是一项挑战。我看过一些会议演讲,其中有一些著名和受人尊敬的软件工程师们提出过一些关于微服务架构的最佳实践,但结果适得其反。
在本文中,我将提出一些微服务架构的最佳实践,这些实践将有助于开发有效的微服务应用程序,其中目标项目的预期寿命将会超过6个月,团队规模从中等到大型(6名以上的开发人员)。
在这里,我也将列出一些关于微服务架构最佳实践的帖子供大家参考,例如Martin Fowler撰写的《微服务架构的特征》、Chris Richardson撰写的《微服务模式》、和Tony Mauro撰写的《在Netflix上采用微服务:架构设计的若干教训》。也有一些很棒的演讲,例如Stefan Tilkov的演讲《微服务模式和反模式》,David Schmitz的演讲《应对微服务严重失败的10条技巧》,Sam Newman的演讲《微服务原理》。此外,我还编写了一份精心挑选的书籍清单,这些书籍对于理解微服务架构设计的以下10个最佳实践必不可少:
1. 领域驱动设计(DDD: Domain-Driven Design)
开发微服务的最重要的挑战是将一个大型、复杂的应用程序拆分成小型、自主管理的、并且可以独立部署的模块。
如果应用程序没有以正确的方式进行拆分,那么产生的微服务将会是紧密耦合的,它们将会同时具有单一体系架构的所有缺点和微服务的所有复杂性,这种架构被称为分布式单一体系架构(Distributed Monolith)。
幸运的是,在这方面已经有一些可以大有帮助的解决方案。Eric Evans是一名软件工程顾问,他曾在不同公司中多次遇到有关业务应用程序中复杂性的问题,并在2004年出版的书籍《领域驱动设计:解决软件核心的复杂性》中总结了一些宝贵的见解。该书概述了以下三个核心概念:
-
软件开发团队应与业务部门或领域专家密切合作。
-
架构师/开发人员和领域专家应首先进行战略设计:找到有界的上下文和相关的核心域、通用语言、子域、上下文映射等等。
-
然后,架构师/开发人员应进行战术设计,将核心领域分解为细粒度的构建块:如实体(Entity)、值对象(Value Object)、聚合(Aggregate)、聚合根(Aggregate Root)等等。
领域驱动设计的详细讨论超出了这篇文章的范围,但是你可以阅读Eric Evans撰写的关于领域驱动设计的早期著作:《领域驱动设计:解决软件核心的复杂性》(蓝皮书),或者阅读更现代一点的,由Vaughn Vernon撰写的《领域驱动设计之实施》一书(红皮书)。
如果将一个大型系统划分为核心域和子域,然后将核心域和子域映射到一个或多个微服务上,那么就可以得到理想的松耦合微服务。
2. 微服务的数据库
将复杂的应用程序拆分为微服务模块后,下一个挑战就出现了 -- 如何处理数据库?是否应该在微服务之间共享数据库?这个问题的答案是把双刃剑,有利有弊。
一方面,微服务之间共享数据库,会导致微服务之间的强耦合,这与微服务体系架构的目标正好相反。即使数据库中出现微小变化,也需要团队之间的同步操作。
而且,在一个服务中管理事务和锁定数据库就已经足够具有挑战性了,管理多个分布式微服务之间的事务和锁定更是一项极其艰巨的任务。
另一方面,如果每个微服务都有自己的数据库/私有表,那么在微服务之间交换数据就相当于打开了潘多拉的魔盒。
因此,许多著名的软件工程师都主张在微服务之间共享数据库,以此作为一种实用的解决方案。但是,在我看来,微服务本质上是一个可持续和长期的软件开发过程,因此,每个微服务都应该有自己的数据库(或私有表)。
3. 微前端
不幸的是,大多数后端开发人员都对前端开发都有一个过时的看法,认为前端开发很简单。
由于大多数软件架构师都是后台开发人员,所以他们很少考虑前端,在架构设计中往往忽略前端。在微服务项目中,后端及其和数据库的交互通常会很好地模块化,但前端只有一个单一整体。
在最好的情况下,开发人员会考虑使用最热门的SPA(React,Angular,Vue)之一来开发这个单一的前端。这种方法的主要问题在于前端的单一架构和后端的单一架构一样的糟糕,正如我之前所描述的。
此外,当前端需要适应浏览器的变化而进行现代化更新时,它就需要进行一场大爆炸式的更新(这就是为什么许多公司仍然使用过时的Angular 1框架的原因)。
前端简单但功能非常强大,并提供了固有的嵌入。开发基于SPA的微前端有很多方法:使用iFrame、Web组件或借助Elements(Angular/React)。
4. 持续交付
微服务架构的关键USP(独特卖点)之一是每个微服务都可以独立部署。如果你有一个由100个微服务组成的系统,并且只需要更改一个微服务,那么你可以只更新一个而无需更改其他99个微服务。
但是,在没有任何自动化系统(DevOps,CI/CD)帮助的情况下独立部署100个微服务是一项艰巨的任务。要充分利用此微服务功能,需要采用CI/CD和DevOps系统。使用不带CI/CD、DevOps系统的微服务架构,就像购买最新的保时捷,然后用手刹来驾驶它。
难怪微服务专家Martin Fowler将CI/CD列为使用微服务体系架构的三个先决条件之一。
5. 可观察性
微服务架构的一个主要缺点是,软件开发变得简单,但却牺牲了运营。对于单一架构来说,对应用程序的监控要简单得多。
但是由于很多微服务在容器上运行,整个系统的可观察性变得非常关键和复杂。
甚至日志记录也变得复杂,无法将许多容器/机器中的日志聚合到一个中心位置。幸运的是,现在市场上已经有很多企业级解决方案。例如,ELK/Splunk可以提供微服务日志记录。
Prometheus/App Dynamics可以提供行业级的监控。微服务领域中另一个非常关键的可观察性工具是Tracing。
通常,对微服务的一个API请求会导致对其他微服务的多个级联调用。要分析微服务系统的延迟,必须测量每个微服务的延迟。Zipkin/Jaeger为微服务提供了出色的跟踪支持。
6. 统一技术栈
微服务架构要求,对于一个微服务,需要采用最适合该微服务的编程语言和框架。这句话不应该只从字面上理解。
有时,微服务可能需要一个新的技术堆栈,例如对于CPU繁重/高性能的任务,可以选择像C++/ Rust这样的编程语言。如果一个微服务是为机器学习服务,那么Python也许是一个更好的选择。
但是,如果没有充分的理由使用不同的编程语言/框架,可能会导致太多的编程语言和框架, 而没有带来任何实质性的好处。想象一下, 如果一个微服务是用Spring Boot + Kotlin + React + MySQL开发的,另一个是用JakartaEE + Java + Angular + PostgreSQL开发的,再另一个又是用Scala + Play Framework + VueJS + Oracle开发的,那么维护这么多不同的编程语言、数据库和框架需要付出巨大的努力,但收获却很小。
7.异步通信
微服务架构中最具挑战性的设计决策之一是服务之间如何通信和共享数据。当每个微服务都有自己的数据存储时,这一点尤为重要。
通常,一个微服务可以单独存在,但却不能单独实现所有的业务目标。所有的微服务需要协同工作才能实现业务目标。
而为了协同工作,它们需要交换数据或触发其他微服务来完成任务。微服务之间最简单和最常见的通信方式是通过同步REST API,这种方法很实用,但不是长久之计。
如果服务A调用服务B,服务B调用服务C,服务C同步调用服务D,那么延迟就会叠加。此外,由于微服务大多是分布式系统,它们可能会失败。
所以同步微服务经常会导致故障级联(failure cascading),即一个服务中的故障可能会导致其他服务中的故障。
微服务之间的同步通信还会导致微服务之间的紧密耦合。对于长期解决方案,微服务应该采用异步通信。
微 服务之间的异步通信有多种方式: 通过消息队列(如Kafka)、通过异步REST(ATOM)或者CQRS。
8. 微服务优先
许多专家认为,对于新建项目,最好从松散耦合的单一体系架构开始,因为微服务体系架构需要大量的初始工作来设置运营。在他们看来,一旦项目足够成熟,“精心”设计的单一架构可以很容易地转换为微服务架构。然而,在我看来,这种方法在大多数情况下都会失败。在实际应用中,单一架构系统内部的模块将紧密耦合,这会导致很难将其转换为微服务架构。另外,一旦应用程序投入生产,在不中断应用程序的情况下,将其转换为微服务将更加困难。所以,如果最终有计划使用微服务架构,我的建议是从一开始就采用微服务架构。
9. 基于库的基础设施
在微服务软件开发的早期,Netflix主要使用Java编程来开发微服务。他们还开发了许多库(Netflix OSS Stack,包括Hystrix、Zuul)。
许多公司效仿Netflix,开始使用Netflix OSS库。后来,许多公司(包括Netflix)发现,由于Java的体积庞大和冷启动问题,它并不是开发微服务的合适语言。
Netflix后来转向Polyglot微服务范式,并决定不再进一步开发Netflix OSS,这导致追随Netflix的公司陷入困境。
因此,与其在特定语言库(如基于Java的Netflix OSS)上投入巨资,不如使用框架(如服务网格、API网关)更为明智。
10. 组织考虑
大约50年前(1967年),梅尔文·康威(Melvin Conway)观察到公司的软件架构受到组织结构的制约(康威定律)。
虽然这项观察已有50年历史,但麻省理工学院和哈佛商学院最近发现,该定律在现代仍然有效。如果一个组织计划开发微服务架构,那么它应该相应地确定团队规模(两个“美式”披萨团队的大小:5人或9人)。
另外,团队应该是跨职能的,理想情况下应该拥有前端/后端开发人员、运维人员和测试人员。只有在高层管理者也相应地改变他们的观点和愿景的情况下,微服务架构才能发挥作用。
原文:https://towardsdatascience.com/effective-microservices-10-best-practices-c6e4ba0c6ee2