目录
一、综述分析
软件架构的复杂度分析通常涵盖了业务复杂度和技术复杂度两个方面。这两个方面相互影响,共同决定了最终的软件系统的复杂性。
业务复杂度:
-
业务流程复杂性: 软件系统的业务流程可能涉及多个步骤、参与者和条件。复杂的业务流程可能需要更多的逻辑和交互,从而增加了系统的复杂性。
-
领域知识复杂性: 如果软件系统涉及复杂的领域知识、业务规则和工作流程,开发团队需要深入理解这些领域概念,这可能会增加开发和交流的难度。
-
数据模型复杂性: 如果业务需要处理大量的数据、不同类型的数据关系以及复杂的查询需求,数据模型的设计和管理可能会变得更加复杂。
-
业务变更频率: 如果业务需求经常变化,软件系统需要具备适应性和灵活性,这可能导致系统设计上的复杂性。
技术复杂度:
-
架构设计复杂性: 选择适当的架构模式、组件分布和通信方式,以满足业务需求可能是一项复杂的任务。
-
分布式系统复杂性: 将软件系统分解为多个服务、微服务或模块,需要考虑通信、数据一致性、负载均衡等分布式系统问题。
-
技术栈选择: 不同的技术栈有不同的优势和限制。选择合适的技术栈可能需要考虑性能、可伸缩性、安全性等多个因素。
-
集成和交互复杂性: 如果系统需要与其他系统集成,协议、数据格式等的统一可能会带来额外的复杂性。
-
性能和可伸缩性: 高性能和可伸缩的需求可能需要考虑缓存、负载均衡、数据库优化等问题,增加了系统的复杂性。
-
安全性和隐私: 系统的安全需求可能会导致加密、认证、授权等的引入,这会增加系统的技术复杂性。
-
错误处理和容错: 处理错误、故障和容错机制需要额外的代码和设计,增加了系统的复杂性。
综合来看,业务复杂性和技术复杂性的相互作用会决定最终的软件系统复杂性。在设计和开发过程中,需要权衡各种需求、挑战和限制,以寻找最合适的平衡点,以确保系统能够满足业务需求并具备良好的可维护性和可扩展性。
二、业务复杂性分析
(一)领域建模
微服务架构的兴起引发了对单体架构弊端的反思,因此,在服务的领域建模和拆分过程中,采用了Eric Evans在2003年提出的领域驱动设计(DDD)原则作为指导原则。
DDD强调了一系列重要概念,以协助在微服务架构中进行领域建模和服务设计:
-
通用语言(Ubiquitous Language): 强调在整个团队中创建一致的业务语言,以确保开发人员和业务专家之间的沟通准确无误。
-
模型驱动设计(Model-Driven Design): 倡导将业务逻辑转化为实际的代码模型,这有助于直接映射业务需求和代码实现。
-
上下文映射(Context Map): 定义了不同界限上下文之间的关系,以协调不同微服务之间的交互。
-
界限上下文(Bounded Context): 将系统划分为一系列明确的业务上下文,每个上下文具有其自己的模型和语言,有助于隔离复杂性。
-
重复概念(Duplicate Concept)和假同源(False Cognate)的识别: 着重在不同上下文中识别并消除相似但不同含义的概念,以确保一致性。
-
消化知识(Crunching Knowledge): 意味着在整个团队中共同理解业务,并将这些知识转化为一致的模型和代码。
通过使用聚合根、实体、值对象、领域服务、应用服务和资源库等编程概念,可以在代码层面上实现领域模型的设计和实现,以反映业务需求。这种方法有助于搭建一个适应微服务架构的清晰、一致且可维护的领域模型。整个过程类似于构建一座巴别塔,通过将知识分解为更小的部分来降低认知负荷,同时保持了一致性。
在领域驱动设计(DDD)中,还有一些重要的模式和原则,用于识别、沟通和选择模型边界以及不同子系统之间的关系,以确保领域模型的完整性和组织间的协作效率。
以下是这些模式和原则的解释,以更清晰的方式表达:
-
共享内核(Shared Kernel): 当两个团队决定共享核心领域或通用子领域时,它们可以通过共享内核来减少重复,使得两个子系统之间的集成更加容易。共享内核可以用于解决生产者和消费者之间的事件共享等情况。
-
客户方-提供方(Customer-Supplier): 在客户方-提供方模式中,客户方服务单向依赖提供方服务。客户方决定提供方的开发自由度,但双方都有各自的领域模型边界和上下文,可以独立发展。
-
跟随者(Conformist): 跟随者模式中,客户方服务单向依赖提供方服务,而提供方倾向于利他主义,向客户方共享信息。作为跟随者,客户方通过严格遵从提供方模型,消除界限上下文之间转换的复杂性。
-
防腐层(Anticorruption Layer): 防腐层模式中,客户方服务单向依赖提供方服务。由于提供方独立演化领域模型,客户方为了减少因提供方模型变化而带来的转换成本,通过使用 Facade 或 Adapter 模式建立防腐层,从而隔离转换逻辑。
-
各行其道(Separate Ways): 在各行其道模式中,双方不考虑集成,各自独立开发,适用于不需要紧密协作的情况。
-
开放主机服务(Open Host Service): 当一个子系统具有内聚性,并满足其他子系统的公共需求时,可以将其封装为服务,通过开放协议供所有需要与之集成的子系统使用。
这些模式和原则帮助团队在微服务架构中进行领域建模和服务设计时做出明智的决策,以确保不同服务之间的协作效率和领域模型的一致性。
(二)领域分层
分层架构的目标是将领域模型隔离开来,以分层的方式分离关注点,并确保本层业务复杂性的变化不会对下层产生负面影响。
在领域驱动设计中,Eric Evans提出了一个四层模型,将不同的组件分别划分到接口层、应用层、领域层和基础设施层。这种划分有助于实现清晰的架构和职责分离。
另外,Vaughn Vernon在《实现领域驱动设计》中提出了端口和适配器架构,也称为六边形架构(Hexagonal Architecture),它已经成为微服务分层架构的事实标准。
这种架构的设计思想是将系统划分为内部核心(领域)和外部适配器(接口),以及通过接口(端口)进行交互。核心包含了领域模型和业务逻辑,而适配器负责与外部系统交互和通信。这种分层和分离的结构有助于实现更好的可维护性、可测试性和灵活性。
总之,分层架构和六边形架构是为了实现系统的解耦、模块化和易于管理,特别适用于领域驱动设计和微服务架构。这些原则和架构有助于管理复杂性,确保系统能够在不同层次上进行适当的变化而不影响其他层次。
接着,Jeffrey Palermo提出了洋葱架构。在端口和适配器架构的基础上贯彻了将领域放在应用中心,将传达机制(UI)和系统使用的基础设施(ORM、搜索引擎、第三方 API...)放在外围的思路,在其中加入了内部层次:代表传达机制和基础设施的外层、 代表业务逻辑的内层。端口和适配器架构与洋葱架构有着相同的思路,它们都通过编写适配器代码将应用核心从对基础设施的关注中解放出来,避免基础设施代码渗透到应用核心之中。这样应用使用的工具和传达机制都可以轻松地替换,可以一定程度地避免技术、工具或者供应商锁定。
然后,Bob大叔(Robert C. Martin)对各种分层架构总结为整洁架构(Clean Architecture)。Herberto Graça进一步总结为清晰架构(Explicit Architecture)。
(三)服务粒度
微服务架构相对于单体架构,能够更有效地将业务复杂性分解到各个微服务中。因此,服务的拆分粒度成为一个关键话题。Neal Ford在他的书《Software Architecture: The Hard Parts》中提出了两个度量指标来客观地衡量服务的大小:
-
语句数量: 这是用于衡量服务大小的一项指标,它能够客观地显示服务所包含的逻辑。更少的语句数量通常意味着更清晰、更专注的服务。
-
公共接口数量: 这是度量和跟踪服务公开的公共接口或操作的数量。更少的公共接口通常表示服务更专注于特定的功能领域。
尽管这些指标仍然存在一些主观性和可变性,但它们是迄今为止较接近客观地衡量和评估服务粒度的方法。
此外,以下是6条衡量服务拆分的标准:
-
服务范围及功能: 评估服务是否做了过多不相关的事情,是否足够内聚,并且是否遵循单一责任原则。
-
代码的波动性: 考虑更改是否可以局限在服务的特定部分内,以及是否存在频繁更改的区域。
-
可伸缩性和吞吐量: 分析服务的不同部分是否可以以不同的方式扩展,以满足不同的吞吐量需求。
-
容错: 评估服务内部是否存在可能导致关键功能失败的错误,以及这些错误是否频繁发生。
-
安全: 考虑是否有些服务部分需要更高的安全级别,以确保敏感数据和功能的安全访问。
-
可扩展性: 分析服务是否始终在扩展以添加新的上下文,以应对未来的变化。
另外,以下是4条衡量服务合并的标准:
-
数据库事务: 考虑是否需要在服务之间进行ACID事务,以确保数据的完整性和一致性。
-
工作流和编舞: 评估是否存在需要影响性能或者涉及Saga事务的编舞模式,以及服务之间的相互通信。
-
共享代码: 考虑是否有需要共享代码的情况,以及共享的代码库是否频繁更改、特定于共享域功能,以及是否存在版本控制问题。
-
数据关系: 分析服务拆分是否保证了使用的数据也可以拆分,以避免数据关系问题。
在决定如何拆分和合并服务时,需要权衡这些标准,以确保系统的架构在不同方面都具有良好的质量和性能。
(四)流程编排
业务流程代表了业务逻辑的执行序列,将其进行编排并以可视化方式展示,可以有效地连接不同业务环节,降低认知负荷,从而降低业务的复杂性。
在微服务架构中,服务内和服务间的流程编排是一个重要议题。微服务架构在处理业务流程、监控状态以及处理流程中的故障方面存在一些挑战。因此,我们需要在微服务架构上丰富设计原则和架构决策,以探索并不断完善基于微服务的流程编排机制。
在过去的SOA时代,许多单体服务使用BPEL引擎或企业服务总线(ESB)来进行流程编排。ESB通过消息管道来实现各个子系统之间的通信交互,使得服务能够在ESB的调度下相互通信而不直接依赖。然而,这种"强管道弱端点"(Smart Pipe and Dumb Endpoint)的模式将大量逻辑移到网络中,导致系统昂贵、复杂,并且几乎无法排除故障。
在当今时代,我们更加注重构建小型、单一用途的微服务,通过"强端点弱管道"(Smart Endpoints and Dumb Pipes)的方式进行连接和通信。微服务内部的组件具备更强大的业务流程处理能力,而服务之间的流程编排则遵循saga模式,以实现跨多个服务的事务操作。
总之,业务流程的编排在微服务架构中具有重要意义,需要考虑如何在微服务架构下实现流程的监控、协调以及故障处理,以满足现代系统对于业务流程的需求。同时,微服务架构的强调点在于强化每个服务的独立性和处理能力,同时在服务之间采用灵活的流程编排方式。
三、技术复杂性分析
(一)高可用
底层逻辑
CAP原则
也被称为布鲁尔定理(Brewer's theorem),是由加州大学的计算机科学家Eric Brewer于1998年提出的,后由Gilbert和Lynch于2003年给出了证明。它关注的是在分布式系统设计中的三个核心特性:一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance)。CAP原则指出,在分布式系统中,这三个特性最多只能同时满足其中的两项,而无法同时满足全部三项。
具体而言:
-
一致性(Consistency):在分布式系统中的所有节点上,对于同一份数据的读操作,返回的应该是最近的写入结果。即系统保证数据一致性,即使是在分布式环境下。
-
可用性(Availability):每个请求都能够获得一个非错误响应,即系统保证服务始终可用,不会因为部分节点故障而无法提供服务。
-
分区容忍性(Partition tolerance):系统在遇到节点之间的通信分区(网络故障)时,仍然能够继续运行。分区是指节点之间无法相互通信,但节点与外部仍然可以通信。
CAP原则的重要性在于,由于分布式系统的特点,无法在所有情况下同时保证数据的一致性、可用性和分区容忍性。因此,在设计分布式系统时,必须根据具体的需求和场景来权衡和选择满足的特性组合。
简而言之,CAP原则提醒我们在分布式系统设计中需要做出取舍,根据实际情况选择满足一致性、可用性和分区容忍性中的两项。
这里我们对一致性进行展开分析,在分布式系统中,一致性有不同的层级和类型,包括以下几种:
-
强一致性(Atomic Consistency/Strong Consistency): 在分布式系统中,强一致性要求在所有节点上的读操作都返回最新的写入结果,即读操作会等待直到所有副本都已更新并达到一致状态。这确保了数据的严格一致性,但可能会导致一些性能损失。
-
最终一致性(Eventual Consistency): 最终一致性放松了实时一致性的要求,允许数据在一段时间内存在不一致,但最终会趋向于一致状态。系统会保证在一段时间后,所有副本最终都会收敛到一致状态。
-
顺序一致性(Sequential Consistency): 顺序一致性要求在分布式系统中,操作的执行必须满足一定的偏序关系,即按照一定的顺序执行。这并不一定要求全局的全序,只需要满足一些特定的偏序关系即可。例如,Zookeeper实现的就是顺序一致性。
-
线性一致性(Linearizable Consistency): 线性一致性要求操作的执行必须满足全局的全序关系,即操作必须在某个全局的顺序下依次执行。这通常要求更高的性能,但可以保证强一致性。例如,etcd实现的是线性一致性。
这些不同级别的一致性提供了在分布式系统中根据需求权衡性能和数据一致性的选择。强一致性提供了最高的数据一致性,但可能牺牲性能;而最终一致性则允许更高的性能,但可能会在一段时间内出现数据不一致的情况。顺序一致性和线性一致性在不同情况下也提供了不同的权衡选择。根据应用的需求,可以选择适当的一致性级别来平衡性能和数据一致性。
BASE理论
BASE理论是分布式系统中的一个设计思想,它是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistency)三个短语的缩写,由eBay架构师Dan Pritchett提出。
在BASE理论中,各个概念的含义如下:
-
基本可用(Basically Available): 在系统出现故障或异常情况时,允许部分可用性的降低,但仍保证核心功能的可用性。这意味着系统可以在有限的损失下继续提供服务,而不会完全瘫痪。
-
软状态(Soft State): 允许系统在一段时间内存在中间状态,而这些状态不会影响系统整体的可用性。这通常涉及到副本同步的延迟现象,即数据在不同节点之间可能会有一段时间的不一致。
-
最终一致性(Eventually Consistency): 这是在分布式系统中追求的一种一致性水平。它表示,虽然数据在不同节点上可能会存在一段时间的不一致,但最终数据会在某个时刻达到一致的状态。系统可以采用异步的方式来处理数据的同步,从而在性能方面获得更大的灵活性。
BASE理论实际上是对CAP原则的一个演化,它强调在分布式系统中,不一定要追求强一致性,而是可以根据业务的特点和需求,权衡一致性和可用性。每个应用都可以根据自身情况,采用适当的方式来达到最终一致性,即在一段时间后,数据会最终达到一致状态。这种方法在大规模互联网系统中得到了实践和验证。
可用性与可靠性
在谈到高可用性时,我们经常会涉及到SRE(Site Reliability Engineering)。高可用性指的是系统在很大一部分时间内都能够保持可用状态,而SRE则关注系统的可靠性工程,即确保系统在运行过程中能够稳定、可靠地工作。
可用性是对系统可供使用的时间进行描述,这取决于系统在特定时间段内的运行时间。系统的可用性越高,意味着系统在更长的时间内都处于可用状态,中断时间较短。
可靠性则是关于系统故障间隔的描述,它考虑了系统在运行中出现的故障次数。SRE的目标就是通过一系列工程实践,减少系统出现故障的次数,从而提高系统的可靠性。
可用性级别 |
业务可用性 |
年度宕机时间 |
周宕机时间 |
日宕机时间 |
---|---|---|---|---|
99% |
基本可用性 |
87.6小时 |
1.68小时 |
14分钟 |
99.9% |
较高可用性 |
8.76小时 |
10.1分钟 |
86秒 |
99.99% |
高可用性 |
52.6分钟 |
1.01分钟 |
8.6秒 |
99.999% |
很高可用性(电信) |
5.26分钟 |
6.05秒 |
0.86秒 |
99.9999% |
极高可用性(航天) |
为了衡量可靠性,通常使用以下三个指标:(MTBF = MTTF + MTTR)
-
平均故障间隔(Mean Time Between Failure, MTBF): 它表示系统在运行一段时间后,平均会发生故障的时间间隔。较长的MTBF意味着系统较少出现故障。
-
平均无故障时间(Mean Time To Failure, MTTF): 它表示系统在正常工作状态下的平均运行时间。较长的MTTF表明系统在正常情况下能够稳定运行。
-
平均故障恢复时间(Mean Time To Repair, MTTR): 它表示系统从发生故障到成功恢复的平均时间。较短的MTTR意味着系统在出现故障后能够迅速恢复正常状态。
可用性A = UpTime/(UpTime+DownTime) = MTBF / (MTBF + MTTR)
综上所述,高可用性强调系统在大部分时间内的可用性,而SRE关注通过提高可靠性工程水平,降低系统故障发生的频率和持续时间,从而实现系统的稳定、可靠运行。
99.9 = (1年 = 365天 = 8760小时) * 0.1% = 8760 * 0.001 = 8.76小时
年宕机时间 |
年宕机次数 |
|
---|---|---|
系统A |
5分钟 |
50次 |
系统B |
1小时 |
1次 |
A.可用性 > B.可用性
A.可靠性 < B.可靠性
韧性工程
韧性工程指的是一种架构特性,它允许系统对于预期的故障保持容忍。这包括多种方面,如容错、容灾、自愈和灾难恢复等。
关于恢复能力的关键指标:
-
恢复点目标(Recovery Point Objective, RPO): 这是系统能够容忍的最大数据丢失量。它衡量了容灾系统的数据冗余备份能力,RPO值越小,表示系统对数据丢失的容忍度越低。
-
恢复时间目标(Recovery Time Objective, RTO): 这是系统能够容忍的服务停止的最长时间。它衡量了容灾系统的业务恢复能力,RTO值越小,表示系统要求更快的业务恢复时间。
在David D. Woods的《韧性工程》中,韧性工程关注以下四个概念:
-
鲁棒性(Robustness): 系统吸收预期扰动的能力。
-
反弹(Rebound): 系统从创伤事件中迅速恢复的能力。
-
优雅的可扩展性(Graceful Extensibility): 系统对非预期情况的适应和处理能力。
-
持续适应性(Sustained Adaptability): 系统持续适应不断变化的环境、涉众和需求的能力。
以当前的Service Mesh为例,涉及的微服务韧性技术包括:
- 服务超时(Timeout)
- 服务重试(Retry)
- 服务限流(Rate Limiting)
- 熔断器(Circuit Breaker)
- 故障注入(Fault Injection)
- 舱壁隔离技术(Bulkhead)
此外,还包括服务扩缩容过程中的快速启动和优雅下线、服务注册和发现中的负载均衡、离群逐出,以及流量控制中的灰度发布、AB测试、蓝绿部署等技术。这些方法都有助于构建具有高度韧性的系统,能够在面对故障和不确定性时保持稳定和可靠。
混沌工程
混沌工程是一门在分布式系统中进行实验的学科,其目的是在生产环境中建立对系统在失控条件下的抵抗能力和信心。混沌工程采用“可证伪性”的科学实验方法,关注验证所定义的系统稳态假设是否能在生产环境的动荡条件下被证伪,从而增强对系统的信心。通过混沌工程,可以建立面对非预期系统状况的韧性文化。
在塔勒布(Nassim Nicholas Taleb)于2012年出版的《反脆弱:从无序中获益》中,脆弱性指的是因为波动和不确定性而遭受损失,而反脆弱性则是通过适应混乱和不确定性来获益,甚至从中获利。
混沌工程提出了与韧性工程、人因学和安全系统研究相悖的思想。它建议在提高系统鲁棒性方面,应该寻找并消除系统的弱点,而不仅仅是通过韧性工程来寻找做错的地方。混沌工程强调选择正确的实验,以了解被测系统的稳态,这需要领域专家对系统的内部逻辑有深入理解,特别是要了解系统如何在设计中考虑到容错原则和预期故障。这也意味着不仅要理解系统可以容忍哪些故障,还要了解系统在实际运行中如何应对符合预期的情况。
混沌工程对于SRE和开发团队来说,不仅是诊断错误,更重要的是研究哪些操作是正确的。这需要选择适当的实验,以深入理解被测系统的状态。这种方法强调了对系统的深入了解,包括系统的容错逻辑和实际运行情况,从而提高系统的韧性和稳定性。
可观测性
可观测性在韧性工程和混沌工程中扮演重要角色,也是服务网格的三大特性之一,而服务网格则是为云原生服务治理而设计的。
可观测性是指在分布式系统中实时采集遥测数据,通过在管控侧(控制平面)观察这些数据,决定是否进行干预,并将干预规则下发到运行侧(数据平面)。这是实现高可用的关键方法,最终目标是对微服务或云原生服务进行治理。
可观测性涉及以下三个关键方面:
-
Metrics(指标):用于记录连续的、可聚合的数据。比如,队列的深度或HTTP请求的数量。这有助于发现异常和设置告警。
-
Logging(日志):用于记录离散的事件,如应用程序的调试信息或错误信息。它是问题诊断的基础。
-
Tracing(调用链跟踪):用于记录请求范围内的信息,例如远程方法调用的执行过程和耗时。它有助于排查系统性能问题。
这三者是可观察性的基础,相辅相成:Metrics用于发现异常,Tracing用于定位问题,Logging用于查找错误根源。这个过程是迭代的,基于之前的问题分析来调整Metrics,以更早发现或预防类似问题。
在分布式跟踪基础设施方面,大多数是基于Google的Dapper设计。例如,Twitter的Zipkin和Uber的Jaeger都是分布式跟踪工具。OpenTracing是调用链跟踪图的开放标准,被广泛采用,与Zipkin、Jaeger等工具兼容。
OpenCensus是谷歌推出的,与OpenTracing不同的是,它还包括了Metrics,并提供Agent和Collector用于数据采集。OpenTelemetry结合了OpenTracing和OpenCensus,进入CNCF的Sandbox项目,致力于统一Metrics、Tracing和Logging的终极解决方案,提供统一的上下文存储和传播,以实现综合的可观测性。
扩展:Apache应用程序性能监控(Application Performance Monitor, APM)工具SkyWalking
(二)性能
在技术架构领域,"高性能"这个词在一定程度上可以看作是修辞上的一种表述。在软件架构的语境下,"高性能"实际上更接近英文中的 "Performance",它强调系统在各个方面的表现和效率。
下面是关于高性能在技术架构中的一些内涵:
性能的不同维度:
在不同的上下文中,性能可能包含以下一到多个方面:
-
短的响应时间/低延迟: 这强调系统在处理请求时的快速响应,确保用户或客户不会因等待时间过长而感到不满。
-
高吞吐量: 这关注系统能够处理的工作量,即在给定时间内能够完成多少任务,保证高效率的处理。
-
资源利用率低: 这意味着系统在完成任务时有效地利用计算资源,以避免资源浪费。
-
容量: 这指的是系统的承载能力,即系统可以同时处理多少个请求或任务,通常与系统的架构和规模有关。
响应时间和延迟:
虽然响应时间和延迟通常被当作同义词使用,但实际上它们并不完全相同。响应时间是指客户或用户感知到的总体等待时间,包括处理时间、网络延迟和排队延迟。而延迟则特指某个请求等待处理的时间,这个时间段内请求处于休眠状态。
性能效率的维度:
性能效率涉及多个方面,可以从时间行为、资源利用率和容量三个维度来理解:
-
时间行为: 这包括系统的响应时间和处理速度,即处理请求所需的时间以及系统能够处理的请求数量。
-
资源利用率: 这关注系统在运行过程中的资源利用情况,如 CPU、内存、网络等,避免资源浪费和过度使用。
-
容量: 容量指系统能够承受的负载,包括峰值负载和长期负载,确保系统在各种情况下都能正常运行。
不同场景下的性能考虑:
对于不同类型的系统,性能的关注点会有所不同。例如,批处理系统可能更关心吞吐量,而在线系统则更注重响应时间。根据具体场景,可以采取不同的策略来提高性能,如通过异步机制提高吞吐量,通过缓存和索引降低检索延迟。
在构建技术架构时,考虑到性能的多个方面,根据具体需求进行优化,能够帮助系统在各种情况下都能表现出高效率和高性能。
(三)事件驱动架构
微服务和事件驱动架构(EDA)被视为现代架构风格中的两个核心概念,前者侧重于数据中心的请求驱动,而后者强调事件为中心的驱动。
Martin Fowler提出了三种不同类型的事件模式,这些模式在构建分布式系统时起到重要作用:
-
事件通知: 这个模式涉及到在系统中的某个事件发生时,系统会通知感兴趣的其他部分。这种模式可用于实时通知、发布订阅模式等,以便在事件发生时执行适当的动作。
-
事件携带状态转换: 在这个模式下,事件不仅仅是一个通知,还携带了一些状态信息,以便接收方可以基于事件的状态进行处理。这种模式常用于状态机、工作流等场景,帮助系统在状态变化时进行协调。
-
事件溯源: 事件溯源是一种记录系统状态变化的方式,通过记录每个事件的发生,可以重新构建出系统的历史状态。这对于审计、故障排查以及实现时间旅行等特性非常有用。
这些事件模式都有助于实现分布式系统中的事件驱动架构,通过事件的发布、订阅和处理,系统可以更加灵活地响应变化和交互。
事件
事件是已经发生的事实,并且是不可变的。相比而言,消息是一个服务为了另一个服务的消费或存储而生产的原始数据,消息是可以被修改的。
事件的生产者如实地产生和投递事件,它不关心这个事件将由谁、因何,以及怎样去处理。而消息的生产者是知道谁来消费的,并且知道封装哪些因素到消息中,以便消费者处理。
事件的Broker被设计为提供事实日志。事件在超时时间后被删除,这个超时时间是由组织或者业务定义的。而消息的Broker被设计为处理各类问题的,当消费者感知到消息后,消息即可被删除。
事件 |
消息 |
|
---|---|---|
Data |
已经发生的事实,并且不可变(Immutable) |
为消费或存储而生产的原始数据 |
Producer/Consumer |
生产者不知道消费者是谁以及如何处理 |
生产者知道消费者是谁以及如何处理 |
Broker |
提供事实日志 超时时间后,事件被删除 |
处理各类问题 被消费者感知后,消息被删除 |
-
离散事件:描述状态(state)的变化、是可执行的
-
连续事件:描述处于怎样的状态(condition)、是可分析的
通常,事件是离散的,用于描述一个事物的状态变化,可以被执行。消费者根据离散事件所描述的状态,执行相应的动作。
事件也可以是连续数据流中的一部分,用来描述一个事物当前处于某种状态下。这些连续的事件是可分析的,消费者可以根据这些状态的变化,分析出某种趋势及背后的原因。
事件应当被设计为最小尺寸、最简类型、单一目的。这里要着重介绍下CloudEvents。CloudEvents在2018年5月进入CNCF基金会的沙箱项目,然后只用了1年多时间就成为CNCF的孵化项目,其发展速度非常快。CloudEvents将会成为云服务之间,事件通讯的标准协议。同时要强调的是,CloudEvents已经发布了多个消息中间件的绑定规范。
服务定义模式
我们已经知道,事件的生产者并不知道消费者是谁,因此不能像消息那样预先定义消息的格式。因此,在事件驱动架构中,需要一个Schema Registry为生产者提供序列化依据,为消费者提供反序列化依据。
Schema类似gRPC中的proto定义。在请求驱动模式下,gRPC的服务端和客户端会分别根据proto定义,生成stub模板代码。然后将模板代码提供给自己的上层代码调用,从而实现序列化和反序列化。
与之类似,在事件驱动模式下,消费者在获取事件后,可以根据CloudEvents标准协议,解析出Schema和Content(通常是二进制),然后通过消费者调用Schema Registry服务,将Content反序列化为事件体。
可以看到,事件的服务定义模式,可以将事件的生产者和消费者充分地解耦。
EventBridge
EventBridge是为用户提供构建松耦合、分布式的事件驱动架构的Serverless事件总线服务。EventBridge的事件传输和存储遵循CloudEvents协议。
在EventBridge中,事件的生产者称为事件源,传输和存储事件的介质称为事件总线,事件的消费者称为事件目标。事件由事件规则转换、匹配、聚合,并路由到事件目标。
EventBridge连接了事件生产和消费的两端,为用户提供了低代码、松耦合、高可用的事件处理能力。EventBridge基于标准的事件协议,有利于促进各类事件源的事件标准统一,使事件孤岛逐步融合进完整的事件生态体系之中。( 扩展阅读:AWS EventBridge)
(四)云原生
怎样才算云原生
云原生是一种软件开发和部署的方法论,旨在最大程度地利用云计算环境的优势,以提高应用程序的灵活性、可扩展性和可维护性。一个应用程序被认为是云原生的,需要满足一系列特征和原则,以确保其在云环境中能够充分发挥优势。
其中,云原生应用服务具备以下特点:
-
自动化: 云原生应用通过自动化的方式进行部署、管理和扩展。自动化能够减少人工干预,提高系统的弹性和可靠性。
-
弹性: 云原生应用可以根据负载的变化自动进行伸缩,以满足不同的需求。这使得应用可以有效地处理高峰和低谷的流量。
-
可观测性: 云原生应用通过集成各种监控、日志和追踪工具,可以实时地监测和分析应用的性能和状态,以便及时发现和解决问题。
-
松耦合: 云原生应用使用微服务架构,将应用拆分成小型、独立的服务。这些服务可以独立开发、部署和扩展,从而提高团队的效率。
-
容器化: 云原生应用使用容器技术进行打包和部署。容器可以在不同的环境中运行,保证应用在各种场景下一致的行为。
-
持续交付: 云原生应用通过持续集成和持续交付流程,实现快速的代码提交、构建和部署,从而加快新功能的发布速度。
-
基础设施即代码: 云原生应用将基础设施的管理和配置纳入到代码中,通过编程的方式定义和管理基础设施,确保一致性和可重复性。
-
服务网格: 云原生应用借助服务网格实现服务之间的通信和协调,将通信逻辑从应用代码中解耦,提供更好的可观测性和控制性。
总之,云原生应用以一种更现代、更敏捷的方式构建和运行应用,充分利用云计算和容器技术的优势,提供更好的性能、可靠性和可扩展性。它强调自动化、弹性、可观测性和松耦合,以满足快速变化的业务需求。
不可变基础设施
Chad Fowler引入的"不可变基础设施架构"(Immutable Infrastructure)原则是指,一旦创建基础设施的实例后,它们进入只读状态,任何修改或升级都需要通过替换新的实例来实现。这个方法强调了部署的简易性、可靠性和一致性,有效地减少了许多常见的问题和故障。
不可变基础设施的优势在于增加了基础设施的一致性和可靠性,同时简化了部署流程并提供了更可预测的环境。它可以避免诸如配置漂移和雪花服务器等在可变基础设施中常见的问题。然而,要有效地运用不可变基础设施,通常需要具备自动化部署的能力、云计算环境中的快速配置服务器的能力,以及处理有状态数据或临时数据(例如日志)的解决方案。
在比较中,过去的操作方式将服务器视为"宠物",例如将邮件服务器称为"Bob"。如果"Bob"出现问题,可能会导致整个系统崩溃。而现代的方式将服务器视为编号的"牛",例如从"www001"到"www100"。当某个服务器故障时,可以将其停用,然后用新的服务器取而代之,实现快速替换。
"雪花服务器"类似于"宠物",它们需要手动管理,可能会因频繁的更新和调整而导致环境变得独特。相反,"凤凰服务器"类似于"牛",总是通过自动化流程从头开始构建,可以轻松地重新创建或"重生"。
"基础设施即代码"(Infrastructure as Code,IaC)是一种将基础设施层描述为代码,并通过代码库进行版本控制的方法。这种方法使用工具(例如Terraform)来自动化基础设施的创建和管理。
机甲运行时
在《多运行时微服务架构》一书中,Bilgin Ibryam将现代分布式应用的需求分为四个主要类型:
-
生命周期(Lifecycle):涵盖了组件的打包、部署、运行过程,以及从错误中恢复和扩展服务等方面。这一类需求关注整个应用的生命周期管理,包括如何高效地进行部署、运行时故障处理和自动扩展等。
-
网络(Networking):涉及服务发现、错误恢复、跟踪和遥测等。此外,它还包括消息交换模式,如点对点通信和发布/订阅模式,以及智能路由等。这方面的需求关注如何构建强大的网络架构,以支持应用的通信和交互。
-
状态(State):在这一类需求中,状态既指服务本身的状态,也指服务管理平台的状态。状态管理在执行可靠的服务编排、分布式任务调度、临时任务调度(如定时作业)、幂等性操作、有状态错误恢复、缓存等方面都是重要的。需要关注底层状态管理机制。
-
绑定(Binding):分布式系统的组件需要与彼此通信,还需要与外部系统集成。这意味着需要连接器能够支持各种协议转换、消息交换模式、格式转换、自定义错误恢复过程和安全机制等。这一类需求关注如何实现强大的绑定和集成能力。
Bilgin Ibryam提出了Mecha作为未来架构的趋势,它是一种作为业务服务外部扩展机制(Sidecar)的概念。Mecha具有以下特点:
- Mecha是通用、高度可配置且可重用的组件,提供了分布式原语,可以直接用于构建应用的能力。
- 每个Mecha实例可以配置为与单个Micrologic(业务组件)一起使用,也可以与多个组件共享。
- Mecha不对Micrologic运行时做任何假设,可以与多语言的微服务甚至单体系统一起使用,使用开放的协议和格式。
- Mecha通过简单的文本格式(如YAML、JSON)进行声明式配置,定义要启用的功能以及如何将其绑定到Micrologic的端点。
- 对于特定的API交互,可以附加规范,如OpenAPI、AsyncAPI、ANSI-SQL等。
- Mecha的设计目标是将不同功能的代理合并为一个,例如网络代理、缓存代理、绑定代理等,提供多种能力的集成。
- 一些与分布式系统的生命周期管理有关的问题可以由管理平台(如Kubernetes或其他云服务)提供,而Mecha运行时则遵循通用的开放规范(如Open App Model)。
这种架构思想的目标是提供更灵活、可扩展、可靠且易于管理的微服务应用,同时充分利用现代云原生技术和开放协议。
服务网格
服务网格是一种用于云原生服务之间通信的基础设施层,采用Sidecar模式提供韧性、流量转移、通信安全和可观测性等能力。它的核心思想是通过容器技术,以无侵入的方式对微服务架构进行升级和演化。
在服务网格中,Sidecar模式的主要优势在于将基础设施能力下沉并独立部署。这意味着基础能力的升级不会影响到服务本身,从而实现了解耦。然而,这种模式的一个主要挑战是业务容器与Sidecar容器需要共存于同一个POD内,这增加了技术复杂性。
目前,主要的服务网格产品包括由Google主导的Istio(其中数据平面的Sidecar内核是Envoy)和CNCF维护的Linkerd。这些产品旨在提供一种灵活且强大的解决方案,以支持云原生应用之间的通信需求,并为微服务架构带来更好的可维护性和可扩展性。
分布式应用运行时
分布式应用运行时(Distributed Application Runtime,简称Dapr)同样采用了Sidecar模式,并借助容器技术实现。然而,Dapr的核心目标不同,它在开发阶段提供了一个框架,在运行时提供了类似于Mecha的能力,有点类似于Java EE的理念。具体而言,Dapr是一个可移植的、无服务器的、事件驱动的运行时环境。它的设计使开发人员能够轻松地构建弹性、无状态和有状态的微服务,这些微服务可以在云端和边缘环境中运行,从而降低了构建现代云原生应用的门槛。
通过Dapr,开发人员可以摆脱复杂的底层实现细节,专注于业务逻辑的编写。Dapr提供了一系列的构建块和组件,用于处理常见的分布式系统问题,如服务间通信、状态管理、事件处理等。这些功能以Sidecar模式部署,与业务容器共存于同一个应用实例中。这种设计旨在让开发人员能够更容易地创建可靠、可观察和可扩展的分布式应用。
总之,Dapr的目标是简化分布式应用的开发和部署,提供一种更高级别的抽象,使开发人员能够更加专注于业务逻辑的实现,而无需过多考虑底层的复杂性。
Apache Camel
Apache Camel是一个基于企业集成模式(Enterprise Integration Patterns,EIP)的框架,它提供了丰富的连接器和组件,用于集成各种不同的系统和应用。Apache Camel的目标是简化不同应用之间的集成,使数据在不同系统之间流动变得更加容易。它可以看作是Mecha绑定的新趋势。
以下是一些Apache Camel的项目:
-
Camel Core:这是Apache Camel的核心项目,提供了集成各种系统以生产和消费数据的框架。
-
Camel Karaf:支持在OSGi容器Karaf上运行Camel。
-
Camel Kafka Connector:将所有的Camel组件作为Kafka Connect连接器使用,使Camel能够与Apache Kafka集成。
-
Camel Spring Boot:通过自动检测Spring上下文中可用的Camel路由,为Camel上下文提供了自动配置,并将关键的Camel实用程序注册为bean,方便在Spring Boot应用中集成使用。
-
Camel Quarkus:将超过280个Camel组件移植并打包为Quarkus的扩展,以便在Quarkus应用中更轻松地使用Camel。
-
Camel K:是一个轻量级的集成框架,构建在Apache Camel之上,专为无服务器和微服务体系结构设计,在Kubernetes上运行。它旨在为在Kubernetes环境中运行的应用程序提供集成能力。
总之,Apache Camel为开发人员提供了丰富的工具和组件,使得在不同系统之间构建数据流和实现集成变得更加简单和高效。
可伸缩性和弹性
云原生架构为部署在其上的服务提供了即用即弹的能力,这意味着服务可以根据需要自动扩展或收缩。这种能力保证了服务的高可用性,因为它可以根据负载情况动态调整资源以确保服务始终可用。同时,这种弹性特征也为我们带来了削峰填谷的能力,这意味着服务可以在需要处理高负载时自动扩展,而在负载减少时自动缩减资源。这不仅提高了服务的性能,还降低了服务的成本,因为资源利用率得到了最大程度的优化。
Neal Ford在《软件架构基础》一书中对这两个概念进行了明确的定义:
-
可伸缩性:指的是在不出现严重性能下降的情况下,系统能够处理大量并发用户的能力。换句话说,当用户数量增加时,系统能够保持相对稳定的性能水平,而不会因为负载增加而出现明显的性能问题。
-
弹性:指的是系统处理突发请求的能力。当系统面临突发的高负载或异常情况时,弹性系统能够适应这些情况并继续提供合理的服务质量,而不会崩溃或出现不可接受的延迟。
总的来说,可伸缩性和弹性是云原生架构的两个重要特征,它们共同确保了服务的高可用性、性能优化和成本效益。
无服务架构
无服务架构(Serverless)的核心理念是让开发者能够专注于业务逻辑,而无需过多关注底层的技术细节。在无服务架构下,开发者不需要担心部署、资源管理等技术层面的问题。这种架构提供了极高的弹性,与事件驱动架构(Event-Driven Architecture,EDA)结合使用,在离线批处理和流式计算等场景中具有广泛的应用前景。
目前,无服务架构主要分为以下两个部分:
-
后端即服务(Backend as a Service,BaaS):这部分涵盖了数据库、消息队列等基础设施,用于支撑业务的同时没有业务逻辑。BaaS将底层的技术细节抽象出来,让开发者可以直接使用这些服务,无需关心它们的底层实现。
-
函数即服务(Function as a Service,FaaS):这部分涵盖了业务逻辑函数。在FaaS中,开发者只需要编写处理特定任务的函数,然后将这些函数部署到云平台。云平台会根据事件触发这些函数,从而实现业务逻辑。这种架构使开发者可以更加专注于业务逻辑,而不需要考虑底层的资源管理和部署。
FaaS全景图展示了无服务架构中的函数即服务生态系统,包括了50多种产品和工具,这些产品和工具可以帮助开发者构建和管理无服务应用。无服务架构的主要目标是减轻开发者的负担,让他们能够更加聚焦于业务创新和价值创造。