10个优秀实践技巧,实现有效的微服务架构
微服务架构是什么?
这是笔者自己整理的定义:
微服务架构是将软件系统分解为自主模块,这些自主模块可独立部署,并通过轻量级,与语言无关的方式进行通信,共同实现业务目标。
软件系统很复杂。由于人脑只能接受一定程度的复杂性,因此大型软件系统的高度复杂性会带来许多问题。大规模、复杂的软件系统难以开发、增强、维护,难以实现现代化以及扩大规模。
多年来,人们做了许多尝试,以解决软件系统的复杂性问题。20世纪70年代,David Parnas和Edsger W引入了模块化软件开发。Dijkstra解决了软件系统复杂性这一问题。在90年代,引入了分层软件架构,解决业务应用程序的复杂性。自21世纪初以来,面向服务的架构(SOA)脱颖而出,以开发复杂的业务应用程序。微服务架构是处理现代软件应用程序复杂性的新方法。
此时可能会出现一个问题:为什么突然需要一种新的软件开发方法?
简单来说,软件开发所处的整个生态系统在过去十年里发生了巨大变化。如今,软件会通过Agile方法开发,利用CI / CD法在Container + Cloud上进行部署,然后保留在NoSQL数据库上,最后呈现在浏览器或智能手机上,而且这些设备在高速网络下会连接在一起。基于这些因素,微服务架构于2012年应运而生。
微服务或Monolith
对于微服务与Monolith,主要有两类人群持相反的观点。
对一类人群而言,微服务架构就是一种货物崇拜(Cargo-Cult)或一种趋势驱动开发(Hype Driven Development),这对于喜欢技术的开发人员来说,就像是游乐场。
而另一类人群表示,微服务架构是“统治一切的架构”,会消除任何软件系统的复杂性。笔者看来,微服务和Monolith架构互为补充。对于长期精简的应用程序,Monolith 架构更为合适。另一方面,对于大型且复杂的应用程序或可能变得大型和复杂的应用程序,微服务架构这一解决方案更好。
如今的软件开发是十分庞大的工程,可以实现微服务架构和Monolith架构的共存,就如SQL和NoSQL并存一样。
10个优秀实践
使用正确的方法设计微服务架构非常具有挑战性和难度。微服务架构不同于Monolith架构,可以提供一劳永逸的解决方案,微服务体系结构针对不同问题提供不同的解决方案。如果选择了错误的解决方案,那么微服务架构将是一颗定时炸弹,注定会引爆。设计欠佳的微服务架构要比Monolith更加糟糕。定义一套微服务架构的优秀实践也十分困难。笔者在一些会议演讲上听到过一些著名且受人尊敬的软件工程师曾提出适得其反的微服务架构优秀实践。
本文提出了一些微服务架构的优秀实践,有助于开发有效的微服务应用程序,在该应用程序中,目标项目可以存在6个月以上,并且团队规模为中型到大型(6名以上的开发人员)。以下几篇文章全面呈现了有关Microservice Architecture的优秀实践,例如Martin Fowler撰写的文章《微服务架构的特征》或Chris Richardson撰写的《微服务模式》或Netflix的《微服务运用》:Tony Mauro撰写的《架构设计的若干教训》。也有一些很棒的演讲,例如Stefan Tilkov的演讲《微服务模式和反模式》,David Schmitz的演讲《应对微服务严重失败的10条技巧》,Sam Newman的演讲《微服务原理》。
1. 微前端
不幸的是,大多数后端开发人员对前端开发的看法比较落后,认为前端开发很简单。由于大多数软件架构师都是后端开发人员,因此几乎不关心前端,并且前端通常在架构设计中被忽略。在微服务项目中,后端数据库常常会高度模块化,但是有一个Monolith前端。在合适的情况下,开发人员会考虑使用最热门的SPA(React,Angular,Vue)之一来开发Monolith 前端。
但主要问题在于,前端Monolith与笔者在文章《微服务架构:简介与在项目中应用的必要性》(MicroserviceArchitecture: A brief overview and why you should use it in your next project)描述的后端Monolith一样糟糕。
此外,由于更改浏览器也要保持前端的同步,就需要进行大爆炸式的现代化(这就是许多公司仍在使用过时的Angular 1框架的原因)。网络简单但功能强大,并本身提供嵌入。基于微前端开发SPA有很多方法:使用iFrame、Web组件或通过Elements(Angular / React)。
2. 连续交付
微服务架构的一个关键USP是每个微服务都可以独立部署。如果系统有100个微服务,要求更改一个微服务,那么可以仅更新一个微服务,而无需更改其他99个微服务。
但是,在没有自动化的情况下独立部署100个微服务(DevOps,CI / CD)是一项艰巨的任务。要充分利用此微服务功能,需要CI / CD和DevOps法。使用不带CI / CD,DevOps的微服务架构,自动化就像购买最新的保时捷,然后手动刹车驾驶。不足为奇的是,CI / CD被微服务专家马丁·福勒(Martin Fowler)列为使用微服务架构的三个先决条件之一。
3. 微服务优先
许多专家认为,对于未开发的(新的)项目,最好从松耦合的单片架构开始,因为微服务架构需要大量的初始工作来设置操作。
专家认为,一旦该项目变得足够成熟,就可以将“精巧”设计的Monolith轻松地转换为微服务。但是,笔者认为这种方法在大多数情况下将以失败告终。实际上,Monolith内部的模块紧密耦合,这使其难以转换为微服务。同样,一旦应用程序正式投入生产,要在不中断应用程序的情况下转换为微服务将变得更加困难。因此,如果最终有计划使用微服务架构,建议一开始就使用微服务。
4. 库的基础设施。
在微服务软件开发的早期,Netflix主要使用Java编程来开发微服务。Netflix还开发了许多库(包括Hystrix,Zuul的Netflix OSS Stack)。许多公司效仿Netflix,并开始使用Netflix OSS库。后来,许多公司(包括Netflix)发现Java并不是开发微服务的事实语言,因为Java体积庞大且存在冷启动问题。
Netflix后来转向Polyglot微服务范式,并决定不再进一步开发Netflix OSS,这导致追随Netflix的公司陷入困境。因此,与其大量投资于特定语言的库(例如基于Java的Netflix OSS),使用框架(例如服务网格,API网关)更为明智。
5. 域驱动设计
开发微服务较大的挑战是将大型、复杂的应用程序拆分为小型、可管理且可独立部署的模块。如果微服务没有以正确的方式对应用程序进行拆分,那么将存在紧密耦合的微服务,这些微服务将具有Monolith的所有缺点以及微服务(又名分布式Monolith)的所有复杂性。
幸运的是,在这方面已经有一个可以提供许多帮助的解决方案。埃里克·埃文斯(Eric Evans)是一名软件工程顾问,曾在不同公司中多次遇到有关业务应用程序中复杂性的问题,并在2004年出版的书籍《域驱动设计:解决软件核心中的复杂性》中总结了很有价值的见解。该书的核心概念可分为以下三类:
- 软件开发团队应与业务部门或领域专家紧密合作。
- 架构师或开发人员和领域专家应首先进行战略设计:查找有界上下文以及相关的核心域、通用语言、子域、上下文映射图。
- 然后,架构师或开发人员应进行战术设计,将核心领域分解为细粒度的构建基块:实体、值对象、聚合、聚合根。
域驱动设计的详细讨论超出了本文的范围,但是大家应该读读原书埃里克·埃文斯(Eric Evans)《域驱动设计:解决软件核心中的复杂性》(蓝皮书)或沃恩·弗农(Vaughn Vernon)所著书籍《实施域驱动设计》(红皮书)。如果将一个大型系统分为核心域和子域,再将核心域和子域映射到一个或多个微服务,那么可以获得理想的松耦合微服务。
6. 可观察性
微服务架构的一个主要缺点在于以运营为代价使软件开发变得简单。使用Monolith监视应用程序要更为简单。但是,由于许多微服务在容器上运行,因此整个系统的可观察性变得非常关键和复杂。甚至日志记录也变得很复杂,无法将来自许多容器或机器的日志聚合到一个中心位置上。
幸运的是,市场上已经有许多企业级的解决方案。例如,ELK / Splunk提供微服务的日志记录。Prometheus / AppDynamics提供行业级的监视。在微服务领域,另一个非常重要的可观察性工具是Tracing。通常,微服务的一个API请求会导致对其他微服务的多次级联调用。要分析微服务系统的延迟,有必要测量每个微服务上的延迟度。Zipkin / Jaeger为微服务提供了出色的跟踪支持。
7. 统一技术栈
微服务架构表明,需要采用对于微服务最适合的编程语言和框架。这不应从字面上理解。有时,微服务可能需要新的技术栈,例如对于CPU繁重或高性能的任务,可以选择C ++ / Rust之类的编程语言。如果微服务可与机器学习一起使用,也许Python是更好的选择。
但是,在没有任何充分理由的情况下,使用不同的编程语言或框架可能会出现太多的编程语言和框架,而没有带来任何真正的好处。想象一个这样的场景:使用Spring Boot + Kotlin + React + MySQL开发一种微服务,使用JakartaEE + Java + Angular + PostgreSQL开发另一种微服务,再使用Scala + Play Framework + VueJS + Oracle开发其他一种微服务,那么需要付出很多努力维护不同的编程语言、数据库和框架,但收获会很少。
8. 每个微服务的数据库
将复杂应用程序拆分为微服务模块后,接下来的挑战出现了——如何处理数据库?
是否应该在微服务之间共享数据库。这个问题的答案是双刃剑,有利有弊。
一方面,在微服务之间共享数据库将带来强大耦合,这与微服务架构的目标恰恰相反。即使数据库中出现微小变化,也需要团队之间的同步操作。同样,在一项服务中,管理事务和锁定数据库也具有挑战性。但是在多个分布式微服务之间管理事务或锁定数据库是一项艰巨的任务。
另一方面,如果每个微服务都有自己的数据库或专用表,则在微服务之间交换数据就会带来会打开潘多拉魔盒式的挑战。因此,许多杰出的软件工程师都提倡在微服务之间共享一个实用的解决方案。但是,笔者认为,微服务完全是一个可持续和长期的软件开发过程。因此,每个微服务都应具有自己的数据库(或专用表)。
9. 异步通讯
微服务架构中很具挑战性的一个设计决策是服务之间如何进行通信和共享数据。当每个微服务都有自己的数据存储时,这一点尤为重要。
通常,一个微服务可以单独存在,但不能单独满足所有业务目标。所有微服务一起工作,实现业务目标,并继续一起工作,这些微服务需要交换数据或触发其他微服务来完成任务。微服务之间最简单且最常见的通信方式是通过Synchronous REST API,这很实用,但不是长久之计。如果服务A调用服务B,服务B调用服务C,服务C同步调用服务D,那延迟就会叠加。
另外,由于微服务主要是分布式系统,因此可能会有故障。同步微服务通常会导致失败的级联,即一个服务中的故障可能导致其他服务出现故障。微服务之间的同步通信还导致微服务之间的紧密耦合。想要有个长久的解决方案,则微服务应该异步通信。微服务之间的异步通信有很多方法:例如,通过Message QueueKafka,通过异步REST(ATOM)或CQRS。
10. 组织注意事项
大约50年前(1967年),梅尔文·康威(Melvin Conway)观察到,公司的软件架构受组织结构(康威法则)的限制。尽管这一发现已有50年历史,但麻省理工大学和哈佛商学院最近发现该法律在现代仍然有效。如果某个组织计划开发微服务架构,则应相应地扩大团队规模(两个“美式”比萨团队:5人或9人)。此外,团队应是跨职能的,并且理想情况下拥有前端或后端开发人员、Ops工程和测试人员。微服务架构仅在高级管理层也相应地改变观点和愿景的情况下才起作用。