多年来,许多分布式编程模型和框架被引入,如通用对象请求代理架构(CORBA)、微软分布式组件对象模型(DCOM)、COM+、Java 远程方法调用(RMI)、Akka、微软 Microsoft Service Fabric 等。本书介绍了我们的贡献 —— 分布式应用程序运行时(Dapr),到目前为止,它已经得到了社区的好评。Dapr 是一个新的分布式运行时间,正在积极开发中。获得最新信息的最好方法是访问 Dapr 的 官方网站。本书没有关注 API 的细节,而是旨在提供 Dapr 如何设计以及我们认为它在未来如何发展的背景信息。我们希望这本书能够帮助你理解 Dapr 的架构和设计理念,这样你就可以在自己的应用程序中更好地应用 Dapr,并为 Dapr 社区做出巨大贡献。
什么是 Dapr?
Dapr 是一个事件驱动的、可移植的运行时,用于构建云和边缘的微服务。它使用配套的容器或进程来提供分布式应用所需的构建模块,包括状态管理、服务发现、可靠的消息传递、可观察性等,这些将在后面详细讨论。Dapr 的伙伴进程,或称边车(Sidecar),通过 HTTP/gRPC 协议暴露了一个标准的 API 表面。这使得 Dapr 可以支持任何支持 HTTP 或 gRPC 的编程语言,而不需要在应用程序代码中包含任何 SDK 的库。Dapr 侧设备是相互连接的,为分布式应用形成一个孤立的、分布式的运行时间,如 图 I-1 所示。
当我们向一些潜在的客户介绍边车架构时,他们中的一些人立即对这一想法产生了兴趣。尽管我们的直觉告诉我们,我们已经找到了一些东西,但我们花了相当多的时间来讨论为什么 Dapr 是必要的,以及 Dapr 将如何改变云原生编程模型。核心问题是这样的:为什么我们需要另一种分布式应用程序的编程模型?或者换句话说,是什么让 Dapr 变得独特和有用?让我们来看看它提供了什么。
图 I-1. Dapr 边车通过标准的 HTTP/gRPC 协议提供分布式构件,与应用程序代码一起工作
一种异构环境的编程模型
根据定义,一个编程模型是一组意见,描述了模型的设计者认为某种类型的程序应该如何编写。一个编程模型的意见越多,它为开发者提供的指导就越强。然而,它也对特定的框架或实现施加了更强的锁定,这可能是有问题的。现代微服务应用程序通常包括由不同团队或外部供应商编写的服务,而且现代计算人员的流动性也是前所未有的。让每个人都接受一种特定的编程语言或框架往往是困难的。
我们承认,在当今的软件世界中,我们需要拥抱多样性,并为具有不同技能、工作风格和偏好的开发人员寻找和谐共处的方法。我们希望开发者能够表达他们的业务逻辑,而不必担心被厂商锁定。在为所有开发者社区寻求一个坚实的共同点时,我们观察到,微服务在一个很高的层次上是一个处理单元,它暴露了一些访问端点,而这些端点又经常与 HTTP 对话。因此,HTTP 似乎是服务开发者之间选择的一个合理的共同标准。例如,为了用 Dapr 保存状态,所有的应用程序代码需要做的就是发送一个 POST 请求,将一个键/值集合作为 JSON 文档发送到 Dapr Sidecar 提供的状态 API。向 REST API 端点发送 POST 请求是一个非常普遍的做法,在我们对不同技术水平的开发人员的多次访谈中,我们从未遇到过任何开发人员认为这很麻烦。
在 HTTP 上实现标准化,可以使用不同编程语言编写的服务具有最大的互操作性。在本书中,你会看到 Dapr 是如何实现一些独特的互操作场景的,比如调用一个用与调用者不同的编程语言编写的行为体。
更多帮助,更少意见
许多现有的分布式编程模型试图将开发者限制在非常狭窄的路径上,以保护他们不犯分布式计算的常见错误。但是,让开发者对分布式计算的来龙去脉一无所知是一把双刃剑。一方面,它可以保护他们不犯错误。另一方面,它又使他们无法有效地处理更复杂的场景。我们经常看到的是,开发者试图绕过框架工作的限制,导致了奇怪的、不自然的滥用和反模式。一些框架甚至试图为开发者创造一个无错误的环境,捕获错误并采用重试或恢复逻辑来使开发者无动于衷。这意味着开发者永远无法了解他们的错误;他们不断地重复犯同样的错误,而底层框架却默默地纠正他们,这导致了不必要的开销和额外的延迟。
Dapr 试图提供帮助,但不那么有主见。它为开发者提供了常见的功能,其默认行为对没有经验的开发者来说也是安全的。另一方面,它并不阻止开发者发展他们的分布式编程技能,并根据需要使用高级结构。例如,Dapr 状态管理默认提供乐观的并发性。这允许大多数事务在不干扰彼此的情况下完成。当冲突发生时,Dapr 并不试图向开发者隐藏错误;相反,开发者被期望处理这种错误。幸运的是,处理来自 HTTP 请求的响应也是 Web 服务开发者的一项基本技能。在发生冲突的情况下,会返回一个明确的 409 Conflict 代码。开发者可以选择简单地将响应转发回客户端,这是网络应用中常见的做法(因为我们通常不希望在服务器上做冗长的自动重试)。因此,暴露错误并不一定会增加服务开发者的负担。另一方面,如果开发者决定对错误进行处理,我们欢迎他们这样做。其结果是在框架和开发者之间建立了一种健康的关系 —— 开发者在大多数时候都没有麻烦,他们可以选择在多大程度上参与错误处理。
不要重复造轮子!
许多框架试图提供 “全栈” 解决方案,解决分布式编程的每一个方面。因为它们是完全打包的解决方案,所以它们很少考虑如何与他人集成 —— 要么用自己的方式,要么用高速公路。
Dapr 采取了一种不同的方法。Dapr 的许多组件是可插拔的,包括状态存储和消息传递。这种设计使开发者在选择使用 Dapr 的服务时有很大的灵活性。此外,由于 Dapr 允许动态绑定,开发者或操作者可以选择绑定到最适合当前开发环境的不同服务。例如,当一个应用程序以断开连接的模式部署在边缘时,它可以被绑定到运行在 Docker 容器中的本地 Redis 存储。当同一个应用被部署在云中时,它的状态存储可以被绑定到一个全局复制的数据存储,如 Azure Cosmos DB。
Dapr 将同样的原则应用于可靠的消息传递。Dapr 没有试图实现一个全新的消息传递系统,而是被设计为与成熟的消息总线一起工作。Dapr 在这些消息总线前面提供了一个通用的、基于 HTTP 的门面,它把配置管理从开发者的角度推开。这种设计允许开发人员以最小的努力实现可靠的消息传递,同时在选择、配置和操作消息传递系统方面给予运营团队极大的灵活性。
Dapr 使用其他开源系统作为整体部分,并且它与流行的开源解决方案合作良好。Dapr 运行时建立在成熟的网络框架之上,如 Fast HTTP;Dapr 在 Kubernetes 上运行时将自己注入为 Sidecar 容器;Dapr Sidecar 与 服务网格 Sidecar 并肩工作;Dapr 使用 OpenTelemetry 作为默认的跟踪解决方案;还有很多其他的例子。Dapr 被设计成一个构建模块的中心。它从来不是一个自成一体的框架,从头开始就把所有的东西都装进去。这种设计使 Dapr 能够以极大的灵活性向开发者展示新的能力。它还允许社区为生态系统贡献新的能力,以增强更多的服务开发者。
这种开放的设计也使得 Dapr 核心运行时间非常轻巧。在写这篇文章的时候,它需要大约 40MB 的磁盘空间,使用大约 4MB 的内存。它以 0.1 个 vCPU 核心运行,并为服务调用增加亚毫秒的开销。轻量级的运行时间减少了资源消耗,改善了挎包注入时间。这使得 Dapr 适用于动态扩展、高密度的场景,如物联网和大数据应用。
统一的编程模型
不同类型的服务存在不同的编程模型:无状态服务、有状态服务、函数、行为体、MapReduce 作业等。Dapr 并没有对这些服务类型进行硬性区分。相反,它将所有的服务视为处理单元,接受一些输入并产生一些输出。
:::info Dapr 允许你以一种一致的方式编写所有的服务。你可以在以后重新配置服务,使之表现得不同。 :::
想象一下,编写一个无状态的 Web 服务,然后将其重新配置为具有输入/输出绑定的函数,并将其托管在你选择的无服务器环境中。或者,你可以通过引入一个状态存储,使其成为一个有状态的服务。你还可以进一步使服务具有身份意识,从而使它能够作为一个行为者。这种灵活性在 Dapr 之前是无法想象的。
许多企业应用由不同类型的服务组成 —— 例如,一个无状态的 Web 前端,一个有状态的后端,以及一群被建模并作为行为体行动的工作者。开发人员常常被迫学习所有这些编程模型,或者他们想把所有的服务类型强加到他们最熟悉的单一编程模型中。尽管我们已经看到一些成功的项目使用了 “纯函数” 或 “纯行为体”,但大多数企业应用跨越了多种编程模型、框架,甚至是托管环境。
Dapr 提供了一个统一的编程模型,通过标准化的 HTTP/gRPC 协议提供功能。开发人员在开始设计他们的服务时,不再需要针对特定的服务类型。相反,当他们需要某些能力时,如状态管理或绑定,他们只需在应用程序的边车上调用相应的 API。即使是同一个服务的不同路线也可以承担不同的行为体,呈现不同的特征。例如,服务可以作为行为体的一个实例被操作,也可以作为一个有状态的服务被调用,该服务在所有行为体实例中提供聚合。
统一编程模型有用性的另一个例子是,通过绑定使网络服务被来自流行的云服务的事件所触发。图 I-2 展示了一个网络服务的照片处理程序,它不仅被浏览器客户端触发,而且还被来自 Azure Storage 和 Amazon S3 的 blob 事件触发 —— 无需修改任何代码。服务开发者不需要学习特定的服务 API 来与这些服务对话 —— 所有的细节都被绑定抽象化了。
图 I-2. Dapr 事件绑定
在本章的其余部分,我们将为您简要介绍 Dapr 的架构以及它是如何工作的。Dapr 背后的核心理念确实非常简单,但我们很高兴看到一个充满活力的社区是如何从这个简单的理念中迅速成长起来的。我们希望这个想法也能引起你的共鸣。
Dapr 架构
Dapr 是由 Dapr CLI、运行时和一些控制面服务组成的。它还带有一个可扩展的构件库,包含了一些常见的构件,如状态存储、消息传递骨干、绑定和可观察性。我们将在本书中详细介绍所有这些部分,所以下面只是对其中一些部分的快速介绍,让你有一个大致的了解。
- Dapr CLI:Dapr CLI 是一个跨平台的命令行工具,你可以用它来配置、管理和监控你的 Dapr 实例。它还提供了对有用工具的访问,如 Dapr 仪表盘。
- Dapr Host:Dapr Host 承载着 Dapr 运行时的一个实例。最常见的形式是 Docker 容器,它被注入到一个 Pod 中,在 Kubernetes 上与用户代码并排运行。Dapr 也可以作为一个服务进程或一个守护进程以独立模式运行。Dapr 主机实现了通信协议,如 HTTP 和 gRPC。
- Dapr API:Dapr API 定义了 Dapr 运行时间的可编程接口。
- Dapr 运行时:Dapr 运行时实现了 Dapr API。它是 Dapr 的核心功能。
- Dapr Operator:Dapr Operator 是一个专门针对 Kubernetes 的组件,支持 Dapr 的 Kubernetes 模式。它管理配置和绑定,这些配置和绑定是作为 Kubernetes 定制资源实现的。
- Dapr 边车注入器:当 Dapr 在 Kubernetes 模式下运行时,该组件处理 Dapr 边车容器的注入。
- Dapr 放置服务:放置(Placement)服务管理到 Dapr 实例或服务分区的路由。它维护一个路由表,将对特定行为体 ID 或分区 ID 的请求引向同一个 Dapr 运行时实例。关于这个服务的细节,请看 第 5 章。
- Dapr Sentry:Dapr Sentry 是一个内置的证书颁发机构(CA),用于证书的颁发和管理。
- 构建块 · 状态存储: Dapr 把行为者和有状态的服务状态保存到可配置的状态存储中。Redis 是默认的本地状态存储,但是其他的,包括集群上的存储,都可以插入到 Dapr 中。还有一个不断增长的由社区提供的支持状态存储的列表,包括 Azure Cosmos DB、Cassandra、etcd、Firestore、Memcached、MongoDB、ZooKeeper 等等。关于如何定义和配置自定义状态存储的细节,见 第 2 章。
- 构建块 · 发布/订阅:默认情况下,Dapr 提供一次性的消息传递,并将 Redis Streams 配置为可靠的消息传递骨干。关于消息传递的细节以及如何定制消息传递行为,请参见 第 3 章。
- 构建块 · 绑定:Dapr 使用绑定(Binding)来连接应用程序代码和不同的输入和输出通道。它定义了一个非常简单的绑定接口,并以单线程的方式调用这个接口。这种设计使得编写一个新的绑定是一项非常容易的任务,通常可以在几个小时内完成。
- 构建块 · 可观察:Dapr 与 OpenTelemetry 集成,OpenTelemetry 是一套用于收集应用程序指标和分布式跟踪的开源库。关于如何配置分布式跟踪和如何收集应用指标的细节,请参见 第 1 章。
你几乎已经准备好使用 Dapr 了。下一节中的入门样本使用了原始的 HTTP/gRPC 接口,但我们也提供了一些特定语言的体验,如下所述。
开发语言支持
Dapr 是不分语言的。它通过 HTTP 或 gRPC 提供分布式应用构建模块。然而,我们承认许多开发者希望使用特定语言、强类型的软件开发包(SDK)。开箱即用,Dapr 提供了一些流行语言的 SDK,包括 C#、Python 和 Java,都支持 Actor。我们希望社区在未来能贡献更多的 SDK,并使用更多的编程语言。
:::tips 即使你选择了一个基于 Dapr 的特定语言的行为体(Actor)框架,你的行为体仍然可以与使用其他 Dapr 行为体框架或纯 Dapr 运行时实现的行为体互操作。 :::
Dapr 运行时的实现和绑定是用 Go 语言开发的,这是开源社区的首选语言。理论上,其他编程语言的 Dapr API 实现也会出现—我们会把这个决定留给社区。
好了,理论已经足够了。现在让我们把 Dapr 付诸行动吧!
开始 Dapr 开发
你有几种不同的选择来开始使用 Dapr:独立模式、Kubernetes 模式或特定语言的 SDK 之一。单机模式是在本地机器上开始使用的最简单方法。Kubernetes 模式是 Dapr 在生产环境中的使用方式。最后,SDK 让你在熟悉的语言中快速掌握 Dapr(尤其是 Dapr 行为体)。本节将带领你了解所有这三种途径。
使用 Dapr 独立模式开发 Hello World
遵循普遍的传统,我们将以一个 “Hello, World” 的应用程序开始我们的旅程,该程序监听一个问候事件,并回应一个 Hello, World! 的信息。
在你的机器上获取 Dapr
Dapr CLI 提供了一个 init 命令,可以将 Dapr 运行时启动到你的本地机器或 Kubernetes 集群。要以独立模式安装 Dapr,请按照以下步骤进行:
- 确保 Docker 正在机器上运行,Dapr CLI 使用 Docker 来运行 Redis 状态存储和 Dapr 放置服务;
- 从 GitHub 下载发布的二进制文件;
- 将 Dapr CLI 二进制文件提取到你选择的文件夹中;
- 另外,为了使 CLI 易于访问,在 macOS 和 Linux 上将二进制文件移到
/user/local/bin文件夹,或者在 Windows 上将步骤 3 中的文件夹添加到你的PATH环境变量中; - 通过运行此程序初始化 Dapr:
dapr init。
init 命令启动了两个容器:一个是 Redis 容器,用于可靠的消息传递以及默认的状态存储;另一个是 Dapr 放置服务容器,管理行为体和分区服务的放置。如果你收到错误信息说 “启动容器失败”,你可能有一个现有的容器,它持有所需的端口(Redis 为 6379,macOS/Linux 上放置服务为 50005,Windows 上放置服务为 6050)。在这种情况下,你需要关闭持有这些端口的容器,并再次尝试启动命令。如果一切正常,你应该看到如下内容:
Making the jump to hyperspace...Downloading binaries and setting up components...Success! Dapr is up and running
一旦你初始化了 Dapr,你可以用以下方法检查当前的 CLI 版本和 Dapr 运行时间版本:
$ dapr --version
在撰写本报告时,该命令返回:
cli version: 0.1.0runtime version: 0.1.0
现在我们可以进入我们的应用程序了!
创建 Hello World 应用
现在是时候创建一个 Hello World 应用程序了。因为 Dapr 与语言无关,所以我们将在本书中用各种编程语言编写示例代码。你可以在 Dapr 样例仓库 中找到不同语言的代码样本。
对于这个例子,我们将使用 Go。用你喜欢的代码编辑器(如 Visual Studio Code)创建一个新的 main.go 文件,内容如下:
package mainimport ("encoding/json""fmt""log""net/http")type incomingEvent struct {Data interface{} `json:"data"`}func main() {http.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) {var event incomingEventdecoder := json.NewDecoder(r.Body)decoder.Decode(&event)fmt.Println(event.Data)fmt.Fprintf(w, "Hello, World!")})log.Fatal(http.ListenAndServe(":8088", nil))}
这段代码在 8088 端口启动了一个网络服务器,当它得到一个对 /greeting 路由的请求时,它打印出请求体(它假定包含一个数据字段)并返回字符串 Hello, World!。
通过 Dapr 边车进程启动你的应用程序
在一个命令行或终端窗口中,启动一个新的 Dapr 运行实例,作为你的应用程序的挎包进程。注意,dapr run 命令允许你在命令行中后缀启动你的应用程序 —— 本例中是 go run main.go。
$ dapr run --app-id hello-dapr --app-port 8088 --port 8089 go run main.go
该命令应产生如下输出:
Starting Dapr with id hello-dapr. HTTP Port: 8089. gRPC Port: 52016== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="starting Dapr Runtime -- version 0.1.0 -- commit 4358565-dirty"== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg= "log level set to: info"== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="standalone mode configured"== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="dapr id: hello-dapr"== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="loaded component messagebus (pubsub.redis)"== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg= "loaded component statestore (state.redis)"== DAPR == time="2019-11-11T21:22:15-08:00" level=info msg="application protocol: http. waiting on port 8088"You’re up and running! Both Dapr and your app logs will appear here ...
输出显示 Dapr 运行时是以独立模式启动的,它在 8088 端口连接到一个应用程序,它使用 Redis 作为状态存储和消息通道,并且它正在监听 HTTP 流量(在指定的端口,8089)和 gRPC 请求(在一个随机端口,52016)。Dapr 在同一个终端窗口中显示其运行时的日志和你的应用程序的日志。这使你可以很容易地追踪 Dapr 边车和你的应用程序之间的互动。
在我们继续之前,让我们看一下我们在前面的命令中使用的命令行开关:
app-id:每个 Dapr 边车进程都由一个字符串 ID 来识别。Dapr 运行时实例使用这些 ID 来相互寻址。app-port:应用服务的运行端口。port:Dapr 边车进程所监听的 HTTP 端口。
表 I-1 总结了在撰写本文时可用的 dapr run 命令行开关。第 1 章 中提供了关于这些开关的更详细的信息,你可以随时使用 dapr run --help 命令来获得关于支持的开关的最新信息。
表 I-1. dapr run 命令开关
| 开关 | 用途 | 默认值 |
|---|---|---|
app-id |
Dapr 边车 ID | |
app-port |
应用服务端口 | |
config |
Dapr 配置文件 | |
enable-profilig |
通过 HTTP 端点启用 pprof 剖析功能 | false |
grpc-port |
Dapr gRPC 端口 | -1(随机) |
log-level |
设置日志的粗略程度:debug、info、warning、error、fatal 或 panic |
info |
max-concurency |
控制允许的并发呼叫的数量 | -1(无限制) |
port |
Dapr HTTP 端口 | -1(随机) |
profile-port |
配置文件服务器要监听的端口 | -1(随机) |
protocol |
告诉 Dapr 使用 HTTP 或 gRPC 来与应用程序进行通信 | http |
开始把玩 Dapr
现在是时候用 Dapr 来玩一玩了!Dapr 边车提供了一个 /<version>/invoke/<action-id>/method/<methodname> 路由,客户可以用它来直接调用你应用程序中的方法。例如,对 /v1.0/invoke/hello-dapr/method/greeting 路由的请求将被传递给你的 Go 应用程序中的 /greeting 处理程序。
为了测试这一点,启动一个浏览器并导航到这个地址:http://localhost:8089/v1.0/invoke/hello-dapr/method/greeting
你应该得到一个 Hello, World! 的消息。恭喜你,你刚刚通过 Dapr 在你的应用程序上调用了一个 Web 方法。
好吧,也许这本身并不是很令人兴奋。在本书的后面,你会看到 Dapr 边车如何为你带来分布式跟踪和 HTTPS 终止等功能,而不需要你写任何额外的代码。
接下来,让我们让应用程序变得有状态。如前所述,从无状态过渡到有状态,在其他框架中并不容易,但在 Dapr 中,这个过程非常自然。
:::info
Dapr 通过支持这些 HTTP 方法的直接调用:GET, POST, DELETE和 PUT。
:::
添加状态
状态管理是 Dapr 边栏带给你的应用程序的能力之一。我们将在 第 2 章 中详细讨论这个话题,但现在让我们通过一个快速的例子来告诉你如何把我们的 Hello World 应用程序变成一个有状态的应用程序。我们将把一个简单的键值保存到一个状态存储中作为应用程序的状态。
定义 Redis 状态存储
首先,我们需要告诉 Dapr 边车,一个状态存储是可用的。在独立模式下,你可以通过在你的应用程序运行的文件夹下的组件文件夹中添加状态存储描述文件来实现这一点。
当你运行 dapr init 命令时,Dapr CLI 会自动创建带有默认 Redis 状态存储配置和 Redis 消息传递主干配置的 components 文件夹。例如,以下是 Dapr CLI 生成的默认 redis.yaml 配置文件:
apiVersion: dapr.io/v1alpha1kind: Componentmetadata:name: statestorespec:type: state.redismetadata:- name: redisHostvalue: localhost:6379- name: redisPasswordvalue: ""
我们将在 第 2 章 中讨论该文件的模式。现在,请注意该文件将 Redis 的主机地址定义为 localhost:6379,没有密码。如果你使用不同的 Redis 服务器,你应该更新这个文件以符合你的 Redis 设置。
更新应用代码来处理状态
你的应用程序可以通过 /<version>/state/store name/<key> 路由从 Dapr 边车请求状态,并且可以通过向边车的 /<version>/state/store name 路由发布一个状态对象来保存状态。当你发布状态时,Dapr 允许你将多个键/值对作为一个 JSON 数组发布:
[{"key": "key-1","value": "some simple value"},{"key": "key-2","value" : {"field-a" : "value-a","field-b" : "value-b"}}]
当你请求返回状态时,比如本例中的 key-1 值,你会得到编码为 JSON 对象的值本身。在这种情况下,你会得到一些简单的值。
更新你的应用代码,如下所示:
package mainimport ("bytes""encoding/json""fmt""io/ioutil""log""net/http""strconv")type stateData struct {Key string `json:"key"`Value int `json:"value"`}func main() {http.HandleFunc("/greeting", func(w http.ResponseWriter, r *http.Request) {resp,:= http.Get("http://localhost:8089/v1.0/state/statestore/mystate")defer resp.Body.Close()body,:= ioutil.ReadAll(resp.Body)strVal := string(body)count := 0if strVal != "" {count, _ = strconv.Atoi(strVal)count++}stateObj:=[]stateData{stateData{Key: "mystate", Value: count}}stateData, _ := json.Marshal(stateObj)resp, = http.Post("http://localhost:8089/v1.0/state/statestore","application/json", bytes.NewBuffer(stateData))if count == 1 {fmt.Fprintf(w, "I’ve greeted you " + strconv.Itoa(count) + " time.")} else {fmt.Fprintf(w, "I’ve greeted you " + strconv.Itoa(count) + " times.")}})log.Fatal(http.ListenAndServe(":8088", nil))}
现在,每当 /greeting 处理程序被调用时,它通过向 http://localhost:8089/v1.0/state/statestore/mystate(其中8089是 Dapr 边车正在监听的端口)发送 GET 请求来请求带有 mystate 键的状态值。然后,它增加该值并将其发布到 http://localhost:8089/v1.0/state/statestore,以便保存。接下来,让我们测试一下我们的应用程序。
测试应用服务
为了测试该应用程序,你需要用 Dapr 边车启动它:
$ dapr run --app-id hello-dapr --app-port 8088 --port 8089 go run main.go
一旦 Dapr 边车被启动,你应该在终端窗口看到几行新的字,表明状态存储已经被找到并初始化:
== DAPR == time="2019-11-11T22:01:03-08:00" level=info msg="dapr id: hello-dapr"== DAPR == time="2019-11-11T22:01:03-08:00" level=info msg="loaded component statestore (state.redis)"== DAPR == time="2019-11-11T22:01:03-08:00" level=info msg="loaded component messagebus (pubsub.redis)"== DAPR == time="2019-11-11T22:01:03-08:00" level=info msg="application protocol: http. waiting on port 8088"You’re up and running! Both Dapr and your app logs will appear here.== DAPR == time="2019-11-11T22:01:15-08:00" level=info msg= "application discovered on port 8088"== DAPR == 2019/11/11 22:01:15 redis: connecting to localhost:6379== DAPR == 2019/11/11 22:01:15 redis: connected to localhost:6379 (localAddr: [::1]:55231, remAddr: [::1]:6379)
启动浏览器并导航到 http://localhost:8089/v1.0/invoke/hello-dapr/method/greeting。当你刷新页面时,你应该看到问候语的数量在增加。
现在,在终端中按下 Ctrl-C 来停止 Dapr 边车和你的应用程序。这模拟了应用程序的完全崩溃(以及 Dapr 边车)。然后再次用 Dapr 边车启动你的应用程序,你会发现应用程序的状态被保留了。这其中的原因应该是显而易见的:Dapr 边车将状态保存到外部的 Redis 存储中。
如果你想检查保存在 Redis 中的状态,你可以使用 Docker 的 exec 命令连接到 Redis 服务器并查询密钥(你可以通过使用 docker ps 得到 Redis 容器的名称):
$ docker exec -it <name of your Redis container> redis-cli
图 I-3 显示,在我的机器上,我有一个 hello-dapr-mystate 哈希键,其数据字段的值为 6,版本字段的值为 7(我们将在 第 2 章 解释版本工作原理)。
图 I-3. 查询 Redis 记录
现在你已经在本地机器上运行你的应用程序,让我们看看如何让同样的应用程序在 Kubernetes 集群上运行。
使用 Dapr Kubernetes 模式开发 Hello World 应用
为了演示在 Kubernetes 上使用 Dapr,我们将创建一对服务:一个定期发送消息的 Python 服务,以及一个监听消息的 Node.js 服务。
要开始行动,你需要一个启用了基于行为体访问控制(RBAC)的 Kubernetes 集群。你还需要针对你的集群配置 kubectl。
:::info 本书假设你熟悉 Kubernetes,所以我们在此跳过任何 Kubernetes 介绍。 :::
安装 Dapr
如果你已经安装了 Dapr CLI,你可以使用以下命令将 Dapr 引导到 Kubernetes 集群上,请确保你的 kubectl 目前是针对该集群配置的。
$ dapr init --kubernetes
或者你可以通过使用 Helm 来部署 Dapr:
$ helm repo add dapr https://daprio.azurecr.io/helm/v1/repo$ helm repo update$ helm install dapr/dapr --name dapr --namespace dapr-system
为了快速验证 Dapr 是否已经配置,使用以下命令列出你的 Kubernetes 集群中的 Pod(如果你使用 Helm,你需要在以下命令中添加 -n dapr-system 开关,因为 Helm Chart 将 Dapr Pod部署在 dapr-system 命名空间下)。
$ kubectl get pods
你应该看到四个与 Dapr 有关的 Pod —— dapr-operator、dapr-placement、dapr-sentry 和 dapr-sidecar-injector,如下面的输出示例所示:
NAME READY STATUS RESTARTS AGEdapr-operator-76888fdcb9-x9ljc 1/1 Running 0 10sdapr-placement-666b996945-dh55v 1/1 Running 0 9sdapr-sentry-68997bc894-c49ww 1/1 Running 0 10sdapr-sidecar-injector-744d97578f-dkcbq 1/1 Running 0 9s
随着 Dapr 在 Kubernetes 上的正确初始化,我们现在可以继续实现我们的消息接收器和消息发送器,首先是接收器。
编写信息接收服务
我们将使用 Node.js 编写接收器。我们不使用 npm 来初始化项目,而是从头开始创建所有的应用程序文件。该应用程序是一个简单的应用程序,它监听 /greeting 路由并打印出它得到的任何东西。要开始使用,请遵循以下步骤:
- 创建一个名为
node_receiver的新文件夹; - 在新文件夹中创建一个新的
app.js文件。如前所述,这个应用程序很简单 —— 它监听对/greeting路由的POST请求并打印出请求正文;正如你所看到的,在下面的代码中没有任何与 Dapr 有关的内容;这是一个简单的 Node.js 服务器,它完全不知道 Dapr 的存在; ```javascript const express = require(‘express’); const bodyParser = require(‘body-parser’);
const app = express(); const port = 8088; app.use(bodyParser.json());
app.post(‘/greeting’, (req, res) => { console.log(req.body); res.status(200).send(); });
app.listen(port, ()=> console.log(Receiver is running on port ${port}))
3. 在同一文件夹下创建一个新的 `project.json` 文件,如下所示:```json{"name": "node_receiver","version": "1.0.0","description": "","main": "app.js","author": "","license": "ISC","dependencies": {"body-parser": "^1.18.3","express": "^4.16.4"}}
在我们进一步讨论之前,让我们确保 Node.js 应用程序能够自行工作:
- 转到
node_receiver文件夹; - 安装必要的依赖项:
npm install; - 启动应用:
node app.js; - 使用像 Postman 这样的工具,向
http://localhost:8088/greeting,发送一个带有以下 JSON 有效载荷的POST请求:{ "msg": "Hello, World!" }。
你应该看到应用程序的控制台中记录的信息。
现在是将应用程序打包成 Docker 容器的时候了,这样我们就可以将其部署到 Kubernetes:
在
node_receiver文件夹下创建一个Dockerfile:FROM node:8-alpineWORKDIR /usr/src/appCOPY . .RUN npm installEXPOSE 8088CMD [ "node", "app.js" ]
使用
docker build构建 Docker 镜像(用你的 Docker Hub 账户名称替换<username>):$ docker build -t <username>/node_receiver .
通过启动容器,然后向它发送一个 POST 请求,进行一次试运行:
$ docker run --rm -p 8088:8088 <username>/node_receiver
一旦你完成了测试,按
Ctrl-C停止,并移除容器;- 将镜像推送到 Docker Hub(假设你已经用你的 Docker Hub 账户登录了 Docker Hub)。
现在,接收器已经准备好了。接下来,我们将转向发送方。$ docker push <username>/node_receiver
编写信息发送服务
我们将用 Python 编写发送器。它将每五秒钟发送一条信息:
- 创建一个名为
python_sender的新文件夹; - 在新文件夹下创建一个新的
app.py文件,内容如下: ```python import time import requests import os
n = 0
while True:
n = (n + 1) % 1000000
message = {“msg” :”Hello, World! “ + str(n)}
try:
resp = requests.post(“http://localhost:3500/v1.0/invoke/nodereceiver/method/greeting“, json=message)
except Exception as e:
print(e)
time.sleep(5)
3. 在同一文件夹下创建一个 `Dockerfile`,其中包含这些内容:```dockerfileFROM python:3.7.1-alpine3.8COPY . /appWORKDIR /appRUN pip install requestsENTRYPOINT ["python"]CMD ["app.py"]
- 构建并推送 Docker 容器:
编码部分到此结束。接下来,我们将创建 Kubernetes 清单来部署发送器和接收器。$ docker build -t <username>/python_sender .$ docker push <username>/python_sender
创建 Kubernetes 清单
最后,我们准备创建 Kubernetes 部署清单,部署发送方和接收方容器。要做到这一点,请按以下步骤操作:
- 创建一个新的
app.yaml文件,内容如下(请确保用你的 Docker Hub 账户名替换<username>)。注意,为了启用 Dapr 边车,你在容器部署中注释dapr.io/enabled和dapr.io/id。如果你期望接收网络流量,你还需要添加dapr.io/port注解。Dapr 边车注入器会对这些注解做出反应,并将 Dapr 边车容器注入到你的应用 Pod 中: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nodereceiver labels: app: node spec: replicas: 1 selector: matchLabels:
template:app: node
spec:metadata:labels:app: nodeannotations:dapr.io/enabled: "true"dapr.io/id: "nodereceiver"dapr.io/port: "8088"
containers:- name: nodeimage: <username>/node_receiverports:- containerPort: 8088imagePullPolicy: Always
apiVersion: apps/v1 kind: Deployment metadata: name: pythonsender labels: app: python spec: replicas: 1 selector: matchLabels: app: python template: metadata: labels: app: python annotations: dapr.io/enabled: “true” dapr.io/id: “pythonsender” spec: containers:
- name: pythonsenderimage: <username>/python_senderimagePullPolicy: Always
2. 使用 kubectl 部署该文件:`kubectl apply -f ./app.yaml`;2. 一旦文件部署完毕,使用 kubectl 来查询已部署的 Pod。你应该看到一个 `nodereceiver` Pod 和一个 `pythonsender` Pod,如下面的样本输出所示。正如你所看到的,每个 Pod 包含两个容器 —— 一个应用容器和一个自动注入的 Dapr 边车容器:```bash$ kubectl get podsNAME READY STATUS RESTARTS AGE...nodereceiver-7668f7899f-tvgk9 2/2 Running 0 8mpythonsender-5c7c54c446-nkvws 2/2 Running 0 8m
- 要查看发送方和接收方是否在相互通信,请使用以下命令(将
<postfix>替换为你环境中的实际 Pod ID 的后缀):
这个命令产生的输出如下:$ kubectl logs nodereceiver-<postfix> node
现在你知道了如何通过 HTTP 协议使用 Dapr 边车。在下一节,你将学习如何使用 gRPC 协议来代替。如果你对使用 gRPC 不感兴趣,你可以跳过下一节。Receiver is running on port 8088{ msg: 'Hello, World! 2' }{ msg: 'Hello, World! 3' }{ msg: 'Hello, World! 4' }{ msg: 'Hello, World! 5' }{ msg: 'Hello, World! 6' }
使用 gRPC
gRPC 是一个由 Google 开发的开源远程过程调用(RPC)系统。它使用协议缓冲区(Protobuf),一种高效的数据序列化机制,也是由谷歌开发的,作为接口描述语言(IDL)和数据序列化方法。与其他 RPC 系统一样,gRPC 客户端通过 gRPC 存根(Stub)与 gRPC 服务器进行通信,存根可以使用 gRPC 工具自动生成。
:::info 对 gRPC 的详细介绍超出了本书的范围。更多细节请参见 https://grpc.io。 :::
gRPC 使用 HTTP/2 作为通信协议。与 HTTP/1.1 相比,这个版本有几个性能上的优势,包括主动数据推送以避免在一个页面上对资源的多次请求,在一个 TCP 连接上多路请求,HTTP 头数据压缩,以及请求管道等功能。
自引入以来,gRPC 在网络服务和微服务社区获得了极大的欢迎。Dapr 也不例外:它将 gRPC 用于 Dapr 边车之间的通信,并为与客户端和应用程序代码的通信提供本地 gRPC 支持。
现在是时候进行一些练习了。在下面的演练中,你将使用 Dapr 的 gRPC 客户端来调用服务器上的一个方法。
从一个 gRPC 客户端调用一个应用程序
在本节中,我们将创建一个 gRPC 客户端,调用 Hello World 服务中的 /greeting 方法。我们将使用你在本介绍中看到的第四种编程语言,C#。
:::info Dapr 是不分语言的,为了证明这一点,我们想在本书中涵盖尽可能多的编程语言。幸运的是,现代编程语言在总体上是相当可读的。 :::
前提条件
要完成下面的练习,你将需要:
- .NET Core SDK 2.2 或以上
- Visual Studio 2013 或更新版本,或 Visual Studio Code(我们已经用 Visual Studio 2017 和 Visual Studio 2019 测试了以下步骤)
- Git 客户端
克隆 Dapr 代码仓库
由于我们想自动生成一个 gRPC 客户端,我们需要访问 Dapr 的 Protobuf 定义文件(通常扩展名为 .proto)。你可以通过克隆 dapr/dapr GitHub 仓库来获得该文件。
$ git clone https://github.com/dapr/dapr.git
:::info
由于 Dapr 代码仓库是基于 Go 的,按照 Go 的惯例,你应该把版本库克隆到本地 $GOPATH/src/github.com/dapr/dapr 文件夹。
:::
创建客户端应用
我们将使用 Visual Studio 创建一个简单的 C# 控制台应用程序作为 gRPC 客户端:
- 创建一个新的 .NET Core 控制台应用程序,命名为
grpc-client; - 在你的项目中添加一些
NuGet包 —— 你需要这些工具来自动生成 gRPC 客户端:Grpc.Toos,Grpc.Protobuf,Grpc; - 在项目中添加一个名为
protos的新文件夹; - 右键点击
protos文件夹,选择Add Existing Items;挑选$GOPATH/src/github.com/dapr/dapr/pkg/proto/dapr/dapr.proto文件(你可以选择添加该文件的链接,而不是将该文件复制到本地); - 右键单击新添加的文件,选择属性菜单选项。在属性窗口中,将构建动作改为
Protobuf compiler,将 gRPC 存根类改为Client only,如 图 I-4 所示:
图 I-4. dapr.proto 属性
- 用以下代码替换
Program.cs文件中的所有代码: ```csharp using Google.Protobuf.WellKnownTypes; using Grpc.Core; using System;
namespace grpc_client { class Program { static void Main(string[] args) { Channel channel = new Channel(“localhost:3500”, ChannelCredentials.Insecure); var client = new Dapr.Client.Grpc.Dapr.DaprClient(channel); Value val = new Value(); val.StringValue = “Hi, Dapr.”; Metadata metadata = new Metadata(); var result = client.InvokeService(new Dapr.Client.Grpc.InvokeServiceEnvelope { Method = “greeting”, Id = “hello-dapr”, Data = Any.Pack(val) }, metadata); Console.WriteLine(result.Data.Value.ToStringUtf8()); } } }
这段代码使用 gRPC 将字符串 `Hi, Dapr.`通过位于`localhost:3500` 的 ID 为 `hello-dapr` 的 Dapr 边车发送至一个 `greeting` 方法。<a name="uo3r7"></a>### 测试客户端应用在这个测试中,我们将重新使用我们先前创建的 Go 版本的 Hello World 应用程序:1. 启动带有 Dapr 边车的 Go 应用程序。注意,在这种情况下,你需要使用 `grpc-port` 开关来指定一个 gRPC 端口:```bash$ dapr run --app-id hello-dapr --app-port 8087 --port 8089 --grpc-port 3500 go run main.go
- 在 Visual Studio 中,按
Ctrl-F5来启动客户端。程序应该在打印完Hello, World!字符串后停止; - Go 应用程序目前期望一个带有
data字段的 JSON 对象;然而,客户端只发送了一个简单的字符串。如果你希望 Go 应用程序能够正确显示有效载荷,你可以更新/greeting处理程序,直接打印正文,而不是尝试解码:
这就结束了客户端的工作。接下来我们将进入服务器端的工作。txt, _ := ioutil.ReadAll(r.Body)fmt.Println(string(txt))
编写一个 gRPC 服务端
现在我们将重写 Hello World 服务,以暴露一个 gRPC 端点而不是 HTTP 端点。为了保持在每个例子中使用不同编程语言的趋势,我们这次将使用 Java。
前提条件
要完成下面的练习,你将需要:Java 开发工具包(JDK),一个 Git 客户端,以及 Maven。
克隆 Dapr 代码仓库(如有必要)
在这个例子中,我们将使用 Dapr 代码库中的 Protobuf 定义。如果你还没有这样做,请克隆该代码库:
$ git clone https://github.com/dapr/dapr.git
创建服务端应用
下面的演示使用了 Maven 命令行工具(mvn)。虽然有 Eclipse 和 IntelliJ 等 IDE 的插件,但我们认为使用命令行工具可以最清楚地了解正在发生什么。以下是创建服务器应用程序的步骤:
使用
mvn创建一个名为grpc-server的新项目。注意,该命令指定组 ID 为io.dapr,这与 Dapr 资源库中的 Protobuf 描述文件相匹配 —— 如果你想使用不同的组 ID,你需要在步骤 4 中更新你的 Protobuf 描述以匹配参数值:$ mvn archetype:generate -DgroupId=io.dapr -DartifactId=grpc-server -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
切换到
grpc-server目录;- 制作一个新的
src/main/proto文件夹(对于 Linux/MacOS,你需要添加-p开关):mkdir src/main/proto; - 将
$GOPATH/src/github.com/dapr/dapr/pkg/proto/dapr/dapr.proto复制到这个文件夹。我们将在本教程中重复使用这个文件,因为它包含我们需要的应用程序服务定义;如果你愿意,你可以从你自己的 Protobuf 定义文件开始,但你需要确保你的服务定义与dapr.proto文件中的应用服务定义兼容; 更新
pom.xml文件以包括必要的依赖性:<dependencies>...<dependency><groupId>io.grpc</groupId><artifactId>grpc-netty</artifactId><version>1.14.0</version></dependency><dependency><groupId>io.grpc</groupId><artifactId>grpc-protobuf</artifactId><version>1.14.0</version></dependency><dependency><groupId>io.grpc</groupId><artifactId>grpc-stub</artifactId><version>1.14.0</version></dependency></dependencies>
在
pom.xml中添加构建脚本:<build><extensions><extension><groupId>kr.motd.maven</groupId><artifactId>os-maven-plugin</artifactId><version>1.5.0.Final</version></extension></extensions><plugins><plugin><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.5.1</version><configuration><protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact><pluginId>grpc-java</pluginId><pluginArtifact>io.grpc:protoc-gen-grpc-java:1.14.0:exe:${os.detected.classifier}</pluginArtifact></configuration><executions><execution><goals><goal>compile</goal><goal>compile-custom</goal></goals></execution></executions></plugin></plugins></build>
运行 Maven 构建命令(
mvn),这将在target\generated-sources\protobuf\grpc-java\io\dapr文件夹下生成您需要的源文件:$ mvn -DskipTests package
添加一个新的
src/main/java/io/dapr/AppServiceImpl.java文件,代码如下;这是一个典型的 gRPC 服务器实现,它实现了应用服务定义的一个方法(invokeService,当 Dapr 向应用分派直接调用请求时,它被调用): ```java package io.dapr;
import com.google.protobuf.Any; import com.google.protobuf.StringValue; import io.dapr.DaprGrpc.DaprImplBase; import io.dapr.DaprProtos.*;
public class AppServiceImpl extends DaprImplBase {
@Override
public void invokeService(InvokeServiceEnvelope request,
io.grpc.stub.StreamObserver
9. 修改 `src/main/java/io/dapr/App.java`:```javapackage io.dapr;import io.grpc.*;public class App {public static void main( String[] args ) throws Exception {Server server = ServerBuilder.forPort(8090).addService(new AppServiceImpl()).build();server.start();System.out.println("Server started.");server.awaitTermination();}}
- 再次运行 Maven 构建命令,确保一切都能编译:
mvn -DskipTests package。
测试 gRPC 服务端
现在 gRPC 服务器已经准备好进行测试了。在这个演练中,我们将重新使用我们在本介绍中早期创建的 C# gRPC 客户端:
使用 Maven 构建并启动 gRPC 服务端:
$ mvn -DskipTests package exec:java -Dexec.mainClass=io.dapr.App
启动一个 Dapr 边车。注意,在这种情况下,(应用)协议被设置为
grpc,并且使用了一个假的echo a命令,因为我们是附加到一个现有的进程:$ dapr --app-id hello-dapr --app-port 8090 --protocol grpc --grpc-port 3500 echo a
从 Visual Studio 运行 C# gRPC 客户端。你应该看到 gRPC 服务端记录的请求,以及返回给客户端的
Hello, World!消息。
在结束这个介绍之前,让我们简单地看看 Dapr 的另一个特性 —— 绑定,它是实现编写平台无关的代码的关键。
绑定(Binding)
绑定让你把你的应用程序与各种事件源或目的地联系起来。这使得你可以编写平台无关的代码,可以动态地适应不同的环境和背景。例如,你可以使用输入绑定来绑定 Azure Blob 存储容器,每当有新的 Blob 被投放到容器中时就会触发绑定,或者你可以使用输出绑定来在服务状态超过某个阈值时调用 AWS Lambda 表达。我们将在 第 3 章 中详细介绍绑定,但现在,让我们做几个快速实验,看看绑定是如何工作的。
单机模式下的绑定
绑定的主要好处之一是关注点的分离。当你使用绑定时,你设计你的应用程序来接收来自一个命名的事件源的事件。然而,你并没有提供关于事件源的任何细节。然后,你(或操作你的应用程序的人)可以在以后选择将你的应用程序重新绑定到一个完全不同的事件源而不影响你的代码。事实上,你甚至可以在你的应用程序运行时重新绑定它们。
Dapr 带有一些预置的绑定。在独立模式下,每个绑定都由 components 文件夹中的元数据文件描述。在下面的练习中,我们将首先作为一个开发者,用 JavaScript 编写一个简单的 HTML 页面,将事件发送到一个名为 target 的目标。然后,我们将作为一个应用程序操作员,将目标配置为 Azure Event Hub。
开发者:编写一个简单的 HTML 页面
在这一部分,我们将写一个带有按钮的 HTML 页面。当按钮被点击时,应用程序通过向 Dapr 边车发出 POST 请求,向名为 target 的绑定发送一条消息:
- 创建一个名为
html-app的新文件夹; - 在该文件夹下创建一个
index.html文件,其内容如下;该代码创建了一个按钮,当点击时,使用 jQuery 向 Dapr 边车发送一个 POST 请求,网址是http://localhost:3500/v1.0/bindings/target:
这样的 POST 是可能的,因为 Dapr 边车默认允许跨区域请求。你可以通过改变<!DOCTYPE html><html><head><script src="jquery.min.js"></script></head><body><button onclick="postMessage()">Click me!</button></body><script>function postMessage() {$.ajax({type: "POST",url: "http://localhost:3500/v1.0/bindings/target",contentType:"application/json",data: JSON.stringify({"data": "Hello, World!"})});}</script></html>
allowed-origins开关的值来改变这种行为(见表 I-1)。
Operator:定义一个 Azure Event Hub 绑定
现在,我们将戴上 Operator 的帽子,定义目标到底是什么。在这次演练中,我们将把目标定义为 Azure Event Hub。
:::info 我们假定你对 Azure 的总体情况很熟悉,包括如何使用 Azure Portal,特别是 Azure Cloud Shell。 :::
按照这些步骤来定义 Azure Event Hub 绑定:
- 登录到 Azure Cloud Shell;
创建一个新的 Azure Event Hub 命名空间:
$ az eventhubs namespace create --name <namespace name> --resource-group <resource group name> -l <region, such as westus>
创建一个新的 Azure Event Hub 事件中心:
$ az eventhubs eventhub create --name <event hub name> --resource-group <resource group name> --namespace-name <namespace name>
检索 Event Hub 的连接字符串。你在第 6 步需要的是输出中的
primaryConnectionString字段:$ az eventhubs namespace authorization-rule keys list --resource-group <resource group name> --namespace-name <namespace name> --name RootManageSharedAccessKey
创建一个新的
components目录;- 在该文件夹下添加一个新的
eventhub.yaml文件;你需要用步骤 4 中的连接字符串替换<your con nection string>;注意,你需要在连接字符串的末尾加上;<event hub name>,以指定准确的事件集线器名称:apiVersion: dapr.io/v1alpha1kind: Componentmetadata:name: targetspec:type: bindings.azure.eventhubsmetadata:- name: connectionStringvalue: <your connection string>
通过 Dapr 发送事件
现在我们准备通过 Dapr 向 target 发送一个事件。正如你所看到的,HTML 代码并意识不到有 Azure Event Hub。它只是将事件发布到 Dapr 边车,边车会解析目标的含义,并将事件转发给指定的目标。通过以下操作来发送该事件:
- 在浏览器中启动 HTML 页面;
- 启动 Dapr 边车:
dapr run --port 3500 echo a; - 点击 HTML 页面上的
Click Me按钮。
好吧,没有什么事情发生。然而,如果你登录到你的 Azure 门户,观察事件集线器上的传入消息,你会看到消息正在通过 Dapr 发送,如 图 I-5 所示。
图 I-5. Azure 门户中的 Event Hub 指标
就像其他 Dapr 功能一样,绑定在 Kubernetes 下工作得很好。我们接下来会快速地看一下这个问题。
在 Kubernetes 模式下的绑定
如果你没有注意到,eventhub.yaml 文件是一个Kubernetes自定义资源定义(CRD)文件。你可以直接把这个文件部署到 Kubernetes 集群,Dapr 边车程序可以接收它并把它作为一个绑定。
要将 eventhub.yaml 部署 到Kubernetes 集群,使用以下命令:kubectl apply -f ./eventhub.yaml。
Kubernetes 上的 Dapr Operator 服务监控 Binding CRD 的变化,并在检测到变化时通知集群中的所有 Dapr 边车。这意味着你可以动态地更新你的绑定定义,在应用运行时将你的应用重新绑定到不同的源。这在许多情况下是相当强大的。例如,当你有一个突入云的边缘应用时,它可以动态地绑定到基于边缘的服务或基于云的服务,以适应环境变化。
总结
这篇介绍简要介绍了 Dapr,这是一种语言中立的编程模型,它使用一个挎包来提供微服务构建块 —— 包括绑定、状态管理、可靠的消息传递和行为者能力 —— 给应用程序代码,而不要求应用程序代码包含任何 Dapr 特定的库或 SDK。
Dapr 的一个关键目标是向开发者抽象出基础设施的细节。通过 Dapr 构建模块,应用程序代码与底层基础设施完全隔离,允许应用程序在运行时在不同的平台上进行配置和重新配置,而不需要改变应用程序代码。这是一个强大的功能,可以实现边缘和多云应用等场景。
我们希望你觉得 Dapr 很有趣。在本书的其余部分,我们将对所有 Dapr 组件进行扩展,并涵盖 Dapr 的各种应用场景。我们还将介绍如何以各种方式扩展 Dapr。我们希望你加入这个社区,帮助 Dapr 成为一个简单、高效、强大的分布式编程模型,为大家服务!
