微服务开发的10个最佳实践
在⽂章《微服务架构概览》中,我详细讨论了微服务架构以及在现代软件开发中使⽤它的优缺点。那么,什么是微服务架构呢?我给出的定义是:
微服务架构是将软件系统分解成可独⽴部署的⾃治模块,这些模块通过轻量级的、语⾔⽆关的⽅式进⾏通信,共同实现业务⽬标。
常用微服务架构软件系统是复杂的。由于⼈脑只能处理⼀定程度内的复杂性,⼤型软件系统的⾼复杂性导致了许多问题。⼤型复杂的软件系统难于开发、增强、维护、现代化和规模化。多年来,为解决软件系统的复杂性做过许多尝试。在上世纪 70 年代, David Parnas 和 Edsger W. Dijkstra 引⼊了模块化软件开发,以解决软件系统的复杂性。在上世纪 90 年代,引⼊了分层软件架构来处理业务应⽤程序的复杂性。⾃本世纪初以来,⾯向服务的架构(Service Oriented Architecture, SOA)成为开发复杂业务应⽤程序的主流。微服务架构是处理现代软件应⽤复杂性的最新⽅法。此时,⼤家可能会提出⼀个问题:为什么我们突然需要⼀个新的软件开发⽅法?简⽽⾔之,与软件开发相关的整个⽣态系统在过去⼗年中发⽣了巨⼤的变化。如今,软件使⽤敏捷⽅法开发,使⽤ CI/CD 在容器 + 云上部署,在 NoSQL 数据库上持久化,在现代浏览器或智能⼿机上展现,机器通过⾼速⽹络连接。由于这些因素的出现,在 2012 年诞⽣了微服务架构。
微服务还是单体架构
主要有两类⼈对微服务和单体架构持相反的观点。对于⼀⼈来说,微服务架构完全是关于货物崇拜或炒作驱动的开发,这只是痴迷于技术的开发⼈员的游乐场。对于另⼀⼈来说,微服务架构是“⼀个管控所有的架构”,它可以消除软件系统的任何复杂性。在我看来,微服务和单体架构是互补的。如果从长远来看,这个应⽤程序仍然会较⼩,则单体架构是适合的⽅法。另⼀⽅⾯,对于⼤型⽽复杂的应⽤程序或有潜⼒变得⼤型⽽复杂的应⽤程序,微服务架构是正确的解决⽅案。现代软件开发是如此庞⼤,以⾄于微服务架构和单体架构将像 SQL 和NoSQL ⼀样的⽅式共存。
微服务的最佳实践
正确设计微服务架构⾮常具有挑战性和困难。与单体架构为所有问题提供⼀个解决⽅案相反,微服务架构为不同的问题提供不同的解决⽅案。如果选择了错误的解决⽅案,那么微服务架构就是⼀个注定要爆炸的定时。⼀个设计糟糕的微服务架构⽐⼀个单体架构还要糟糕。为微服务架构定义⼀组最佳实践也很有挑战性。我曾在⼀些会议上看到⼀些著名的、受⼈尊敬的软件⼯程师提出了微服务架构的最佳实践,但这些实践却适得其反。
在这⾥,我提出了⼀些最佳实践,这些实践将有助于开发有效的微服务应⽤程序,在这些应⽤程序中,⽬标项⽬应该存在超过 6 个⽉的时间,并且团队规模从中等到⼤型(6+ 开发⼈员)。另外,还有⼀些关于微服务架构最佳实践的⽂章,例如 Martin Fowler()的《微服务架构的特征(Characteristics of a Microservice Architecture )》、Chris Richardson 的《微服务模式(  Microservices Patterns )》、以
及 Tony Mauro 的《在 Netflix 采⽤微服务:架构设计的教训( Microservices at Netflix: Lessons for Architectural Design )》。也有⼀些很棒的演讲,例如 Stefan Tilkov 的《微服务模式和反模式(  Microservices Patterns and Antipatterns )》,David Schmitz 的《微服务严重失败的 10 个技巧( 10 Tips for failing badly at Microservices)》,Sam Newman 的《微服务原理(  Principles of Microservices )》。
1. 领域驱动设计:
开发微服务的⾸要挑战是将⼤型、复杂的应⽤程序分割成⼩型、⾃主、独⽴的可部署模块。如果微服务没有以正确的⽅式进⾏分割,将会出现紧耦合的微服务,这些微服务将具有单体架构的所有缺点,并具有分布式单体架构的所有复杂性。幸运的是,已经有⼀个解决⽅案可以在这⽅⾯提供很⼤的帮助。Eric Evans 当时是⼀名软件⼯程顾问,他在不同公司的业务应⽤程序中遇到了关于软件复杂性的反复出现的问题,于是在 2004 年出版的《领域驱动设计:处理软件核⼼的复杂性》⼀书中总结了他的宝贵见解。该书概述了三个核⼼概念:
软件开发团队应该与业务部门或领域专家密切合作。
架构师 / 开发⼈员和领域专家应该⾸先进⾏战略设计:到有界的上下⽂和相关的核⼼域以及普遍存在的语⾔、⼦域、上下⽂映射。
然后,架构师 / 开发⼈员应该进⾏战术设计,将核⼼域分解为细粒度的构建块:实体、值对象、聚合、聚合根。
领域驱动设计的详细讨论超出了这篇⽂章的讨论范围,但是你应该读⼀下起初的 DDD 书籍 Eric Evans 的《领域驱动设计:处理复杂的软件(蓝⽪书)( Domain Driven Design: Tackling Complexity in the Heart of Software  (Blue Book))》,或者更现代⼀点⼉的 DDD 书籍  Vaughn Vernon 的《实现领域驱动设计(红⽪书)( Implementing Domain Driven Design (Red Book))》。如果将⼀个⼤型系统划分为核⼼域和⼦域,然后将核⼼域和⼦域映射到⼀个或多个微服务,那么我们将得到理想的松耦合微服务。
2. 每个微服务⼀个数据库
在将复杂的应⽤程序拆分为多个微服务模块之后,下⼀个挑战出现了,如何处理数据库?我们是否应该在微服务之间共享数据库?这个问题的答案是⼀把双刃剑。⼀⽅⾯,在微服务之间共享数据库将导致微服务之间的强耦合,这与微服务架构的⽬标正好相反。即使是数据库中的⼀个⼩更改也需要团队之间的协调同步。此外,在⼀个服务中管理数据库的事务和锁就已经⾜够具有挑战性了。⽽在多个分布式微服务之间管理事务和锁更是⼀项艰巨的任务。另⼀⽅⾯,如果每个微服务都有⾃⼰的数据库或私有表,那么在微服务之间交换数据就打开了挑战的潘
多拉盒⼦。因此,许多著名的软件⼯程师都提倡在微服务之间共享数据库,将其作为⼀种实⽤的解决⽅案。然⽽,在我看来,微服务是关于可持续和长期的软件开发的。因此,每个微服务都应该有⾃⼰的数据库(或者私有表)。
3. 微前端
不幸的是,⼤多数的后端开发⼈员对前端开发有⼀种过时的看法,认为前端开发很简单。由于⼤多数软件架构师都是后端开发⼈员,他们很少关注前端,⽽前端在架构设计中往往被忽视。通常在微服务项⽬中,后端与它们的数据库被很好地模块化,但只有⼀个整体前端。在最好的情况下,他们考虑⽤最热门的单页⾯应⽤(react、 Angular、vue)其中之⼀来开发独体前端。这种⽅法的主要问题是,前端的单体架构和后端单体架构⼀样糟糕,正如我前⼀篇⽂章所述。另外,当前端因为浏览器的变化⽽需要更新时,它就需要⼀个⼤的更新(这就是为什么那么多公司仍然使⽤过时的 Angular 1 框架的原因)。⽹络是简单的,但⾮常强⼤,并天⽣提供了穿透⼒。开发基于单页⾯应⽤的微前端有很多⽅法:使⽤ iFrame、Web 组件或借助于(Angular/react)元素。
4. 持续交付
微服务架构的关键卖点之⼀是每个微服务都可以独⽴部署。如果你有⼀个系统,例如 100 个微服务,并且只需要更改⼀个微服务,那么你可以只更新⼀个微服务,⽽不需要修改其他 99 个微服务。但是,在
没有⾃动化的情况下独⽴部署 100 个微服务(DevOps、CI/CD)是⼀项艰巨的任务。要充分利⽤微服务的这⼀特性,需要 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,那么延迟就会增加。此外,由于微服务主要是分布式系统,它们有可能会失败。通常,同步微服务会导致级联失败,即⼀个服务的失败会导致其他服务的失败。微服务之间的同步通信也导致了微服务之间的紧密耦合。对于长期解决⽅案,微服务应该异步通信。微服务之间的异步通信有很多⽅式:通过消息队列,例如Kafka,通过异步的 REST (ATOM)或 CQRS。
8. 微服务优先
许多专家认为,对于新项⽬,最好从松耦合的单体架构开始,因为微服务架构需要⼤量的初始⼯作来设置运维。在他们看来,⼀旦项⽬变得⾜够成熟,“漂亮的”设计就可以很容易地转化为微服务。然⽽,在我看来,这种⽅法在⼤多数情况下都会失败。实际上,实体内部的模块将是紧耦合的,这将使其很难转换成微服务。⽽且,⼀旦应⽤程序投⼊⽣产,在不破坏应⽤程序的情况下将其转换为微服务将变得更加困难。因此,我的建议是,如果最终有使⽤微服务架构的计划,那么就从微服务开始。
9. 基础设施优于类库
在微服务软件开发的早期,Netflix 主要使⽤ Java 编程来开发微服务。他们还开发了许多类库(Netflix 的 OSS 栈,包括 Hystrix、Zuul)。许多公司通过 Netflix 跟进并开始使⽤ Netflix OSS。后来,许多公司(包括 Netflix)发现,Java 实际上并不是开发微服务的语⾔,因为它体积庞⼤,⽽且冷启动问题严重。Netflix 后来转向 Polyglot 微服务范式,并决定不再进⼀步开发 Netflix OSS,这导致了追随者公司陷⼊困境。因此,与其⼤量投资于特定语⾔的类库(如基于 Java 的 Netflix OSS),不如使⽤框架(如服务⽹格、API ⽹关)。
10. 组织考虑
⼤约 50 年前(1967 年),Melvin Conway 观察到公司的软件架构受到组织结构的限制(Conway 定律)。尽管这⼀观察发现已有 50 年历史,但⿇省理⼯学院(MIT)和哈佛商学院(Harvard Business S
chool)最近发现,这⼀法则在当今仍然有效。如果⼀个组织计划开发微服务架构,那么它应该使团队规模更为恰当(两个“美国”⽐萨团队:7±2 ⼈)。此外,团队应该是跨职能的,最好有前端 / 后端开发⼈员、运维⼯程师和测试⼈员。微服务架构只有在⾼层管理者也相应地改变他们的观点和愿景的情况下才能发挥作⽤。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。