云原生|我对云原生软件架构的观察与思考 Posted 2021-04-03 云服务圈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了云原生|我对云原生软件架构的观察与思考相关的知识,希望对你有一定的参考价值。
作者 | 易立,阿里云资深技术专家,容器技术负责人
第一篇 - 云原生基础设施 (已发布,文末点击阅读原文查看)
前言
在《云原生基础设施》一文中我们谈到了,云原生计算包含三个维度的内容,云原生基础设施,软件架构和交付与运维体系,本文将聚焦于软件架构层面。
“Software architecture refers to the fundamental structures of a software system and the discipline of creating such structures and systems. ” - 维基百科。
云原生应用架构的目标是构建松耦合、具备弹性、韧性的分布式应用软件架构,可以更好地应对业务需求的变化和发展,保障系统稳定性。本文将分享我在这个领域的观察和思考。
缘起 - 12要素应用
2012 年,Heroku 创始人 Adam Wiggins 发布十二要素应用宣言。它为构建一个优雅的互联网应用,定义了需要遵循的一些基本原则和方法论,也广泛影响了众多的微服务应用架构。十二要素重点关注:应用程序的健康成长,开发者之间的有效的协作,以及避免软件架构腐化的影响。其内容在今天也值得每个同学认真体会。
图片来源:https://12factor.net/zh_cn/
12 要素应用为我们提供了很好的架构指导,帮助我们:
构建水平伸缩的弹性应用架构,更好支撑互联网规模应用。
减少开发环境和生产环境的差异,并使用持续交付实施敏捷开发。
提升应用的可移植性,适合云化部署,降低资源成本和管理复杂性。
松耦合架构设计
微服务的核心理念是,系统中的各个服务可被独立开发、独立部署,独立升级,各个服务之间是松耦合的。云原生应用架构理念是进一步强调架构的松耦合,降低服务之间相互依赖的程度。
API 优先的应用架构设计
在面向对象的软件架构中,最重要的是定义对象以及对象的接口契约。SOLID 原则是最被人广为熟知的设计原则。
Single responsibility principle - 单一职责原则
Open/closed principle - 开放/封闭原则
Liskov substitution principle - 里氏替换原则
Interface segregation principle - 接口隔离原则
Dependency inversion principle - 依赖翻转原则
将以上五个原则的英文首字母拼在一起就是 SOLID 原则,这也是帮助我们构建高内聚,低耦合、具备柔性的应用架构。在分布式微服务应用架构中,API优先是契约优先(Contract First)的自然拓展。
API 应该是被优先设计的:
我们知道用户需求是复杂多变的,比如从桌面到移动端,应用的展现方式和操作流程都有可能不同;然而业务逻辑的概念模型和服务交互是相对稳定的。相对而言,API 的接口是更加稳定的,而具体的实现是可以迭代实现和持续变化的。定义良好的 API 可以更好保障应用系统的质量。
API 应该是声明式,可描述/自描述的:
通过规范化的描述,API 易于沟通、易于理解、易于验证,简化开发协同。支持服务的消费者和提供者并行开发,加速开发周期。支持不同的技术栈的实现,比如对于同一个 API 接口,其服务实现采用 Java 。前端应用可以使用 javascript ,而服务器端应用可以使用 Golang 进行服务调用等等。这样可以让开发组织可以根据自己的技能栈和系统要求灵活选择合适的技术。
API 应该具备 SLA :
API 作为服务间的集成界面,与系统的稳定性息息相关。SLA 应该作为 API 设计的一部分,而不是部署后再考虑。在分布式系统中,稳定性风险无处不在,通过 API 优先的设计模式,我们对独立的服务进行稳定性架构设计、容量规划;我们还可以对独立的 API 进行故障注入、稳定性演练,来消除系统性的稳定性风险。
在 API 领域,最重要的趋势是标准化技术的崛起。
gRPC 是 Google 开源的的高性能、通用的、平台无关的 RPC 框架。它采用分层设计,其数据交换格式基于 Protobuf (Protocol Buffers) 协议开发,具备优秀的序列化/反序列化效率,也支持众多开发语言。在传输层协议, gRPC 选择了 HTTP/2,相较于 HTTP/1.1,其传输效率有了很大提升。此外 HTTP/2 作为一个成熟的开放标准,具备丰富的安全、流控等能力,同时拥有良好的互操作性。gRPC 不仅可以用于 Server 端服务调用,也可以支持浏览器、移动 App 和 IoT 设备与后端服务的交互。gRPC 在功能上已经具备完整的 RPC 能力,也提供了扩展机制来支持新的功能。
在 Cloud Native 的潮流下,跨平台、跨厂商、跨环境的系统间互操作性的需求必然会催生基于开放标准的 RPC 技术,而 gRPC 顺应了历史趋势,得到了越来越广泛地应用。在微服务领域, Dubbo 3.0 宣布了对 gRPC 协议的支持,未来我们也会看到更多的微服务架构基于 gRPC 协议开发,并提供良好的多语言支持。此外,在数据服务领域,gPRC 也成为一个优秀的选择,大家可以参考 Alluxio的文章:
https://www.alluxio.io/blog/moving-from-apache-thrift-to-grpc-a-perspective-from-alluxio/
此外在 API 领域 Swagger (OpenAPI规范),GraphQL 都是大家值得关注的开放标准。大家根据自己的业务诉求灵活选用,本文不再赘述。
Event Driven Architecture 的崛起
谈事件驱动架构 (EDA - Event Driven Architecture),我们首先来解释一下什么是事件。事件是指对已经发生的事情、状态变化等的记录。它们是不可变的(无法更改或删除),并且按其创建顺序排序。相关各方可以通过订阅已发布的事件来获取有关这些状态变化的通知,然后使用所选择的业务逻辑根据这些信息采取操作。
事件驱动架构是一种构建松耦合的微服务系统的架构方式,微服务之间通过异步事件通信来进行交互。
事件驱动架构实现了事件的生产者和消费者的彻底解耦。生产者无需关注事件如何被消费,同时消费者无需关注事件的生产方式;我们可以动态添加更多消费者而不影响生产者,可以增加消息中间件对事件进行动态路由和转换。这还意味着事件的生产者和消费者没有时序上的依赖,即使由于应用宕机无法及时处理消息,在重新恢复后,程序可以继续从消息队列中获取这些事件继续执行。这样的松耦合架构,为软件架构提供更高的敏捷性、灵活性和健壮性。
事件驱动架构的另一个重要优点是提升了系统的可伸缩性。事件生产者在等待事件消费时不会被阻塞,并且可以采用 Pub/Sub 方式,让多个消费者并行处理事件。
事件驱动架构还可以完美地与 Function as a Service (FaaS) 相整合。事件触发函数执行业务逻辑,在函数中也可以编写集成多个服务的“胶水代码”,简单、高效地构建事件驱动架构的应用。
分布式的松耦合架构大大增加了应用基础设施的复杂性。基于云的部署交付方式和云服务(消息队列、函数计算服务等)可以使得该架构的稳定性,性能和成本效益进一步提高。
与传统同步处理方式相比,异步事件处理存在与事件排序、幂等性、回调和异常处理相关的要求,整体设计难度更大一些。
在大多数情况下,由于缺乏跨多个系统的分布式事务支持,维护数据一致性是非常具有挑战性的。开发者可能需要权衡可用性和一致性之间的关系。比如通过Event Sourcing(事件溯源)实现最终一致性,详情:
https://martinfowler.com/eaaDev/EventSourcing.html
互操作性。在现实世界中,事件无处不在,然而不同生产者对事件的描述却不尽相同。开发者希望无论事件是从哪里发出,都能够以一致的方式构建事件驱动的应用程序。CloudEvents(https://cloudevents.io/) 是一种以通用、一致的方式描述事件数据的规范,由 CNCF Severless 工作组提出,提升了事件驱动应用的可移植性。目前,阿里云 EventBridge、Azure Event Grid 等事件处理中间件,以及 Knative Eventing ,阿里云函数计算等 FaaS 技术已经提供了对 CloudEnvents 的支持。
由于 EDA 自身架构的优点,在互联网应用架构,业务数据化和智能化、IoT等场景有非常广阔的前景。关于 EDA 的架构讨论,不在此继续展开。
面向交付的应用架构
在云原生软件架构中,我们在设计阶段不只是关注软件如何被构建,也需要以终为始。关注如何合理设计和实现软件,才可以被更好地交付和运维。
应用和运行环境解耦
在 12 要素应用中,应用和运行环境解耦就已经被提出。而 Docker 容器的出现则进一步加强了这个理念。容器是一种轻量化的应用虚拟化技术,容器之间共享操作系统内核,支持秒级启动,Docker 容器镜像是一个自包含的应用打包格式,将应用和其依赖(如系统库、配置文件)等打包在一起,在不同环境保持部署一致性。
容器可以作为 Immutable Infrastructure (不可变基础设施)的基础,提升应用交付的稳定性
。不可变基础设施是由 Chad Fowler 于 2013 年提出的构想:在这种模式中,任何基础设施的实例(包括服务器、容器等各种软硬件)一旦创建之后便成为一种只读状态,不可对其进行任何更改。如果需要修改或升级某些实例,就是创建一批新的实例进行替换。这种模式的可以减少了配置管理工作的负担,保障系统配置变更和升级可以可靠地重复执行,避免令人头疼的配置漂移问题;易于解决部署环境间的差异,让持续集成与持续部署过程变得更流畅;支持更好的版本管理,在部署出错时可进行快速回滚,
Kubernetes 作为容器的分布式编排调度系统,进一步提升了容器应用的可移植性。
K8s通过一系列抽象如 Loadbalance Service, Ingress, CNI, CSI,帮助业务应用可以屏蔽底层基础设施的实现差异,灵活迁移。通过这样的能力,我们可以实现工作负载在数据中心、边缘计算和云环境的动态迁移。
在应用架构中,我们需要避免将静态环境信息,比如IP,mac地址等与应用逻辑耦合。在微服务架构中,可以利用 Zookeeper/Nacos 等实现服务的注册发现;在 Kubernetes 中,我们可以通过 Service,Service Mesh 减少对服务端点IP的依赖。此外,对应用状态的持久化也尽可能通过分布式存储或者云服务等实现,这样可以大大提升应用架构可伸缩性和自愈能力。
自包含可观测性
分布式系统所面对的最大挑战之一就是可观测性。可观测性可以帮助我们解系统当前的状态,并作为应用自愈,弹性伸缩和智能运维的基础。
在云原生架构中,微服务应用是自包含的,应该自己具备可观测性,可以方便地被系统进行管理和探查。首先是,应用应该具备自身健康状态的可视化能力。
在 Kubernetes 中,业务应用可以提供一个 liveness 探针,可以通过 TCP、HTTP 或者命令行方式对应用就绪进行检测。对于 HTTP 类型探针,Kubernetes 会定时访问该地址,如果该地址的返回码不在 200 到 400 之间,则认为该容器不健康,会杀死该容器重建新的容器;
对于启动缓慢的应用,为了避免在应用启动完成之前将流量导入。Kubernetes 支持业务容器提供一个 readiness 探针,对于 HTTP 类型探针,Kubernetes 会时访问该地址,如果该地址的返回码不在 200 到 400 之间,则认为该容器无法对外提供服务,不会把请求调度到该容器;
同时在新的微服务架构中已经内置了可观测探针,比如在 SpringBoot 的 2.3 发布了两个新的 actuator 地址,/actuator/health/liveness 和 /actuator/health/readiness ,前者用作存活探针,后者用作就绪探针。业务应用可以通过Spring系统事件机制来读取、订阅、修改 Liveness State 和 Readiness State ,这样可以让 Kubernetes 平台可以做更加准确的自愈和流量管理。
https://spring.io/blog/2020/03/25/liveness-and-readiness-probes-with-spring-boot
此外,应用可观测性包含三个关键能力:日志、监控与链路追踪。
Logging – 日志(事件流):用于记录离散的事件,包含程序执行到某一点或某一阶段的详细信息。不但包括应用、 OS 执行过程的日志,还应包含运维过程中的日志信息,如操作审计等。
Metrics – 监控指标:通常是固定类型的时序数据,包括 Counter、Gauge、Histogram 等,是可聚合的数据。系统的监控能力是多层次的,既包含计算、存储,网络等基础设施服务层次的监控指标,也应该包含业务应用的性能监控和业务指标监控。
Tracing – 链路追踪 - 记录单个请求的完整处理流程,可以为分布式应用的开发者提供了完整的调用链路还原、调用请求量统计、应用依赖分析等能力,能够帮助开发者快速分析和诊断分布式应用架构下的性能和稳定性瓶颈。
在分布式系统中,稳定性、性能、安全等问题可能发生在任何地方,需要全链路可观测性能力保障,需要覆盖基础设施层、 PaaS 层,应用等不同层次,并且可以在不同系统间实现可观测性数据的关联、聚合、查询和分析。
软件架构的可观测领域具备广阔的前景,也涌现出众多的技术创新。2020 年 9 月 CNCF 发布了云原生可观测性的技术雷达:
https://www.cncf.io/blog/2020/09/11/cncf-end-user-technology-radar-observability-september-2020/
其中,Prometheus 已成为企业首选的云原生应用程序的开源监控工具之一。Prometheus 培养了一个活跃的开发者和用户社区。在 Spring Boot 应用架构中,通过引入 micrometer-registry-prometheus 的依赖,既可以让应用的监控指标被 Prometheus 服务所采集。更多信息可以参考文档:
https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html
在分布式追踪领域,OpenTracing 是 CNCF 下属的开源项目。它是一个技术中立的分布式追踪的规范,提供统一接口,可方便开发者在自己的服务中集成一种或多种分布式追踪的实现。Jaeger 是Uber 开源的分布式追踪系统,兼容 OpenTracing 标准,已经成功在 CNCF 毕业。此外OpenTelemetry是一个潜在的标准,它试图在融合 OpenTracing 和 OpenCensus 这两个项目,形成统一的技术标准。
对于很多遗留的业务系统,现有应用并不具备完备的可观测性能力。新兴的服务网格技术可以成为提升系统可观测性的新方式。通过数据平面代理的请求拦截,网格可以获取服务间调用的性能指标。此外,在服务调用方应用中只需加入需要转发的消息 header,在服务网格上即可获得完整的链路追踪信息。这样的方式极大简化了可观测性能力的建设,可以让现有的应用低成本融入云原生可观测性体系中。
阿里云提供了丰富的可观测性能力。
XTrace分布式追踪提供了对 OpenTracing/OpenTelemetry 标准的支持。ARMS 提供了托管 Prometheus 服务,可以让开发者无需关注系统的高可用和容量挑战。可观测性是 AIOps 的基础,在未来企业IT应用架构中将扮演更加重要的角色。
面向失败的设计 - Design For Failure
根据”墨菲定律“ — “Anything that can go wrong will go wrong”。分布式系统可能受到硬件、软件等因素、或者内部和外部的人为破坏。
云计算比自建数据中心提供了更高SLA、更加安全的基础设施,但是我们在应用架构设计时依然要时刻关注系统的可用性,关注潜在的”黑天鹅“风险。
系统化的稳定性需要在软件架构,运维体系和组织保障等方面全局考虑。在架构层面,阿里经济体有着非常丰富的经验,比如防御式设计、限流、降级、故障隔离等,而且也向社区贡献了Sentinel、ChaosBlade等优秀的开源项目。
本文,我会谈谈几个在云原生时代可以进一步思考的地方。我的总结是 “Failures can and will happen, anytime, anywhere. Fail fast, fail small, fail often and recover quickly.”
首先是“Failures can and will happen”,我们需要提升服务器的可替换性。
在业界有一个非常流行的隐喻:“Pets vs. Cattle”,宠物和家畜。我们面对一个架构选择:对于应用所在服务器我们是需要精心伺候,防止系统宕机,出现问题后不惜一切代价抢救 (Pet);还是倾向于出现问题后,可以通过简单抛弃和替代进行恢复(Cattle)。云原生架构的建议是:允许失败发生,确保每个服务器,每个组件都能够在不影响系统的情况下发生故障并且具备自愈和可替代能力。这个设计原则的基础是应用配置和持久化状态与具体运行环境的解耦。Kubernetes 的自动化运维体系让服务器的可替换性变得更加简单。
此外是 “Fail fast, fail small, recover quickly” 。
立即失效(Fail fast)是一个非常反直觉的设计原则,它背后的哲学是既然故障无法避免,问题越及早暴露、应用越容易恢复,进入生产环境的问题就越少。采用了 Fail-fast 策略以后,我们的关注点将从如何穷尽系统中的问题转移到如何快速地发现和优雅处理失败。只要跑的够快,故障就追不上我。:-) 在研发流程上,通过集成测试尽可能在早期发现应用存在的问题。在应用级别,可以采用断路器(Circuit Breaker)等模式防止一个依赖服务的局部故障引起全局问题;此外通过 K8s 的健康监测、可观测性可以实现对应用故障的探知,通过服务网格的断路器功能,可以将故障发现、流量切换和快速自愈这些能力外置到应用实现之外,由系统能力保障。Fail small的本质在于控制故障的影响范围——爆炸半径。这个原则在架构设计和服务设计上都需要我们持续关注。
最后是“Fail often”,混沌工程是一种在生产环境周期性引入故障变量,验证系统对非预期故障防御的有效性的思想。
Netflix 引入混沌工程概念解决微服务架构的稳定性挑战,也得到了众多互联网公司的广泛应用。在云原生时代又有了更多新的手段,Kubernetes 让我们可以轻松注入故障,杀死pod,模拟应用失效和自愈过程。利用服务网格我们可以对服务间流量进行更加复杂的故障注入,比如 Istio 可以模拟缓慢响应、服务调用失败等故障场景,帮助我们验证服务间的耦合性,提升系统的稳定性。 更多关于交付和运维架构的更多稳定性思考,我们会在下一篇文章中和大家分享。
应用基础设施能力下沉
云原生软件架构的重要目标让开发者关注业务逻辑,让平台去承载系统复杂性。云原生计算重新定义了应用与应用基础设施的边界,进一步提升了开发效率,降低了分布式应用开发的复杂性。
服务治理能力与业务逻辑解耦
在微服务时代,以 Spring Cloud 与 Apache Dubbo 为代表的应用框架取得了巨大的成功,它们通过代码库方式提供了服务通信、服务发现和服务治理能力(流量转移、熔断、限流、全链路追踪等)。这些代码库被构建在应用程序本身中,随着应用一起发布和维护。这样的架构存在一些无法回避的挑战。
https://philcalcado.com/2017/08/03/pattern_service_mesh.html
为了解决上述挑战,社区提出了 Service Mesh(服务网格)架构。它将业务逻辑与服务治理能力解耦。下沉到基础设施,在服务的消费者和提供者两侧以独立进程的方式部署。这样既达到了去中心化的目的,保障了系统的可伸缩性;也实现了服务治理和业务逻辑的解耦,二者可以独立演进不相互干扰,提升了整体架构演进的灵活性;同时服务网格架构减少了对业务逻辑的侵入性,降低了多语言支持的复杂性。
Google、IBM、Lyft 主导发起的 Istio 项目就是服务网格架构的一个典型的实现,也成为了新的现象级“网红”项目。
上图是Istio的架构,逻辑上分为数据平面和控制平面。数据平面负责服务之间的数据通信。应用和以 sidecar 方式部署的智能代理 Envoy 成对出现。其中由 Envoy 负责截获和转发应用网络流量,收集遥测数据并且执行服务治理策略。在最新的架构中, istiod 作为控制平面中负责配置的管理、下发、证书管理等。Istio 提供了一系列通用服务治理能力,比如:服务发现和负载均衡,渐进式交付(灰度发布),混沌注入与分析,全链路追踪,零信任网络安全等。可以供上层业务系统将其编排到自己的IT架构和发布系统之中。
服务网格在架构上实现了数据平面与控制平面的分离,这是一个非常优雅的架构选择。
企业客户对数据平面有着多样化的需求,比如支持等多样化协议(如Dubbo),需要定制化的安全策略和可观测性接入等。服务控制平面的能力也是快速变化的,比如从基础的服务治理,到可观测性,到安全体系,稳定性保障等等。但是控制平面与数据平面之间的API是相对稳定的。
CNCF 建立了通用数据平面 API 工作组(Universal Data Plane API Working Group / UDPA-WG),以制定数据平面的标准 API。通用数据平面 API(UDPA)的目标是:为 L4/L7 数据平面配置提供实现无关的标准化 API,类似于 OpenFlow 在 SDN 中对 L2/L3/L4 所扮演的角色。UDPA API 涵盖服务发现、负载均衡、路由发现、监听器配置、安全发现、负载报告、运行状况检查委托等。
UDPA API 基于现有的 Envoy xDS API 逐步演进,目前除支持 Envoy 之外,将支持客户端负载均衡实现 (比如 gRPC-LB),更多数据平面代理,硬件负载均衡和移动客户端等等。
我们知道 Service Mesh 不是银弹,其架构选择是通过增加一个服务代理来换取架构的灵活性和系统的可演化性,但是也增加了部署复杂性(sidecar管理)和性能损失(增加两跳)。UDPA 的标准化和发展将给服务网格架构带来的新一次变化。
gRPC 在最新版本中提供了对UDPA负载均衡的初步支持:
https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md
”proxyless “服务网格概念浮出水面,一个概念示意图如下:
gRPC 应用直接从控制平面获取服务治理的策略, gPRC 应用之间直接通信无需额外代理。这个可以看到开放的服务网格技术的雄心,进化成为一套跨语言的服务治理框架,可以兼顾标准化、灵活性与运行效率。Google 的托管服务网格产品已经率先提供了对 ”proxyless“ gRPC 应用的支持。
新一代分布式应用运行时
对于分布式应用,Bilgin Ibryam 在Multi-Runtime Microservices Architecture :
https://www.infoq.com/articles/multi-runtime-microservice-architecture/,
文中 分
析并总结了典型的四大类需求:
熟悉传统企业架构的同学可能发现,传统的 Java EE (现在改名为 Jakarta EE )应用服务器的目标也是解决类似的问题。一个典型 Java EE 应用服务器的架构如下图所示:应用生命周期由各种应用容器管理,如 Web 容器,EJB 容器等。应用的安全管理、事务管理、连接池管理都是交给应用服务器完成。应用可以通过 JDBC 、JMS 等标准 API 接口访问外部的企业中间件,如数据库、消息队列等。
不同的外部中间件通过 Java Connector Architecture 规范实现与应用服务器的插拔。应用通过 JNDI 在运行时实现与具体资源的动态绑定。Java EE 将系统的 cross-cutting concern下沉到应用服务器来解决,让开发者只关注应用的业务逻辑,开发效率有了较好的提升;同时减轻应用对环境和中间件实现的依赖,比如可以在开发环境中用 ActiveMQ ,在生产环境中使用 IBM MQ 替换,而无需修改应用逻辑。
在架构上,Java EE 是一个大的单体应用平台,拖慢了自身架构迭代的速度,跟不上时代的变化。由于Java EE过于复杂、沉重,在微服务兴起之后已经被大多数开发者所遗忘。
Dapr(https://dapr.io/)是微软给出的答案。Dapr 是一个事件驱动的,可移植的,构建微服务应用的运行时环境。支持应用在云或边缘部署,支持语言与框架的多样性。Dapr利用 Sidecar 的模式,把应用逻辑中的一些横切关注点需求(Cross-cutting)分离和抽象出来,从而达到应用与运行环境的解耦以及对外部依赖(包括服务之间)的解耦。
其上是 Dapr 运行时和“building block” (构件)。Dapr 构件解耦了外部服务和服务的消费者,可以按需加载。构件以统一的 HTTP/gPRC API 为应用层提供服务访问。我们可以将外部服务从 Amazon DyanamoDB 切换为 Azure ComosDB ,上层应用无需修改任何代码。Dapr 运行时作为一个独立的 sidecar 进程,独立于应用逻辑。
应用通过轻量化的 SDK 来简化对构件 API 的调用,基于 gRPC/HTTP 开放协议可以轻松支持多语言。
尽管 Dapr 和 Service Mesh 在架构上有些类似,服务治理功能有所重叠,但两者在本质上却大有不同。服务网格对应用是透明的基础设施;而 Dapr 为状态管理,服务调用和故障处理,资源绑定,发布/订阅,分布式跟踪等提供抽象,需要应用程序通过 SDK/HTTP/gRPC 显式调用 Dapr 能力,它是面向开发人员的开发框架。
Dapr 还非常年轻,还在快速迭代中,距离被广大开发者和三方厂商所支持还有很长的路要走。但是 Dapr 给我们揭示出一个新的方向:通过关注点分离,让开发者只需关注业务逻辑自身,而分布式架构的系统关注下沉到基础设施中实现;让业务逻辑与外部服务解耦,避免厂商绑定;同时应用和应用运行时是两个独立的进程,通过标准化API进行交互,生命周期解耦,便于升级和迭代。
Serverless 的机遇与挑战
在上一篇文章中,我已经对 Serverless 应用基础设施,如函数即服务(FaaS), Serverless 容器做了介绍。本文谈谈函数即服务 FaaS 应用在架构方面的一些思考。
FaaS 的核心思维是:开发者不必关心基础设施运维、容量规划或者扩容缩容,只需为使用的云资源和服务付费既可。这个思考的背后是:让开发者避免投入基础设施的运维,尽可能复用现有的云服务能力,让开发时间重新分配到对用户有更有直接影响和价值的事情上,比如健壮的业务逻辑、能吸引用户的界面及快速响应、可靠的 API 上。
在软件架构层面中, FaaS 将复杂的业务逻辑拆解成一系列细粒度的函数,并通过事件驱动的方式触发调用。函数之间是松耦合的,可以通过如下两种典型的模式进行协同、组合。
Workflow Orchestration 工作流编排:以阿里云 Serverless 工作流为例,可以通过一个声明式的业务流程来编排任务。这种方式简化了开发和运行业务流程所需要的任务协调、状态管理以及错误处理等繁琐工作,让开发者聚焦于业务逻辑开发。
Event Choreography 事件协调:函数服务之间通过事件交换消息,由事件总线等消息中间件来进行事件的转发,并触发其他函数执行。下面是一个示例应用场景,通过 EventBridge,将订单,用户通知、商家通知、接单、结单等基于函数实现的业务逻辑串联在一起。这种方式更加灵活,系统的健壮性也更好。但是缺点是缺乏显式的建模,开发和维护相对较复杂。
Serverless 具备很多优势, 比如:降低运维成本,提升系统安全性,提升研发效率,加速业务交付等等。然而 Serverless 还有一些不能回避的问题需要我们来做判断:
成本管理:
对于“Pay as you go”的收费模式的一个弱点是无法准确预测具体会产生多少费用,这于许多组织预算管理的方式不同。
厂商锁定:
即使 Serverless 应用基于开放的语言和框架,但是多数Serverless应用还依赖一些非标准化的 BaaS(Backend as a Service)服务,如对象储存,Key- Value 数据库,认证,日志,监控等。
调试和监控:
与传统应用开发相比, Serverless 应用的调试与监控工具能力还不完善。良好的可观测性是将 serverless 计算的重要助力。
架构复杂性:
Serverless 开发者无需关注底层基础设施的复杂性,但是应用架构的复杂性需要格外关注。事件驱动架构和细粒度函数微服务,与传统开发模式非常不同。大家需要根据业务需求和自己的技术能力,在合适的场景应用,然后逐渐扩大应用范围。
关于典型的 Serverless 应用架构,大家可以参考 What a typical 100% Serverless Architecture looks like in AWS ! :
https://medium.com/serverless-transformation/what-a-typical-100-serverless-architecture-looks-like-in-aws-40f252cd0ecb
Cloud Programming Simplified: A Berkeley View on Serverless Computing:
https://www2.eecs.berkeley.edu/Pubs/TechRpts/2019/EECS-2019-3.pdf
也是深入了解 Serverless 计算的一个好的参考。
应用运行时的敏捷进化
更快、更轻、更敏捷的应用运行时技术是云原生计算的持续追求。
正因为此,Golang、Node.js、Python 等语言开发者在持续攀升,有几个值得大家关注的技术:
在 Java 领域,GraalVM (https://www.graalvm.org/)已经逐渐成熟。
它是基于HotSpot上增强的一个跨语言的全栈虚拟机,支持众多语言的运行平台(包括Java、Scala、Groovy、Kotlin、JavaScript、Ruby、Python、C、C++等)。
GraalVM允许您将程序提前编译为本地可执行文件。
与经典Java VM相比,生成的程序具有更快的启动时间和更低的运行时内存开销。
Quarkus(https://quarkus.io/)/Micronaut(https://micronaut.io/) 等作为云原生定制的新一代Java框架,可以实现惊艳的启动时间和资源开销。
更多分析可以参考
。
WebAssembly 则是另外一个令人激动的技术。WebAssembly 作为一个面向现代 CPU 体系架构设计的,安全的、可移植、高效率的虚拟机沙箱,可以在任何地方(服务器、浏览器、IoT等等)、任何平台(不同操作系统,不同CPU体系架构下)安全运行应用。WebAssembly System Interface(WASI)是来标准化 WebAssembly 应用与系统资源的交互抽象,比如文件系统访问,内存管理,网络连接等,提供类似 POSIX 这样的标准 API 。
平台开发商可以针对具体的操作系统和运行环境提供 WASI 接口不同的实现,可以在不同设备和操作系统上运行跨平台的 WebAssembly 应用。这可以让应用执行与具体平台环境实现解耦,使得应用“Build Once, Run Anywhere”的理想逐渐形成现实。虽然目前 WebAssembly 已经超越了浏览器的领域,但是其发展还在非常初期,期待社区共同推动。有兴趣的同学可以看看 WebAssembly 与 Kubernetes 双剑合璧:
https://www.infoq.cn/article/rEcOgQiurqaTyY7dJ6hA
趋势总结
云原生软件架构还在快速发展中,涉及的内容也非常广泛。上述内容更多是个人总结、理解和判断,期待与大家的交流和深入探讨。
更多参考:
https://martinfowler.com/architecture/
https://www.ibm.com/cloud/blog/7-missing-factors-from-12-factor-applications
https://www.infoq.com/articles/microservices-design-ideals/
https://www.infoq.com/articles/architecture-trends-2020/
https://theburningmonk.com/2020/08/choreography-vs-orchestration-in-the-land-of-serverless/