在一个支持 Dapr 的应用程序中,基本的计算单元是一个服务。Dapr 的目标之一是使你能够以你喜欢的方式用你选择的语言编写这些服务。Dapr 与你的服务并肩而立,并在你需要时带来必要的能力,如状态管理、跟踪和安全通信。这一章将带领你了解 Dapr 的各种功能是如何产生的。
云计算之前的世界
大约在 2000 年,我(Haishi)在旧金山的一家金融软件公司工作。在二楼有一个服务器室,需要用特殊的钥匙卡才能进入。这个房间总是冷冰冰的,而且很吵。它承载着公司运营所需的一切 —— 域控制器、邮件服务器、文件服务器、源码库、数据库、测试实验室和所有的人力资源记录。除了服务器机架外,房间里到处都是堆积到天花板的磁带架。公司定期对服务器进行备份,所有的备份都存放在这些磁带上。房间里还配备了巨大的纯氮气罐,一旦发生火灾,就会释放纯氮气来拯救房间。
IT人员对服务器的保护非常好。他们经常因为开发人员要求部署新的服务版本而感到恼火 —— 毕竟,保持首席执行官的邮箱正常工作是更重要的事情。部署新版本是一项重要的工作:服务器被备份,数据库被备份,服务器被打补丁,数据迁移脚本被执行,验证测试被执行。如果测试失败,一切都会回滚,IT 人员就会对我们投来恶狠狠的目光。他们的传呼机发出哔哔声,并告诉我们他们只能在明天再做一次部署。
当时,浏览器/服务器(B/S)架构正在崛起,并将取代既定的客户/服务器(C/S)架构。B/S 架构的主要承诺是通过使客户机像网络浏览器一样薄来实现客户机的顺利采用。随着越来越多的公司试图采用基于订阅的商业模式,而不是传统的基于许可的商业模式,B/S 架构将大量的计算资源集中到集中的服务器上。这些服务器对业务至关重要。然而,管理它们成为一个越来越难的问题。公司不得不通过广泛的能力规划过程,以确保他们能够购买足够的服务器来满足他们的要求,而不至于使预算爆满。同时,由于服务必须在更新和服务器故障期间保持可用,这些公司需要有能力快速和持续地部署和更新他们的应用程序,并尽量减少对运行服务的干扰。
云计算的承诺和挑战
几年后,云计算兴起,以应对运行高可用托管服务的挑战。它承诺了可用性和弹性,提供基于消费的定价和无限的容量。然而,这些承诺并不是免费的。它们对应用程序的设计方式提出了一些新的要求。
可用性
云计算通过冗余实现了高可用性。云服务器并不神奇 —— 它们就像你自己的数据中心的服务器一样会失败。但是,当云服务器发生故障时,供应商不会有足够的人力资源来诊断和修复服务器的问题。相反,他们会简单地从他们庞大的服务器池中抽出另一台服务器,并将你的应用程序迁移过来,然后它就会照常运行。
这意味着,在任何时候,你的应用程序都可能被停止,并在一个全新的服务器实例上重新启动。这对你如何设计你的服务提出了一些挑战。例如,如果你积累了内存中的状态,当重启时,这些状态就会丢失。如果你的服务需要很长的时间来初始化,服务的可用性也会受到影响。此外,如果你在本地保存了一些状态,比如把文件写到本地驱动器上,那么当迁移发生时,这些状态就会丢失。迁移状态总是很麻烦的。因此,许多云工作负载管理系统要求应用程序是无状态的,这意味着这些应用程序不会向托管环境保存任何本地状态。
将应用程序位从一个服务器移动到另一个服务器需要时间。如果一个应用程序可以被分解成更小的片段,移动和恢复这些小片段就会更有效率。使用这些较小的服务,有时被称为微服务,正在成为构成应用程序的事实上的方式。微服务通常是松散耦合的,因为当它们的同伴被移动和重启时,它们应该保持功能。
如果你的服务必须是有状态的,这意味着它必须保存一些本地状态,那么这些状态就需要被复制以确保可用性。在这种情况下,通常使用单个写入器而不是多个写入器来避免数据冲突。
弹性
当一项服务的需求超过托管服务器的能力时,有两种可能的解决方案:服务可以被迁移到更强大的服务器上,或者可以部署多个服务的副本来分担工作负荷。后一种方法被称为 “扩展”,是在云中扩展的首选方式。因为云供应商有大量的服务器,理论上服务可以无限地扩展。反过来说,当工作负载减少时,一项服务可以扩展到释放未使用的计算能力。因为你为你在云中的消费付费,如果你的工作负载有季节性的大负荷(如零售企业)或偶尔的高峰(如新闻企业),这种弹性可以成为一个主要的成本节约者。
对于简单的 Web API,扩展是相对简单的。这是因为每个网络请求通常是独立的,而数据操作自然被交易范围所分割。对于这样的 API,扩展可以像在负载平衡器后面添加更多的服务实例一样简单。然而,并不是所有的应用程序都被设计成可扩展的。特别是当一个服务假设它完全控制了系统状态时,该服务的多个实例可能会做出相互冲突的决定。调和这种冲突是一个困难的问题,特别是当长时间的网络分区发生时。这时,系统被说成是有一个 “分裂的大脑”(脑裂),这常常导致不可调和的冲突和数据损坏。
分区是扩展服务的一种有效方式。一个大的工作负载通常可以按照逻辑或物理边界分割成多个分区,例如按照客户(或租户)、业务部门、地理位置、国家或地区。每个分区都是一个较小的系统,可以根据需要扩大或缩小。分区在扩展有状态服务时特别有用。如前所述,一个有状态的服务的状态需要被复制以确保可用性。分区控制了被复制的数据量,并允许平行复制,因为所有的复制操作都是针对分区的。
分区可以是静态的,也可以是动态的。静态分区在路由方面比较容易,因为路由规则可以预先确定。然而,静态分区可能会导致不平衡的分区,这反过来又会导致过度紧张的热点和未充分利用的服务器能力等问题。动态分区使用 一致性哈希 等技术动态地调整分区。动态分区通常可以确保工作负载在各分区之间均匀分布。然而,需要一些分区感知的路由逻辑,将用户流量路由到适当的分区。在动态分区上也很难确保数据分离,而这是一些合规标准所要求的。当你设计你的服务时,你需要根据你的技术和非技术需求来决定分区策略,并坚持下去,因为以后改变分区策略往往是困难的,特别是当你有不断进入的数据流时。
然而,需要一些分区感知的路由逻辑,将用户流量路由到适当的分区。在动态分区上也很难确保数据分离,而这是一些合规标准所要求的。当你设计你的服务时,你需要根据你的技术和非技术需求来决定分区策略,并坚持下去,因为以后改变分区策略往往很困难,特别是当你有不断进入的数据流时。
云原生应用
云原生应用程序被设计为在云环境中运行。一个应用程序要被认为是 “云原生”,它应该具备以下特点。
可自动部署
必要时可以持续地重新部署云原生应用。当启用自动故障转移以确保高可用性时,需要这一功能。虚拟化和容器化允许将应用打包成独立的包,可以部署在不同的主机节点上,而不存在外部依赖的可能冲突。这些包必须是可部署的,无需人工干预,因为故障转移机制可能在任何时候由于不同的条件,如机器故障和计算资源再平衡而被触发。
如前所述,将无状态服务移动到新机器上比移动有状态服务更简单,而且能够快速启动新的服务实例是提高可用性的关键。然而,无状态并不是云原生应用的一个管理要求。分区等技术使有状态的服务实例也能被有效地移动。
组件间的隔离
当其他组件发生故障时,多组件云原生应用中的一个组件应继续运行。尽管该组件可能无法提供所需的功能,但当所有依赖的服务重新上线时,它应该能够恢复到一个完全正常的状态。换句话说,一个失败的组件或一个重新启动的组件不应该在其他组件中引起连带错误。
这种隔离通常是通过明确定义的 API、具有自动退役功能的客户端库和通过消息传递的松散耦合设计的组合来实现。
组件之间的隔离也意味着组件应该是单独可扩展的。基于消耗的模式要求云原生应用的操作者对各个组件的资源消耗进行微调,以便以最小的资源消耗更好地满足需求。这就要求组件在相关组件被扩展时能够适应工作负载的变化。
云原生应用通常由微服务组成(事实上,这两个词经常被认为是同义词)。尽管名字叫微服务,但微服务不一定是小的,这个概念是关于操作的。微服务应用程序是孤立的、可持续部署的、易于扩展的,这使它们非常适合于云环境。
基础设施是无趣的
为了使云平台实现可用性和弹性的承诺,工作负载必须与底层基础设施分离,以便在需要时调动基础设施进行常见的云操作,如故障切换和扩展。基础设施的问题应该是开发人员最后考虑的事情。诸如开放应用模型(OAM,本章稍后讨论)和 Dapr 等项目旨在为开发者提供工具和抽象,使他们能够设计和开发与底层基础设施无关的应用,用于云和边缘部署。
Dapr 和容器
容器化为工作负载提供了轻量级的隔离机制,而 Kubernetes 在集群计算节点上提供了强大的工作负载编排。Dapr 被设计为与容器和 Kubernetes 一起自然工作。然而,正如你在前一章看到的,Dapr 在非容器化环境中也能工作。这样设计的主要原因是为了支持物联网场景和不使用容器的传统场景。例如,在虚拟机或物理服务器上运行的传统系统可以使用 Dapr 进程,它可以被配置为 Windows 服务或 Linux 守护程序。你也可以把 Dapr 运行时作为一个独立的容器来运行,由于它是轻量级的,它可以很容易地被部署到容量较小的设备上。
无论你如何部署 Dapr 边车,边车都会提供本地服务端点,为你的应用程序带来各种功能,你可以利用这些功能,而不需要担心任何基础设施的细节 —— 这就是 Dapr 的关键价值。一个云原生应用程序经常使用各种基于云的服务,如存储服务、秘密存储、认证服务器、消息传递骨干等等。Dapr 将这些服务抽象成简单的接口,这样你的应用程序就可以重新配置,在不同的部署环境中使用不同的服务实现,包括容器化服务和托管服务。
IaaS, PaaS, SaaS 和 Serverless
许多云计算项目一开始就在争论云计算的哪一层 —— IaaS、PaaS 或 SaaS —— 应该是入口点。我们暂时不讨论 SaaS(软件即服务),因为从应用程序的角度来看,使用 SaaS 意味着调用一个托管的 API。这并不影响应用程序本身如何被托管。
IaaS(基础设施即服务)让你对你在云中配置的计算资源有最灵活的控制。因为你是在基础设施层面上工作,你可以以你想要的确切方式定制你的托管环境。你可以选择网络拓扑结构、操作系统、运行时间、框架等等。然后你在指定的基础设施上部署你的应用程序,并像在本地数据中心一样管理它。然而,在基础设施层面的工作使你更难利用云的可用性和弹性功能。例如,尽管你的云供应商可以自动恢复一个失败的虚拟机,并触发你的应用程序的重新部署,但建立环境所需的时间可能很长,造成意外的中断。
PaaS(平台即服务)是比较有主见的。当你遵循 PaaS 平台所选择的编程模型时,许多基础设施的负担就从你的肩上卸下了。Dapr 被设计成可以与不同的 PaaS 平台一起工作。从架构上看,Dapr 只是一组你的应用程序可以使用的服务端点。在某种程度上,它是一个 “本地 SaaS”,你的应用程序可以消费。它并不要求你的应用程序是如何编写的,或者你的应用程序使用哪种框架。
由于 Dapr 是作为一个边车运行的,它可以与任何支持分组容器概念的无服务器平台一起工作。这包括不同类型的管理型 Kubernetes 集群以及支持容器组的 Azure Container Instances(ACI)。要在不支持容器组的无服务器平台上运行,你可以把 Dapr 进程打包成应用容器的一部分。
考虑到这些讨论,我们现在将介绍 Dapr 如何在分布式组件中提供服务调用的抽象性。服务调用功能提供了基本的支持,使微服务应用中的组件能够相互通信。
服务调用
多个服务可以通过 Dapr 边车相互通信,如 图 1-1 所示。当由名为 dapr-a 的 Dapr 边车代表的服务 a 试图调用由名为 dapr-b 的 Dapr 边车代表的服务 b 定义的方法 foo 时,该请求会经过以下步骤:
- 服务
a通过localhost:3500向它自己的 Dapr 边车发送请求(假设边车监听端口为3500),路径为/v1.0/invoke/<target Dapr ID>/method/<target method>(注意,一个服务总是通过localhost向它自己的 Dapr 边车发送调用请求); dapr-a边车会解析dapr-b边车的地址,并将请求转发给dapr-b的调用端点;dapr-b边车调用了服务b的/foo路由。
图 1-1. 通过 Dapr 边车调用服务
名称解析
调用另一个服务所需的第一步是定位该服务。每个 Dapr 边车都由一个字符串 ID 来识别。名称解析的任务是将 Dapr 的 ID 映射到一个可路由的地址。默认情况下,Dapr 在 Kubernetes 上运行时使用 Kubernetes 名称解析机制,而在本地模式下运行时使用多播 DNS(mDNS)。Dapr 也允许其他名称解析器作为一个组件插入到运行时中。
Kubernetes
当你部署一个带有 Dapr 注释的 Pod 时,Dapr 会自动向你的 Pod 注入一个 Dapr 边车容器。它还创建了一个带有 -dapr 后缀的 ClusterIP 服务。为了验证这一点,使用 kubectl 从 Dapr 的样例代码仓库 中部署以下 Pod 清单:
apiVersion: apps/v1kind: Deploymentmetadata:name: pythonapplabels:app: pythonspec:replicas: 1selector:matchLabels:app: pythontemplate:metadata:labels:app: pythonannotations:dapr.io/enabled: "true"dapr.io/id: "pythonapp"spec:containers:- name: pythonimage: dapriosamples/hello-k8s-python
然后你可以使用 kubectl 来查看所创建的服务:
$ kubectl get svc pythonapp-daprNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEpythonapp-dapr ClusterIP 10.0.79.144 <none> 80/TCP,50001/TCP 11m
默认情况下,Kubernetes 使用 CoreDNS 进行名称解析。下面的命令在你的集群中创建一个 busyboxPod,并检查默认的名称解析配置:
$ kubectl apply -f https://k8s.io/examples/admin/dns/busybox.yaml$ kubectl exec busybox cat /etc/resolv.conf
在我们的环境中,即在 Azure Kubernetes Service(AKS)上托管,它们产生了以下输出:
nameserver 10.0.0.10search default.svc.cluster.local svc.cluster.local cluster.localvvdjj2huljtelaqnqfod0pbtwh.xx.internal.cloudapp.netoptions ndots:5
你可以使用以下命令手动查询服务地址:
$ kubectl exec -ti busybox -- nslookup pythonapp-dapr
这个命令在我们的环境中产生了以下输出。你应该在你的环境中得到类似的结果,除了解决的服务 IP。该输出显示了 Dapr 如何将 Dapr ID pythonapp-dapr 解析为要调用的相应服务:
Server: 10.0.0.10Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.localName: pythonapp-daprAddress 1: 10.0.79.144 pythonapp-dapr.default.svc.cluster.local
当 Pod 被扩展时,服务的流量被均匀地分配到实例中 —— 这就是 Dapr 边车扩展的方式。
mDNS
多播 DNS 协议用于通过广播 UDP 数据包将主机名解析为小型网络上的 IP 地址。从本质上讲,每个参与者宣布自己的地址,并根据其同伴宣布的内容更新其 DNS 查找缓存。当以本地模式运行时,Dapr 使用 mDNS 进行名称解析。
当多个 Dapr 边车在同一主机上运行时,它们需要监听不同的 HTTP/gRPC 端口以避免端口冲突。在这种情况下,mDNS 会解析到 Dapr 实例所监听的特定端口。例如,Dapr ID dapr-a 可能被解析到 localhost:3500,而 Dapr ID dapr-d 可能被解析到 localhost:3600。
:::info 在写这篇文章的时候,Dapr 的 mDNS 实现 被限制在一台主机上。有可能在你阅读这篇文字的时候,这个实现已经被扩展到支持同一本地网络上的多台机器。在这之前,Dapr 的本地模式只支持在同一台主机上运行的 Dapr 边车,监听不同的端口。在扩展之后,在同一本地网络上运行的 Dapr 边车将能够通过 Dapr ID 相互寻址。 :::
请求和响应
Dapr 转发所有的请求头以及 HTTP 请求的查询参数,以及所有与 gRPC 请求相关的元数据。尽管服务可以通过 HTTP 或 gRPC 与 Dapr 对话,但 Dapr 的边车总是通过 gRPC 相互通信。当把一个 HTTP 请求转换为 gRP C请求时,所有的 HTTP 头信息都被编码到 gRPC 请求的头信息元数据元素中。
Dapr 支持常见的 HTTP 动词,包括 GET、POST、DELETE 和 PUT。Dapr 使用相互的 TLS 来保障侧设备之间的通信。Dapr 边车通过边车特定的证书相互认证,这些证书根植于集群级 CA(在 Kubernetes 上运行时),或者客户提供的根证书 —— 详见 第 4 章。服务与其相关的边车之间的通信通常不受保护,因为边车被认为与服务处于同一安全域,但你可以通过 Dapr 配置两个服务之间的端到端加密。
并发控制
Dapr 运行时支持一个 max-concurrency 开关。当设置为正值时,它控制了多少个并发请求可以被派发到用户服务。一旦并发请求的数量超过了给定的阈值,额外的请求将被搁置,直到释放出额外的处理能力。这意味着一个客户的请求可能会因为服务繁忙而超时。
服务调用实验
在本节中,我们将进行一个小型的服务调用实验。我们将在这里使用 PHP,但请记住,你可以选择任何你喜欢的语言;你所需要做的就是编写一个简单的 Web 服务器,不需要任何 Dapr 特定的逻辑或库。
创建 PHP 服务
第一步是创建一个简单的 PHP Web 服务。这个服务响应任何请求的路线,并返回请求方法、请求路径和请求头信息:
- 创建一个名为
php-app的新文件夹; - 在
php-app文件夹下,创建一个名为app.php的新 PHP 脚本文件,内容如下: ```php <?php
$method = $_SERVER[‘REQUEST_METHOD’]; $uri = $_SERVER[‘REQUEST_URI’];
$headers = array(); foreach ($SERVER as $key => $value) { if (strpos($key, ‘HTTP‘) == 0 && strlen($key) >5) { $header = strreplace(‘ ‘, ‘-‘, ucwords(str_replace(‘‘, ‘ ‘, strtolower(substr($key, 5))))); $headers[$header] = $value; } }
echo json_encode(array(‘method’ => $method, ‘uri’ => $uri, ‘headers’ => $headers)); ?>
3. 在同一文件夹下,用这些内容创建一个 Docker 文件:```dockerfileFROM php:7.4-cliCOPY . /usr/src/myappWORKDIR /usr/src/myappCMD ["php", "-S", "0.0.0.0:8000", "app.php"]
- 构建并推送 Docker 镜像:
$ docker build -t <image tag> .$ docker push <image tag>
部署 PHP 服务
下一步是创建一个 Kubernetes 部署规范来部署 PHP 服务。还将定义一个 LoadBalancer 服务,这样就可以通过一个公共 IP 访问该服务:
- 创建一个新的
php-app.yaml文件,内容如下: ```yaml kind: Service apiVersion: v1 metadata: name: phpapp labels: app: php spec: selector: app: php ports:- protocol: TCP port: 80 targetPort: 8000 type: LoadBalancer
apiVersion: apps/v1 kind: Deployment metadata: name: phpapp labels: app: php spec: replicas: 1 selector: matchLabels: app: php template: metadata: labels: app: php annotations: dapr.io/enabled: “true” dapr.io/id: “phpapp” dapr.io/port: “8000” spec: containers:
- name: phpimage: <image tag>ports:- containerPort: 8000imagePullPolicy: Always
2. 使用 kubectl 部署该文件。然后获取 `phpapp` 服务的公共 IP:```bash$ kubectl apply -f php-app.yaml$ kubectl get svc phpapp
- 使用浏览器或 Postman 发送一个请求到
http://<你的服务ip>/abc/def?param=123,你应该得到一个与下面类似的 JSON 文档:{"method": "GET","uri": "\/abc\/def?param=123","headers": {...}}
暴露 -dapr 服务
如前所述,当你部署一个带有 Dapr 注释的 Pod 时,Dapr 会创建一个 -dapr ClusterIP 服务。在这个实验中,你将编辑该服务,将其类型改为 LoadBalancer,这意味着它将通过负载均衡器分配一个公共 IP:
$ kubectl edit svc phpapp-dapr
将此文件中的 ClusterIP 替换为 LoadBalancer,然后保存文件。kubectl 应该报告说,服务已经被编辑了:service/phpapp-dapr edited。
等待一个公共 IP 被分配给 phpapp-dapr。然后就可以通过暴露的 Dapr 服务来调用 PHP 服务:http://<dapr service IP>/v1.0/invoke/phpapp/method/foo。
你可以尝试不同的请求路径、参数和请求头的组合。你将能够观察到 Dapr 如何将元数据转发到 PHP 服务。
通用命名空间
在写这篇文章的时候,我们正在设计一种新的 Dapr 能力,暂且称为通用名称空间(Universal Namespace)。到目前为止,Dapr 的名字解析和通信只在单个集群中工作。然而,我们希望扩展 Dapr,使其支持跨多个集群的名称解析和通信。
通用名称空间的想法是允许用户用集群标识符来后缀服务名称,这样不同集群上的服务就可以通过 <服务名称>.<集群标识符> 这样的名称来相互寻址。例如,要调用 cluster-1 上的 service-1 的方法 foo,客户只需向 Dapr 端点发送一个 POST 请求,地址是 http://localhost:3500/v1.0/invoke/service-1.cluster1/foo。
由于 Dapr 侧设备经常作为 ClusterIP 服务被暴露,我们计划引入一个新的 Dapr 网关服务,它可以与一个公共 IP 相关联。通过 DNS 进行的名称解析将解析到网关服务地址,而网关将为本地服务的请求把关。当然,如果你选择用公共 IP 暴露 Dapr 服务,另一个集群上的 DNS 记录可以被更新,直接将流量路由到目标服务,而不需要通过网关。
为了保证集群间的通信安全,集群的根证书(或共享根证书)被交换,以进行相互的 TLS 认证。
:::info 我们将在 第 7 章 中更多地讨论这个问题和其他即将推出的 Dapr 功能。 :::
发布/订阅
微服务架构提倡松散耦合的服务。当一个服务通过 Dap r调用另一个服务时,它不需要自己去解决物理服务地址。相反,它可以通过对应的 Dapr ID 简单地引用另一个服务。这种设计允许在服务放置方面有很大的灵活性。然而,调用服务仍然需要知道目标服务的确切名称。
许多现代应用程序由多个服务组成,这些服务是由不同的团队开发和操作的。特别是在大型企业环境中,不同的服务可能有不同的生命周期,一个服务可能被一个完全不同的服务所取代。维护这些服务之间的直接联系可能很困难,甚至不可能。因此,我们需要一些方法来保持服务的松散耦合,但有效地集成为一个完整的应用程序。
为了实现更好的松散耦合,Dapr 支持基于消息的集成模式。在我们深入了解 Dapr 如何提供消息支持之前,让我们回顾一下这种方法的好处。
基于消息的集成的好处
基于消息的集成允许服务通过一个消息传递骨干网来交换消息,如 图 1-2 所示。所有的服务不是直接向对方发送请求,而是通过消息传递骨干网交换消息来相互通信。这种额外的间接性带来了许多理想的特征,在下面的小节中进行了总结。
图 1-2. 基于消息的集成
缓解性能差异
不同的服务有不同的吞吐量。如果所有的服务都是通过直接调用连锁起来的,那么系统的整体吞吐量就由连锁中最慢的服务决定。这通常是不可接受的。例如,当后端服务处理订单时,网络服务应该保持响应以接受新的订单,这可能需要更长的时间。
在这种情况下,前端服务可以接受一个新的订单,把它放到一个处理队列中,并准备接受下一个订单。同时,后端服务可以以自己的速度接取和处理订单,而不影响前端服务的响应能力。此外,后端服务可以独立扩展,以便更快地耗尽处理队列。
先进的排队功能,如重复数据删除(删除重复的请求)、优先级排队(将优先级较高的请求撞到队列的前面)和批处理(将请求合并到一个事务中),可以帮助进一步优化系统。
提升可用性
基于消息的集成在某些情况下也可以帮助提高系统的可用性。例如,当后端因维护或升级而停机时,前端可以继续接收新的请求并排队。
有了全球冗余的消息传递主干,你的系统甚至可以在区域性故障中幸存下来,因为即使你所有的系统组件发生故障,排队的请求仍然可用。当系统重新启动时,它们可以被接走。
一些排队系统允许在不从队列中移除工作项目的情况下将其检出。一旦工作项目被签出,处理器就会有一个时间窗口来处理它,然后从队列中移除。如果处理者未能在规定的时间内完成,该工作项目将被提供给其他人签出。这是一个 at-least-once 消息传递的实现,确保一个工作项目至少被处理一次。
灵活的集成拓扑结构
你可以通过基于消息的集成实现各种服务拓扑结构:发布/订阅、突发到云、基于内容的路由、分散收集、竞争性消费者、死信通道、消息代理等等。
在发布/订阅模式中,发布者向订阅者订阅的主题发布消息。图 1-3 说明了你可以用发布/订阅实现的一些集成拓扑结构。
图 1-3. 发布/订阅可以整合的模式
发布/订阅的主要好处之一是,发布者和订阅者是完全解耦的(好吧,确切地说,他们仍然是由消息格式耦合的,但我们在这里只是在谈论拓扑结构)。发布者从不关心它所发布的主题有多少订阅者,也不关心订阅者是谁。这同样适用于订阅者。这意味着发布者和订阅者可以在任何时候被演化或替换,而不影响彼此。这是相当强大的,特别是当多个团队试图一起工作以提供一个复杂的应用程序时。
使用 Dapr 来发布/订阅
要订阅主题,你的应用程序应该向 Dapr 边车发送一个 GET 请求,并将主题列表和相应的路由作为一个 JSON 数组,如以下 Node.js 示例代码所示:
app.get('/dapr/subscribe', (_req, res) => {res.json([{ topic: "A", route: "A" },{ topic: "B", route: "B" }]);});
然后,当发布方向订阅的主题发布内容时,边车会通过 POST 请求向你发送事件:
app.post('/A', (req, res) => {console.log("A: ", req.body);res.sendStatus(200);});
要向一个主题发布,你的应用程序应该向 Dapr 边车上的 /publish/<topic> 端点发送一个 POST 请求,并将要发送的消息作为 POST 主体,包装成一个 CloudEvent(更多内容见下节):
const publishUrl = `http://localhost:3500/v1.0/publish/<topic>`;request( { uri: publishUrl, method: 'POST', json: <message> } );
:::info 你可以从 Dapr 的 样例代码仓库 获得完整的发布/订阅样例的源代码。 :::
发布/订阅如何工作
发布/订阅需要一个消息传递的骨干。然而,正如介绍中所解释的,Dapr 的设计原则之一是不要重新发明车轮。因此,Dapr 并没有创建一个新的消息传递骨干网,而是被设计成可以与许多流行的消息传递骨干网集成,包括 Redis Streams、NATS、Azure Service Bus、RabbitMQ、Kafka 等等(完整的列表见 Dapr 资源库)。
Dapr 使用 Redis Streams 作为默认的消息传递骨干。Stream 是 Redis 5.0 引入的一种仅附加的数据结构。你可以使用 XADD 将数据元素添加到 Stream 中,你也可以使用像 BLPOP 这样的 API 来检索数据。每个订阅者都被分配了自己的消费者组,这样他们就可以并行地处理自己的消息副本。
Dapr 遵循 CloudEvents v1.0 规范。在撰写本文时,CloudEvents 是一个云原生计算基金会(CNCF)的沙盒级项目;它的目标是创造一种一致的方式来描述事件,可以在事件生产者和消费者之间使用。Dapr 实现了所有必要的属性:id、source、specversion 和 type。它还实现了可选的 datacontenttype 和 subject 属性。表 1-1 显示了这些属性是如何被填充的。
表 1-1. CloudEvents 属性
| 属性 | 属性值 |
|---|---|
data |
信息有效载荷 |
datacontenttype |
application/cloudevent+json, text/plain, application/json |
id |
UUID |
source |
发送者的 Dapr ID |
specversion |
1.0 |
subject |
订阅的主题 |
type |
com.dapr.event.sent |
Dapr 组件
Dapr 将不同的功能分组,如发布/订阅、状态存储、中间件和秘密,作为组件(又称构建块)。它提供了默认的实现,但如果你愿意,你可以插入其他的实现。
一个组件由一个元数据文件定义,该文件为 Kubernetes CRD 格式。例如,下面的文件定义了一个 Redis 状态存储,很有创意地命名为 statestore:
apiVersion: dapr.io/v1alpha1kind: Componentmetadata:name: statestorespec:type: state.redismetadata:- name: redisHostvalue: <YOUR_REDIS_HOST_HERE>:6379- name: redisPasswordvalue: <YOUR_REDIS_KEY_HERE>
当在本地模式下运行时,Dapr 会在你的 Dapr 安装文件夹下寻找一个 ./components 文件夹(可以通过 --component-path 来覆盖),并加载在该文件夹下发现的所有组件文件。当在 Kubernetes 模式下运行时,这些文件应该作为 CRD 部署到你的 Kubernetes 集群。例如,为了应用前面的状态存储定义,你将使用:
$ kubectl apply -f ./redis.yaml
下面是一个 OAuth 2.0 授权中间件的定义:
apiVersion: dapr.io/v1alpha1kind: Componentmetadata:name: oauth2spec:type: middleware.http.oauth2metadata:- name: clientIdvalue: "<your client ID>"- name: clientSecretvalue: "<your client secret>"- name: scopesvalue: "https://www.googleapis.com/auth/userinfo.email"- name: authURLvalue: "https://accounts.google.com/o/oauth2/v2/auth"- name: tokenURLvalue: "https://accounts.google.com/o/oauth2/token"- name: redirectURLvalue: "http://dummy.com"- name: authHeaderNamevalue: "authorization"
该文件定义了一个名为 oauth2 的 middleware.http.oauth2 类型的组件。你可以通过定义一个自定义配置,将多个中间件组件组合成一个自定义管道,如接下来所述。
Dapr 配置
Dapr 边车可以用自定义配置启动,在本地模式下是一个文件,在 Kubernetes 模式下是一个配置对象。Dapr 配置再次使用 Kubernetes CRD 语义,因此同一配置可以在本地模式和 Kubernetes 模式下使用。
在撰写本文时,你可以使用 Dapr 配置来定制分布式跟踪,并创建自定义管道。该模式可能会在未来的版本中被扩展;请查阅在线 Dapr 文档 以了解最新的细节。下面的例子显示了一个配置,它启用了分布式跟踪并定义了一个带有 OAuth 中间件的自定义管道。
apiVersion: dapr.io/v1alpha1kind: Configurationmetadata:name: pipelinespec:tracing:samplingRate: "1"httpPipeline:handlers:- type: middleware.http.oauth2name: oauth2
要应用这个配置,请使用 kubectl(假设文件名是 pipeline.yaml):kubectl apply -f ./pipeline.yaml。
:::info 在写这篇文章的时候,你需要重新启动你的 Pod 来接收新的配置变化。 :::
要应用自定义配置,你需要在你的 Pod 规格中添加 dapr.io/config 注解:
apiVersion: apps/v1kind: Deploymentmetadata:name: echoapplabels:app: echospec:replicas: 1selector:matchLabels:app: echotemplate:metadata:labels:app: echoannotations:dapr.io/enabled: "true"dapr.io/id: "echoapp"dapr.io/port: "3000"dapr.io/config: "pipeline"spec:containers:- name: echoimage: <your Docker image tag>ports:- containerPort: 3000imagePullPolicy: Always
自定义流水线
在自定义流水线中定义的中间件在请求侧按照它们在自定义配置文件中出现的顺序应用,在响应侧则按照相反的顺序应用。如 图 1-4 所示,一个中间件实现可以选择参与入口管道、出口管道或两者。
除了自定义的中间件,Dapr 总是在链的顶端加载两个中间件:分布式跟踪中间件和 CORS 中间件。我们将在下一节中讨论分布式跟踪。CORS 是由一个运行时开关(allowed-origins)配置的,它包含一个用逗号分隔的允许请求来源的列表。在未来的版本中,这个开关可能被合并到 Dapr 配置中。
图 1-4. 一个自定义流水线
自定义流水线实验
在下面的实验中,你将用一个奇怪的中间件创建一个自定义流水线,将所有请求体改为大写。我们创建这个中间件的目的是为了测试;你可以用它来验证你的自定义流水线是否已经到位。
由于到目前为止,我们在每个样本中都使用了不同的编程语言,我们将在这个练习中改用 Rust。你当然可以选择不同的语言和网络框架来编写应用程序,它只是简单地把收到的东西回传过来。下面的讨论假设你的机器上已经安装并配置了 Rust。
创建 Rust 应用
首先,让我们来创建 Rust 应用:
使用
cargo创建一个新的 Rust 项目:$ cargo new rust-web$ cd rust-web
修改你的
Cargo.toml文件以包括必要的依赖性:[dependencies]actix-web = "2.0"actix-rt = "1.0.0"actix-service = "1.0.0"serde = "1.0"bytes = "0.5.2"json = "*"
修改
src/main.rs文件,使其包含以下代码: ```rust use actix_web::{ web, App, Error, HttpResponse, HttpServer, }; use json::JsonValue; use bytes::{Bytes};
async fn echo(body: Bytes) -> Result
[actix_rt::main]
async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .data(web::JsonConfig::default().limit(4096)) .service(web::resource(“/echo”).route(web::post().to(echo))) }) .bind(“127.0.0.1:8088”)? .run() .await }
4. 启动该应用程序,并确保其工作:`carge run`;4. 使用一个网络测试工具,如 Postman,向网络服务器发送一个带有 JSON 有效载荷的 `POST` 请求。你应该看到响应中输出的有效载荷。<a name="sxpnt"></a>### 制作一个自定义流水线在这部分练习中,你将创建两个清单文件,一个 Dapr 配置文件和一个中间件定义文件:1. 在你的应用程序文件夹下创建一个新的 `components`文件夹;1. 在这个文件夹下添加一个新的 `uppercase.yaml` 文件,内容如下。该文件定义了一个 `middleware.http.uppercase` 中间件,它没有任何元数据:```yamlapiVersion: dapr.io/v1alpha1kind: Componentmetadata:name: uppercasespec:type: middleware.http.uppercase
- 在你的应用程序文件夹下定义一个
pipeline.yaml文件,包含以下内容。该配置定义了一个具有一个middleware.http.uppercase中间件:apiVersion: dapr.io/v1alpha1kind: Configurationmetadata:name: pipelinespec:httpPipeline:handlers:- type: middleware.http.uppercasename: uppercase
测试流水线
要在本地测试应用程序,从你的应用程序文件夹中启动带有 Dapr 边车的 Rust 应用程序:
$ dapr run --app-id rust-web --app-port 8088 --port 8080 --config ./pipeline.yaml cargo run
然后使用 Postman 向以下地址发送一个带有 JSON 有效载荷的 POST 请求:http://localhost:8080/v1.0/invoke/rust-web/method/echo。你应该看到,响应的 JSON 文档只包含大写字母。
:::info 在 Kubernetes 上的测试被留作练习,供感兴趣的读者参考。 :::
OAuth 2.0 授权
OAuth 2.0 授权中间件是Dapr提供的中间件组件之一。它启用了 OAuth 2.0 授权代码授予流程。具体来说,当 Dapr 边车收到一个启用了 OAuth 2.0 中间件的请求时,会发生以下步骤:
- Dapr 边车会检查授权令牌是否存在。如果没有,Dapr 会将浏览器重定向到配置的授权服务器。
- 用户登录并授予对应用程序的访问权。该请求被重定向到 Dapr 边车,并附上一个授权码。
- Dapr 把授权码换成一个访问令牌。
- 边车将令牌注入一个配置好的头,并将请求转发给用户应用程序。
:::info 在撰写本文时,中间件不支持刷新令牌。 :::
图 1-5 说明了如何配置 OAuth 中间件以向应用程序提供 OAuth 授权。
图 1-5. 带有 OAuth 中间件的自定义流水线
OAuth 是一个流行的协议,被许多授权服务器所支持,包括 Azure AAD、Facebook、Fitbit、GitHub、Google APIs、Slack、Twitter 等等。为了与这些授权服务器合作,你需要在你想使用的服务器上注册你的应用程序。不同的服务器提供不同的注册体验。在最后,你需要收集以下信息:客户端 ID,客户端密钥,作用域,授权 URL,令牌 URL。
一旦你收集了所需的信息,你可以定义 OAuth 中间件和你的自定义管道。中间件遵循 OAuth 流程,并将访问令牌注入配置的 authHeaderName 头中。
编写自定义中间件
Dapr 的 HTTP 服务器使用 FastHTTP,所以 Dapr 的 HTTP 中间件也被写成 FastHTTP 处理程序。Dapr 定义了一个简单的中间件接口,它由一个 GetHandler 方法组成,该方法返回一个 fasthttp.RequestHandler:
type Middleware interface {GetHandler(metadata Metadata) (func(h fasthttp.RequestHandler) fasthttp.RequestHandler, error)}
你的实现应该返回一个函数,接收下游请求处理程序并返回一个新的请求处理程序。然后你可以在下游处理程序周围插入入站或出站逻辑,如以下代码片段所示:
func GetHandler(metadata Metadata) fasthttp.RequestHandler {return func(h fasthttp.RequestHandler) fasthttp.RequestHandler {return func(ctx *fasthttp.RequestCtx) {//inbound logich(ctx) //call the downstream handler//outbound logic}}}
你的自定义中间件,像其他自定义组件一样,应该贡献给 Dapr 的 components-contrib 仓库,在 /middleware 文件夹下。然后你需要针对 主仓库 提交另一个拉动请求,以注册新的中间件类型。你还需要按照 注册程序 来注册你的中间件。
:::info 这里描述的注册过程在未来的版本中可能会被取消。在撰写本文时,只支持 HTTP 中间件,但我们计划在未来的版本中也支持 gRPC 中间件。 :::
分布式追踪
诊断分布式应用中的问题是具有挑战性的。你需要从多个服务中收集痕迹,并在日志条目中建立关联,以建立完整的调用链。这是一项艰巨的任务,特别是在一个有几十个甚至几百个服务的大规模系统中,每秒钟有数百万个事务。
Dapr 使你的应用拓扑结构更加复杂,因为它在所有参与的服务周围插入了边车。另一方面,由于 Dapr 注入了边车,它可以提供诊断方面的帮助。由于所有的流量都流经 Dapr 边车,Dapr 可以自动建立请求之间的关联,并将分布式跟踪收集到一个一致的视图中。Dapr 利用 OpenTelemetry 来实现这一点。
使用 Dapr 分布式跟踪的另一个好处是,你的服务代码不需要被检测,也不需要包含任何跟踪库。所有的方法调用都由 Dapr 自动追踪。因为追踪配置是完全独立的,改变或重新配置追踪系统不会影响你的运行应用程序。在写这篇文章的时候,Dapr 并没有向用户服务暴露一个追踪的 API,以满足额外的追踪需求。然而,在 Dapr 中暴露这样一个 API 已经被讨论过了。
追踪中间件
Dapr 分布式跟踪是一个可以插入任何 Dapr 边车的中间件。它在 HTTP 和 gRPC 协议下工作。该中间件是围绕两个关键概念建立的:跨度(Span)和相关 ID(Correlation ID)。
跨度(Span)
一个跨度代表一个单一的操作,比如一个 HTTP 请求或一个 gRPC 调用。一个跨度可以有一个父跨度和多个子跨度。一个没有父跨度的跨度被称为根跨度。一个跨度由一个跨度 ID 来识别。Dapr 跟踪两种跨度:服务器跨度和客户端跨度。当一个服务收到一个请求时,Dapr 创建一个服务器跨度。当一个服务调用另一个服务时,Dapr 创建一个客户端跨度。这些跨度类型帮助追踪系统追踪服务调用中的不同角色。
Dapr 从请求路径中提取方法名称并将其作为跨度的名称,它使用 “跨度状态” 来记录调用结果。Dapr 将 HTTP 响应代码映射到一些跨度状态,如 表 1-3 所总结的。
表 1-4. 跨度状态映射
| HTTP 状态码 | 跨度(Span)状态 |
|---|---|
| 200 | OK |
| 201 | OK |
| 400 | 无效的参数 |
| 403 | 权限不允许 |
| 404 | 未找到 |
| 500 | 内部错误 |
| 其它 | 按原样记录 |
相关 ID
Dapr 使用一个相关的 ID 来跟踪多个服务的调用链。当一个请求进来时,Dapr 在 HTTP 请求头或 gRPC 元数据中搜索一个 X-correlation-ID 标头。如果该 ID 存在,Dapr 将新的追踪跨度与现有的相关 ID 相联系。如果不存在,Dapr 认为该请求是一个新的由客户发起的请求,并开始一个新的调用链。
导出器
在写这篇文章的时候,Dapr 使用 OpenCensus,它正在被合并到 OpenTelemetry 中,用于收集分布式跟踪。OpenCensus 支持导出器的概念,它可以将跟踪和指标发送到不同的跟踪后端。出口器是 OpenCensus 的扩展点,它们是 Dapr 用来连接不同后端系统(如 Zipkin 和 Azure Monitor)的东西。在撰写本文时,Dapr 支持 OpenTelemetry 本地导出器、Zipkin 导出器和一个用于测试的字符串导出器。
配置
导出器被定义为 Dapr 组件,你可以用 Dapr 配置来配置整个追踪行为。
用 Zipkin 进行追踪
Zipkin 是一个流行的分布式跟踪系统。下面的演示告诉你用 Zipkin 配置 Dapr 分布式跟踪的步骤。
创建配置文件
你需要定义两个工件,一个 Zipkin 导出器组件和一个启用了追踪功能的 Dapr 配置:
创建一个
zipkin.yaml文件。每个导出器组件都有一个enabled属性,可以用来打开或关闭导出器。其他属性是针对导出器的。Zipkin 导出器需要一个exporterAddress属性,它指向 Zipkin API 端点:apiVersion: dapr.io/v1alpha1kind: Componentmetadata:name: zipkinspec:type: exporters.zipkinmetadata:- name: enabledvalue: "true"- name: exporterAddressvalue: "http://zipkin.default.svc.cluster.local:9411/api/v2/spans"
创建一个名为
tracing.yaml的 Dapr 配置文件。该配置包含一个samplingRate开关,用来控制对跟踪的采样频率(将其设置为"0"则禁止跟踪):apiVersion: dapr.io/v1alpha1kind: Configurationmetadata:name: tracingspec:tracing:samplingRate: "1"
部署一个 Zipkin 实例
使用以下命令,在本地使用 Docker 容器部署 Zipkin 实例:
$ docker run -d -p 9411:9411 openzipkin/zipkin
要在你的 Kubernetes 集群上部署 Zipkin 实例,使用这些命令:
$ kubectl run zipkin --image openzipkin/zipkin --port 9411$ kubectl expose deploy zipkin --type ClusterIP --port 9411
在 Kubernetes 上启用和查看追踪功能
按照这些步骤,在 Kubernetes 上启用追踪功能:
应用导出器组件和 Dapr 配置:
$ kubectl apply -f tracing.yaml$ kubectl apply -f zipkin.yaml
修改你的 Pod 清单,包括一个使用自定义 Dapr 配置的注释:
annotations:dapr.io/config: "tracing"
部署或重启你的 Pod,并像往常一样操作你的服务。追踪数据会被自动收集并发送到 Zipkin 端点;
- 你可以使用 Zipkin 用户界面来查看追踪数据。要访问用户界面,你可以通过 kubectl 设置端口转发,然后使用
http://localhost:9411来查看追踪数据。$ kubectl port-forward svc/zipkin 9411:9411
在本地启用和查看追踪功能
要在本地启用追踪功能,请遵循以下步骤:
将本地 Zipkin 实例作为 Docker 容器启动:
$ docker run -d -p 9411:9411 openzipkin/zipkin
修改
zipkin.yaml文件以指向本地 Zipkin 端点:apiVersion: dapr.io/v1alpha1kind: Componentmetadata:name: zipkinspec:type: exporters.zipkinmetadata:- name: enabledvalue: "true"- name: exporterAddressvalue: "http://localhost:9411/api/v2/spans"
在你的应用程序文件夹下创建一个
components文件夹,并将zipkin.yaml文件移至该文件夹;使用下面的命令,用 Dapr 边车启动你的服务:
$ dapr run --app-id mynode --app-port 3000 --config ./tracing.yaml <command to launch your service>
像往常一样使用你的服务。然后你可以使用 Zipkin 用户界面查看追踪数据,网址是
http://localhost:9411。
用 Azure Monitor 追踪
在撰写本文时,Dapr 用户是一个本地转发器(Local Forwarder),它收集 OpenCensus 的跟踪和遥测数据,并将其转发给 Application Insights。Dapr 提供了一个预先构建的容器,daprio/dapr-localforwarder,以促进配置过程。一旦你运行了这个容器,你就可以按照与配置 Zipkin 类似的步骤,将本地转发器配置为 Kubernetes 集群上的 ClusterIP 服务。一旦本地转发器配置完毕,在你的本地输出器配置文件中输入本地转发器代理端点:
apiVersion: dapr.io/v1alpha1kind: Componentmetadata:name: nativespec:type: exporters.nativemetadata:- name: enabledvalue: "true"- name: agentEndpointvalue: "<Local Forwarder address, for example: 50.140.60.170:6789>"
然后你可以使用丰富的 Azure Monitor 功能来查看和分析你收集的跟踪数据。图 1-6 显示了 Azure Monitor 用户界面的一个例子,显示了一个有多个服务相互调用的应用。
图 1-6. 在 Azure Monitor 中查看追踪数据
你可以同时部署多个导出器。Dapr 将追踪数据转发到所有导出器。
服务操作
作为一个分布式编程模型和运行时,Dapr 并不关注服务操作。然而,Dapr 被设计为与现有的 Kubernetes 工具链和新的开源项目(如 OAM)一起工作,以管理由多个服务组成的应用程序。本节并不是要提供全面的介绍,而是要作为相关资源的索引。
服务部署和升级
当在 Kubernetes 上运行时,Dapr 使用边车注入器,自动将 Dapr 边车容器注入注有 dapr.io/enabled 属性的 Pod 中。然后,你可以像往常一样,使用常见的 Kubernetes 工具(如kubectl)来管理你的 Pod。例如,下面的 YAML 文件描述了一个带有一个独立容器和一个负载平衡服务的部署(这个例子来自分布式计算器样本,你可以在 Dapr 的样例仓库 找到)。
kind: ServiceapiVersion: v1metadata:name: calculator-front-endlabels:app: calculator-front-endspec:selector:app: calculator-front-endports:- protocol: TCPport: 80targetPort: 8080type: LoadBalancer---apiVersion: apps/v1kind: Deploymentmetadata:name: calculator-front-endlabels:app: calculator-front-endspec:replicas: 1selector:matchLabels:app: calculator-front-endtemplate:metadata:labels:app: calculator-front-endannotations:dapr.io/enabled: "true"dapr.io/id: "calculator-front-end"dapr.io/port: "8080"spec:containers:- name: calculator-front-endimage: dapriosamples/distributed-calculator-react-calculatorports:- containerPort: 8080imagePullPolicy: Always
你可以使用 kubectl 来部署:kubectl apply -f calculator-front-end.yaml。
一旦创建了部署,你可以使用 kubectl 将其扩展到多个副本:
$ kubectl scale deployment calculator-front-end --replicas=3
:::info 关于管理 Kubernetes 部署的更多信息,请查阅 文档。 :::
OAM
开放应用模型(OAM)是一个开源项目,旨在为云原生应用提供一种平台无关的建模语言。OAM 描述了由多个相互连接的组件组成的应用程序的拓扑结构。它关注的是应用的拓扑结构,而不是单个服务的编写方式。Dapr 深入到更深的层次,为云原生应用提供一个通用的编程模型和支持的运行时。正如你在本章前面看到的,Dapr 可以处理服务名称解析和调用路由。同时,Dapr 可以被配置为与现有的服务网格系统一起使用,以提供服务间微调的流量控制。图 1-7 显示了 OAM 的逻辑拓扑结构、Dapr 路由和服务网格策略是如何相互叠加的。
图 1-7. OAM 和 Dapr 之间的关系
OAM 允许你为每个组件附加特性,以控制诸如缩放等行为。例如,下面的手动扩展特性将一个前端组件扩展到五个副本:
apiVersion: core.oam.dev/v1alpha1kind: ApplicationConfigurationmetadata:name: custom-single-appannotations:version: v1.0.0description: "Customized version of single-app"spec:variables:components:- componentName: frontendinstanceName: web-front-endparameterValues:traits:- name: ManualScalerproperties:replicaCount: 5
你可以将多个组件归入一个作用域(Scope),你可以通过应用配置(Application Config)将作用域、组件和特性联系起来。这种设计背后的核心原理是关注点的分离。OAM 的目标是为开发者提供一种独立于任何基础设施关注点的描述应用程序的方法,为应用程序操作员提供一种配置应用程序以满足业务需求的方法,并为基础设施操作员提供一种描述如何在特定平台上实现所需拓扑和配置的方法。
一个常见的组件形式是作为 Kubernetes Pod 部署的容器。这意味着你可以将 Dapr 注释添加到你的组件中,Dapr 边车注入器可以将 Dapr 边车注入你的 Pod 中。
OAM 和 Dapr 的结合为编写平台无关的应用程序提供了完整的解决方案 —— OAM 提供了平台无关的建模语言,而 Dapr 为状态、服务调用、发布/订阅、安全和绑定提供了抽象的通用 API。在撰写本文时,OAM 和 Dapr 都在积极开发中。预计在未来的版本中会有更多的集成被引入。
为什么我们相信编写平台无关的应用程序是重要的?因为我们设想了一个无处不在的计算的未来,其中计算发生在数据的背景中。一个应用程序应该适应数据的存在。这意味着应用程序需要在不同的云平台和企业内部系统上工作,以及在跨越云和边缘的混合环境中工作。
总结
这一章详细介绍了 Dapr 如何使服务通过直接调用和消息传递来相互沟通。你还看到了 Dapr 是如何通过中间件来构建定制的处理流水线的。你可以将多个中间件连接起来,形成一个自定义的流水线,可以拦截和转换入口流量或出口流量。
Dapr 提供了对 HTTP 和 gRPC 协议的分布式跟踪的内置支持。分布式跟踪是分布式框架的一个强制性功能,可以有效地诊断分布式应用。Dapr 利用 OpenTelemetry 与各种追踪后端进行整合。
OAM 和 Dapr 旨在为编写平台无关的云原生应用程序提供一个完整的解决方案。OAM 提供了一种平台无关的建模语言来描述应用拓扑结构。Dapr 提供了基于 HTTP/gRPC 的 API,抽象了一些常见的任务,如服务名称解析和调用、发布/订阅、状态管理、安全管理和与外部服务的绑定。
在下一章,我们将介绍 Dapr 状态 API。
