每个人都听说过微服务。但你知道怎么设计吗? 微服务是当今软件工程师的一个热门话题。让我们了解如何使用微服务架构风格构建真正模块化、业务敏捷的IT系统。
一、微服务概念
微服务体系结构由轻量级、松散耦合的服务集合组成。每个服务都实现了单个业务功能。理想情况下,这些服务应该是具有足够的内聚性,可以独立地开发、测试、发布、部署、扩展、集成和维护。
正式定义
“微服务架构风格是一种将单个应用程序开发为一组小型服务的方法,每个小服务运行在自己的进程中,并且以轻量级机制(通常是HTTP REST API)通信。这些服务是围绕业务能力建立的,并且可以由完全自动化的部署机构独立部署。这些服务的集中管理只有最低限度,可以用不同的编程语言编写并使用不同的数据存储技术。”—— James Lewis and Martin Fowler
定义微服务的特性
每个服务都是一个轻量级、独立和松散耦合的业务单元。
每个服务都有自己的代码库,由一个小团队管理和开发(主要是用于敏捷环境中)。
每个服务负责一部分功能或者说业务能力,并且做得很好。
每个服务都可以为其用例选择最佳的技术栈(无需将整个应用程序绑定在一个框架中)。
每个服务都有自己的DevOps计划(测试、发布、部署、扩展、集成和独立维护)。
每个服务都部署在一个独立自给的环境中。
服务通过使用定义良好的API(智能端点)和简单协议如基于HTTP 的REST协议(哑管道)相互通信。
每个服务负责持久化自己的数据和保持外部状态(只有当多个服务使用相同的数据时,这种情况才在公共数据层中处理)。
白小白:
智能端点和哑管道,其实我一直认为“哑”管道不如“笨”管道或者“呆”管道更易理解。防呆设计是一种预防用户错误使用产品造成不良后果的设计理念,比如USB设计成一半有实体芯片,就是让用户可以不假思索的在插错后直接掉转方向再插。不让用户思考就是“呆”的含义。“哑”管道的“哑”其实就是体现在微服务的通信过程尽量简单,不要让通信机制有“思考能力”,不在其中加入过多的处理机制,反例是SOA时代的ESB产品,ESB产品通常会包含复杂的设施用于消息路由,编排和转换,以及业务规则应用。反过来,智能端点的概念就容易理解了,也就是将与某服务相关的处理都限定在微服务的范畴之内,通信过程中的微服务端点是“智能”的,这也从一个方面体现了微服务“高内聚”的含义,有了高内聚,才能具备自治和独立性,从而可以支持“松耦合”的机制。
微服务的好处
微服务可以用于扩展大型系统,也为持续集成和交付提供了巨大的能力。
独立缩放:《 The Art of Scalability》( http://t.cn/EAvlQ4o)这本优秀的书中所描述的Scale Cube概念,是微服务架构所支持的。在开发微服务以实现功能分解时,应用程序通过Y轴自动缩放。当服务调用量较高时,微服务可以通过克隆更多的CPU和内存,通过X轴进行扩展。为了在多台机器上分发数据,可以分离大型数据库(分库分表)转换成更小、更快、更容易管理的部件,从而实现Z轴的缩放。
独立发布和部署:使用微服务,Bug修复和特性发布更易于管理,风险更小。可以在不重新部署整个应用程序的情况下更新服务,并在出现问题时回滚或前滚更新。
独立开发:每个服务都有自己的代码库,由一个小的焦点小组开发、测试和部署。开发人员可以专注于一种服务,并且只关注相对较小的范围。这将提高生产率、项目速度、持续创新能力和源码质量。
优雅降级:如果服务崩溃,其影响不会传播到应用程序的其他部分,并导致系统发生灾难性故障,从而体现某种程度的健壮性。
分散治理:开发人员可以自由选择技术栈,制定最适合其服务的设计标准和实现决策。团队不必因为过去的技术决定而受到惩罚。
业务关切
独立的服务本身并不能形成一个系统。要使微服务体系结构真正成功,需要大量投资来处理跨系统的问题,例如:
服务复制:一种让服务易于扩展的基于元数据的机制
服务注册和发现:启用服务查找并查找服务端点的机制
服务监测和日志:收集来自不同微服务的日志的机制,并提供一致的报告
弹性:服务在故障期间自动采取纠正行动的机制
DevOps:处理持续集成和部署的机制(CI和CD)
API网关:为客户端提供入口的机制
二、中间件与设计模式
API网关(所有客户端的单一入口点)
API网关风格的微服务体系结构(图片来自:Microsoft Azure Docs),是用于微服务的最常见的设计模式。API网关是一个中间层,具有最小化的路由功能,只是充当一个“哑管道”,里面没有业务逻辑。一般来说,API网关允许客户端基于REST/HTTP调用托管的API。其他类型的微服务集成模式有:点对点风格(直接从客户端应用程序调用服务)和消息代理风格(实现异步消息传递)。
API网关充当所有客户端的单一入口点,API网关也作为一种边缘服务来将微服务作为托管API公开给外部世界。这听起来像是一个反向代理,但也有一些额外的责任,例如简单的负载平衡,认证和授权,故障处理,审核,协议转换,和路由机制。开发团队可以选择以下方法之一来实现API网关。
自己编程实现:具有更好的客户化和管控能力。
部署现有的API网关产品:节省初始开发时间,并使用高级内置功能(缺点在于:此类产品依赖于供应商,并不完全免费。配置和维护通常是冗长而耗时的)
解释API网关行为的一些设计模式如下(请参阅微服务设计模式 http://t.cn/RKx8bhG).
网关聚合(http://t.cn/EAvT2jl):将针对多个内部微服务的多个客户端请求(通常是HTTP请求)聚合到单个客户端请求中,减少了使用者和服务之间的交互和网络延迟。
网关分流(http://t.cn/EAvTGmA):使单个微服务能够将一些共享的服务功能分流到API网关级别。这些跨服务功能包括认证、授权、服务发现、容错机制、QoS、负载平衡、日志记录、分析等。
网关路由(第7层路由,通常是HTTP请求 http://t.cn/EAvTMm4):使用单一入口端点将请求路由到内部微服务的端点,这样服务调用者就不需要自行管理多个独立的端点
请注意,API网关应该始终是一个高可用性和高性能的组件,因为它是整个系统的入口点。
事件总线(用于异步事件驱动通信的、发布/订阅、中介通道)
应用程序的不同部分在进行相互通信时,无论消息的顺序(为处理异步的消息)或使用的语言(为了体现语言无关性),都可以使用事件总线来实现。大多数事件总线支持发布/订阅、分布式、点对点和请求响应消息传递。一些事件总线(如Vert.x)允许客户端使用相同的事件总线与相应的服务器节点进行通信,这是全堆栈团队所喜爱的一个很酷的特性。
服务网格(用于服务间通信的外挂(Sidecar)机制)
服务网格通过提供服务间通信的辅助架构来实现外挂模式,包括弹性(容错、负载平衡)、服务发现、路由、可观察性、安全性、访问控制、通信协议支持等功能。
实际上,外挂实例部署在每个服务的旁边(理想情况下是在同一个容器中)。他们可以通过服务本身的网络功能来进行通信。服务网格的控制平面被单独部署,以提供中心功能,如服务发现、访问控制和可观察性(监视、分布式日志记录)。最重要的是,服务网格风格的设计模式允许开发人员从微服务代码中分离网络通信功能并使服务只关注于业务功能。(来自:Netflix Prana, 微服务网格)
尽管上面的图片显示了服务之间的直接连接,但是处理服务间通信的好方法是使用一个简单的事件总线作为中介,以保持最低级别的耦合。
聚合器(BFF模式)
如果应用程序需要裁剪每个API以适应客户端应用程序类型(Web端、移动端以及其他不同平台),则可以通过聚合器(Aggregator)执行不同的业务规则,也可以执行不同的配置以根据客户端功能适配不同的构建。这可以在API网关级别实现,也可以在服务级别并行实现。这种模式对于提供特定的用户体验非常有用。但是,开发团队应该足够小心,将BFF保持在可管理的范围内。
白小白:
“通过聚合器”,原文是“via a facade”,直译是“通过一个外观/门面”。门面模式(外观模式),是一种Java的设计模式,为子系统中的一组接口提供了一个统一的访问接口,引申自一个前店后厂的生意模式,前面是门面,后面会有进料、生产、包装多个服务。用在这里是指将相关的服务通过聚合器聚合在一起,这个聚合器就是门面。服务调用者与门面交互而不是与一组服务交互降低了耦合性,但同时违反了面向对象设计原则开闭原则,开闭原则要求模块在扩展时可以不改动内部的代码,但显然当聚合器后端的某个服务发生变更时,需要在聚合器层面也发生变更,这也是文中说“开发团队应该足够小心”的原因,因为违反了开闭原则,就会降低可复用性。
三、最佳实践
✅ 领域驱动设计:围绕业务领域进行服务建模。
为了处理大型模型和团队,可以应用领域驱动设计(DDD)。DDD通过将大型模型划分为不同的有界上下文来明确他们之间的相互关系和子领域。这些有界上下文可以在应用设计级别转换为单独的微服务。(参见:领域驱动设计中的有界上下文 http://t.cn/EAAK4Xk)
✅ 分散数据管理(避免共享数据库):当多个服务使用一个共享数据架构时,会在数据层形成紧耦合。为了避免这种情况,每个服务都应该有自己的数据存取逻辑和独立数据存储。开发团队可以根据服务和数据性质的不同自由选择最适合的数据持久性方法。
✅ 智能端点和哑管道:每个服务都拥有一个定义良好的外部通信API,并尽量避免泄露实现细节。通信则始终使用简单协议,如基于HTTP的REST协议。
✅ 异步通信:当跨服务使用异步通信时,其他服务不会阻塞数据流。
✅ 避免服务耦合:服务应保持松耦合和高内聚。产生耦合的主要原因包括共享数据库模型和严格的通信协议。
✅ 分散开发:避免在多个服务/项目之间共享代码库、数据架构或开发团队成员。让开发者从源头上关注创新和质量。
✅ 将领域知识排除在网关之外:让网关处理路由和跨服务问题(如身份验证、SSL终端等)。
✅ 基于令牌的认证:不要在每个微服务级别实现安全组件,因为这将需要组件与集中式/共享用户存储库对话并检索身份验证信息;而是考虑实现API网关级别的身份验证,使用广泛使用的API安全标准,如OAuth2和OpenID Connect。一旦从认证提供者获得令牌之后,就可以用于与其他微服务进行通信。
✅ 事件驱动性质:既然人可以成为对事件作出反应的自主主体,系统也可以。(参见:为什么微服务应该是事件驱动的:自主性与权威性 http://t.cn/EAACWOx)
✅ 最终一致性:由于微服务的高内聚特性,很难在整个系统内实现很强的一致性。因此开发团队必须处理最终一致性。
✅ 容错:由于系统由多个服务和中间件组成,因此在某些地方可能很容易发生故障。对于这些薄弱环节,有一些实现模式,如断路器,防水舱,重试,超时,快速失败,故障转移缓存,速率限制,负载释放,可以将重大故障的风险降到最低。(参见:设计一种面向故障的微服务体系结构http://t.cn/RChKlg9)
✅ 产品工程化:把微服务工程化为一种产品,而不是作为一个项目,可以让微服务更好的发挥作用。这意味着,不能仅仅考虑能用而且及时交付,而是要长期致力于卓越的工程化。
四、微服务实践
何时使用微服务
微服务架构最适合的应用场景:
具有高可伸缩性需求的应用
对交付速度要求较高的项目
具有丰富域或多个子域的业务用例
小型、跨功能的开发团队协作开发大型产品的敏捷环境(请参阅:微服务架构的真正成功故事 http://t.cn/EAANng7)
一些实现微服务的入门框架
Vert.x:轻量级,易于理解/实现/维护,多语言支持(支持多种语言),事件驱动,非阻塞,可以说,具备了以最少的硬件处理高并发需求时的最佳性能和可伸缩性,并且具备足够的开放性(与传统的限制性框架不同,Vert.x只提供有用的组件,开发人员可以自由地创新并仔细构建他们的应用程序)
Akka:令人满意的性能,实现了Actor模型(一种并发模型),有利于响应式微服务和事件驱型微服务
Spring Boot/Spring Cloud:容易上手(采用熟悉范式),基于良好的旧Spring框架,有点重的框架,许多集成可用,大规模的社区支持
Drop Wizard:有利于RESTful Web服务的快速开发,它搭载了一些不错的Java工具和库,如Google Guava、Jetty Server、Logback、Hibernate Validator、Joda Time、Jersey和Jackson。
部署选项
容器:有利于执行DevOps目标(快速开发,缩短上市时间,无缝缩放)
云计算架构:有利于构建可靠和可伸缩的基础设施,为地理位置分散的用户服务。
无服务器架构:适合处理高度不稳定的流量。
维护自己的IT基础设施:对那些拥有足够能力和资源建设整个基础设施的组织来说是件好事。
微服务的开发理念
自给系统:由独立系统组装软件(按业务垂直切分系统)
微前端:将单体应用的Web UI划分为独立的特性,这些特性可以作为独立的UI组件开发,并直接与微服务进行通信。
需要搜索和学习的关键词
领域驱动设计(DDD)| 有界上下文(BC)| 聚合持久性(PP)| 命令和查询责任隔离(CQRS)| 命令查询分离(CQS)| 事件溯源(ES)| CAP定理 |最终一致性 |十二要素应用|SOLID原则|
五、参考架构
此体系结构是由使用Microsoft技术的Microsoft开发人员提出的。在这里,API Gateway是针对不同的Web和移动用户而定制的。对于数据层,数据存储技术是根据业务功能仔细选择的(关系数据库用于结构化数据,Redis用于临时数据缓存,MongoDB和Cosmos DB用于非结构化数据)。事件总线处理服务间通信。撇开技术不说,这是基于微服务的应用最常见的集成模式。
一种非阻塞应用程序的微服务体系结构,该应用程序使用来自各种事件源(例如交通数据、天气指数、股票市场线索、社交媒体帖子、传感器输出)的大量输入数据流来向最终用户显示实时更新。这些输入数据流最初由使用Kafka实现的事件日志收集。它将数据保存在磁盘上,因此可以用于批处理调用(分析、报告、数据科学、备份、审计)或用于实时调用(运营分析、CEP、管理仪表板、警报应用程序)。上图中,使用Spark按指定的时间间隔,将持续的输入数据流划分为微批次,并输入到WSO2 Siddhi CEP引擎中。后者标识事件并使用MongoDB存储以非结构化形式存储数据。微服务调取这些数据并显示给最终用户。仔细观察这一设计, Vert.x事件总线能够创建与前端UI组件的连接,该特性仅用于有效地更新UI中的相关部分。撇开技术不说,这是基于事件驱动的非阻塞微服务应用程序的一个很好的架构。
用于订单管理应用程序的云原生泛渠道微服务体系结构(图片:ibm.com)这个设计的一个主要特点是,IBM架构师没有使用API网关,而是为每个客户端通道(移动应用程序、Web应用程序、IOT设备、API使用者)提出了一个具有独立后端的边缘层。另一个特点是将微服务层划分为业务逻辑层和基础层两个子层。基础层(即核心服务层)使用各种云原生服务(云数据存储、集成和索引Watson会话的Elastic搜索引擎)处理持久化和集成任务。业务逻辑层集成了基础层的数据,并提供了有意义的业务功能。这将是一个很好的架构,可以为地理上分散的大量用户群提供服务,并通过各种平台访问应用程序。