Dapr 知多少 | 分布式应用运行时
Intro
Dapr 官方团队已于最近(2021.2.17)正式发布Dapr v1.0,Dapr 已正式生产可用,可以部署到自托管环境或 Kubernetes 集群。对于绝大多数开发者来说,想必对 Dapr 只是有所耳闻,而具体是什么(What),可以解决什么样的问题(Why&How),有怎样的应用场景(Where),并不知悉。本文就尝试简要梳理下 Dapr,并尝试回答以上问题。
What's Dapr
Distributed Application Runtime. An event-driven, portable runtime for building microservices on cloud and edge.
分布式应用运行时。一个事件驱动、可移植的运行时用于在云上和边缘计算上构建微服务。
以上是Dapr官方GitHub仓库上对 Dapr 的简介。文字虽短,口气却很大,因为其除了涵盖了当前所有的技术热点:分布式、云、微服务,还自我标榜为:分布式应用运行时。分布式应用我们或多或少有些了解,运行时也听到不少,比如常见的语言运行时:Java 运行时,.NET 运行时,Go 运行时等等,那运行时又是什么东西?简要来说:运行时是程序运行依赖的执行环境。以.NET 程序运行时 CLR 为例,它为.NET 应用程序提供了一个托管的代码执行环境负责应用程序在整个执行期间的内存管理、线程管理、安全管理、远程管理、即时编译等。
那分布式应用运行时,就是提供分布式应用运行所依赖的的执行环境。那运行分布式应用需要哪些环境依赖呢?回答这个问题,我们要先思考开发分布式应用的挑战是什么?明确了挑战,那就找到了答案。
从单机到分布式,是追求更快和更高的性能,但也带来了更多的不确定性。比如,不确定计算机何时异常,不确定磁盘何时损坏,不确定网络通信的延迟,也不确定消息是否被正常消费。这些不确定性构成了分布式应用的挑战,简而言之:
异构的机器与网络:稳定性问题
普遍的节点故障:可靠性问题
不可靠的网络:一致性问题
面对这些挑战,业界提出了诸多的分布式理论、协议,如 CAP 定理,BASE 理论,一致性协议 2PC/3PC/ZAB,来保证系统的正常运行。虽然问题貌似是有了解决方案,但是应用的复杂度升高了。应用除了需要实现业务需求,还要兼顾非业务需求,集成诸如服务发现、负载均衡、失效转移、动态扩容、数据分片、调用链路监控等分布式系统的核心功能,对应用有很强的侵入性,这就是以 Spring Cloud 为代表的微服务框架的常见做法。
那如何解决侵入性的问题呢?这个问题随着容器编排技术的成熟有了新的解法。Kubernetes 可以不侵入应用层,在容器层解决问题,比如 K8S Service 就具有服务发现、负载均衡的能力,HPA 具有动态扩容的能力。随着 K8S 的快速发展,云原生的概念,也就越来越深入人心,那如何利用好 K8S 提供的基座能力,将更多的分布式能力下沉,让应用开发回归业务呢?其中 Service Mesh 提出的 Sidecar 模式,就很好的解决了微服务架构中网络通信的问题。Sidecar 主要就是用来处理诸如服务发现、负载均衡、请求熔断等一系列非业务需求,应用在部署时动态插入 Sidecar,服务间的通信通过 Sidecar 进行代理,以完成对服务间网络通信的接管。
到这里,微服务开发在 Service Mesh 的帮助下,已经渐渐回归业务本身,让更多的开发者看到了一丝曙光。It's enough? 来看下 Bilgin Ibryam 在Multi-Runtime Microservices Architecture文章中提及的分布式应用的四大需求:
从上图可以看出,除了网络(Networking)外,生命周期(Lifecycle)、状态(State)、捆绑(Binding)也是分布式应用要解决的问题之一。网络问题可以借由 Service Mesh 比如 Istio 予以解决。那其他三个该如何解决呢?又要应用自行开发集成吗?显然不符合应用回归业务本身的诉求。这时,Dapr 登场了,Dapr 提出的分布式应用运行时就是实现了以上四个需求并将其下沉作为分布式应用的运行环境。
简而言之:Dapr 将分布式能力进行封装下沉作为运行时以简化分布式应用开发的技术复杂度。
How Dapr Works
那 Dapr 如何简化分布式应用的开发呢?下面我们来看一看 Dapr 的主要特性。
一图胜千言:Dapr 通过以 HTTP/gRPC API 这种与语言无关的方式暴露封装的分布式能力供应用调用,从而支持使用任意语言或框架进行开发集成。目前官方已经提供了 Go,Node,Python,.NET,Java, C++,PHP,Rust,Javascript 的 Sdk,简化 Dapr 的集成。
其中 Dapr 的核心构建块(Building Block)就是用来提供各种不同的分布式能力,我们来分别看一看。
Service-to-service invocation
提到跨服务方法调用,这个大家肯定会想,这简单啊,不就是服务暴露 API 就好了嘛。是,但不完全是。比如 nodeapp 暴露了一个 API:http://10.0.0.2:8000/neworder
,按照传统的方式,直接 HTTP POST 这个 API 访问就得了,但在 Dapr 中,其提供了服务间方法调用的接口规范,需要按照
`POST/GET/PUT/DELETE http://localhost:<daprPort>/v1.0/invoke/<appId>/method/<method-name>
的格式进行访问。那假设pythonapp需要访问nodeapp的方法,就需要POST一个请求到
http://localhost:3500/v1.0/invoke/nodeapp/method/neworder`。你可能会想为何多此一举呢?此举的意义何在呢?目的很简单,就是为了实现对服务间网络通信的控制以完成诸如服务发现、流量控制、重试熔断、安全访问等,而这相关的网络控制功能就是集成在 Dapr 的 Sidecar 中,以对应用透明的方式集成进来的。整体的服务调用流程如下图所示:
PS:如果对 Istio 熟悉的同学需要注意,二者虽然都是通过 Sidecar 的模式进行网络控制,但二者是有有区别的。Dapr 是以 API 的方式,而 Istio 是以代理的方式(不改变 HTTP 请求 URI)。
State management
在进行微服务开发时,绕不开的话题就是服务间的状态共享、并发一致性问题。对于状态共享,你可能会说,各个服务连接到同一个 Redis 实例就 OK 了。是,但不得不考虑潜在的更新冲突的问题。Dapr 以更友好的 HTTP API 的方式进行状态的存储和读取,同时支持通过 ETags 进行并发控制,并支持通过选项设置并发和一致性行为。
存储:
POST http://localhost:<daprPort>/v1.0/state/<storename>
读取:
GET http://localhost:<daprPort>/v1.0/state/<storename>/<key>
删除:
DELETE http://localhost:<daprPort>/v1.0/state/<storename>/<key>
以下是保存状态的举例:
concurrency
用于指定并发选项:first-write-wins/last-write-wins(以第一次写入为准/以最后一次写入为准),默认以最后一次写入为准。consistency
用于指定一致性选项:strong/eventual(强一致性/最终一致性),默认为最终一致性。
目前支持使用 Azure CosmosDB、 Azure SQL Server、 PostgreSQL,、AWS DynamoDB、Redis 作为状态存储介质。
Publish and subscribe
发布订阅模式,老生常谈了,主要是用于微服务间基于消息进行相互通信。你可能也会说,这也要拿出来说,我搞个 RabbitMQ/RocketMQ 就是了。是的,但我还是要说,Dapr 提供了一致性的消息发布、订阅 API,而无需关注具体使用的是何种 Message Broker,从而和底层基础设施解耦。
发布:
POST http://localhost:<daprPort>/v1.0/publish/<pubsubname>/<topic>[?<metadata>]
获取可订阅主题:
GET http://localhost:<appPort>/dapr/subscribe
订阅:
POST http://localhost:<appPort>/<path>
Resource bindings and triggers
Dapr 的 Bindings 与 Azure Functions 很类似,其是建立在事件驱动架构的基础之上的。通过建立触发器与资源的绑定,可以从任何外部源(例如数据库,队列,文件系统等)接收和发送事件,而无需借助消息队列,即可实现灵活的业务场景。Dapr 的 Bindings 分为两种:
Input Bindings(输入绑定):当外部资源的事件发生时,借助输入绑定,你的应用即可通过特定的 API:
POST http://localhost:<appPort>/<name>
收到外部资源的事件,用于处理特定逻辑。Output Bindings(输出绑定):输出绑定允许你调用外部资源。比如,在订单处理场景中,在订单创建成功后,可以将订单信息通过 Dapr 的绑定 API:
POST/PUT http://localhost:<daprPort>/v1.0/bindings/<name>
输出到 Kafka 特定队列上。
Actors
Dapr 中的 Actor 模型,和 Orleans 的 Virtual Actor 一脉相传,之前写过一篇文章Orleans 知多少 | .NET Core 分布式框架介绍过。简单来讲:Actor 模型 = 状态 + 行为 + 消息。一个应用/服务由多个 Actor 组成,每个 Actor 都是一个独立的运行单元,拥有隔离的运行空间,在隔离的空间内,其有独立的状态和行为,不被外界干预,Actor 之间通过消息进行交互,而同一时刻,每个 Actor 只能被单个线程执行,这样既有效避免了数据共享和并发问题,又确保了应用的伸缩性。
Actor 模型大大简化了并发编程的复杂度,Dapr 在 Actor 运行时中提供了许多功能,包括并发控制,状态管理,生命周期管理如 Actor 的激活/停用以及用于唤醒 Actor 的 Timer(计时器)和 Reminder(提醒)。这些功能同样也是通过API的方式予以提供。
调用 Actor 方法:
POST/GET/PUT/DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/method/<method>
创建 Timer:
POST/PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/timers/<name>
创建 Reminder:
POST/PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name>
Observability
Dapr 记录指标,日志,链路以调试和监视 Dapr 和用户应用的运行状况。 Dapr 支持分布式跟踪,其使用 W3C 跟踪上下文标准和开放式遥测技术,可以轻松地诊断在生产环境中服务间的网络调用,并发送到不同的监视工具,如 Prometheus。
Secrets
Dapr 提供了 Secret 管理,不过不同于 K8S 中的 Secret,其支持与公有云和本地的 Secret 存储集成,以供应用检索使用。
What Can We Do With Dapr
了解了 Dapr 是什么,以及其提供的特性,那 Dapr 的应用场景就一目了然了。也就是官网首页的 Slogan:Simplify cloud-native application development--Focus on your application’s core logic and keep your code simple and portable。
简化云原生应用的开发,确保应用专注于业务,并保证代码简单可移植。
因此,在考虑云原生应用开发的技术选型时,尽情尝试吧,目前在国内阿里云也已采用。
Last
在云原生如火如荼发展之际,Dapr V1.0 的正式发布,为开发者指明了云原生时代微服务的开发方向。相信 Dapr 在未来的微服务架构选型中必将占有一席之地!
参考:
版权声明: 本文为 InfoQ 作者【圣杰】的原创文章。
原文链接:【http://xie.infoq.cn/article/cf5fb1eb61b6679274ba5f3be】。文章转载请联系作者。
评论