:::info 免责声明:本章中的观点代表了作者的个人意见,是基于与一些社区成员和 Dapr 团队的讨论。它们不应该被当作 Dapr 未来或计划中的功能的路线图。 :::
Dapr 仍然是一个年轻的项目。从它的创建开始,它的目标就是成为一个开放的、中立的、有帮助的、非侵入性的运行时间。Dapr 的未来将由整个 Dapr 社区的集体努力来塑造。我们鼓励每个人加入这个社区,并贡献新的想法和代码。
作为最早的两个开发者,本书的作者有幸与许多来自不同角度的人讨论了 Dapr 的未来。本章的目标是捕捉一些已经分享的想法,并希望能激发新的想法和新的应用场景。
本章将关于 Dapr 未来的想法组织成几个大的类别:能力交付、行为体、边缘计算等等。对于每一个类别,它首先提出了基本的理由或思路,然后介绍了一些由这些想法激发的具体功能。在所有情况下,这些只是 Dapr 正在或可能走向的几个方向的例子。
能力交付
正如你现在所知道的,Dapr 使用一个边车架构来为分布式应用提供普遍需要的能力。图 7-1 中转载的来自 Dapr 资源库 的构件(Building Block)介绍图最能说明这一点。
图 7-1. Dapr 构件
随着 Dapr 的发展,构建模块的数量也在增加。例如,Secret 构件在本书写作前几个月才被引入。
到目前为止,一直是由 Dapr 核心团队决定在 Dapr 运行时中包含哪些构建模块。然而,让社区引入更多的构件是合乎逻辑的。而且,允许客户只加载他们需要的构建模块,而不是像当前 Dapr 实现中那样总是预加载所有的模块,这也是可取的。
为了使 Dapr 成为一个能力交付工具,我们只需要一些适度的扩展,我们将在下一节讨论。
架构
当 Dapr 边车启动时,它会读取并加载所有配置的组件,如状态存储和消息总线。然后,它为每个组件设置了一套路由,如下面的代码片段所示,它设置了状态路由:
func (a *api) constructStateEndpoints() []Endpoint {return []Endpoint{{Methods: []string{http.Get},Route: "state/<storeName>/<key>",Version: apiVersionV1,Handler: a.onGetState,},{Methods: []string{http.Post},Route: "state/<storeName>",Version: apiVersionV1,Handler: a.onPostState,},{Methods: []string{http.Delete},Route: "state/<storeName>/<key>",Version: apiVersionV1,Handler: a.onDeleteState,},}}
我们可以使用一个类似的机制来加载能力包。尽管我们可以为要加载的新组件实现类似的逻辑,但我们可以考虑通过支持一些标准的 API 描述,如 OpenAPI 规范,来自动生成路由。能力包既可以作为自我托管的包被加载到 Dapr 边车进程中,也可以被配置为指向远程托管的 API 服务器,如 图 7-2 所示。如图所示,在远程 API 的情况下,本地 API 代理(可以由 OpenAPI 工具自动生成)被配置为提供功能路由并将请求代理到远程服务器。
图 7-2. Dapr 能力包
我们还可以用 API 代理做更多有趣的事情。例如,代理可以维护一个本地缓存,并直接从缓存中提供重复的请求,只把新的请求送到远程服务器上。这个额外的缓冲层有几个好处:
- 它通过直接从缓存中提供缓存结果,提供更好的性能。
- 它减少了对远程 API 的调用数量。这减少了服务消耗的成本,同时也避免了服务器可能的节流。
- 当远程 API 不可用时,它有助于维持系统的可用性。
这很好,但我们可以更进一步。想象一下,这个能力包有相关的元数据,看起来像这样:
metadata:name: object-detectionoffers:- name: azure-cognitive-servicetype: OpenAPIreference: http://...- name: yolotype: Containerreference: docker.io/some-image
正如你所看到的,清单定义了不同形式的同一能力的多种提供:远程 API 或本地容器。在这种情况下,API 代理可以做的是在远程 API 不可用时返回到本地容器实例。这在架构上很重要 —— 应用程序可以基于某些能力建立逻辑模型,而不需要担心这些能力在哪里以及如何交付,因为一切都隐藏在 Dapr 路由后面。当 Dapr 被扩展为任意能力的通用交付工具时,它可以支持许多有趣的场景,接下来我们将简要地总结一下。
应用场景
使用 Dapr 给应用程序带来的能力不仅仅是一种服务于 API 的花哨方式。该架构实现了一些相当强大的场景。我们将在本节中探讨其中的一些。
分布式 API 管理
许多企业使用各种云服务来支持自己的应用程序。通常很难跟踪所有的服务订阅或加强对服务消费的策略控制,特别是当涉及到来自多个云供应商的服务时。因此,许多企业使用一个集中的 API 管理服务,代理所有的云服务调用。然后他们在 API 管理服务上应用策略来控制服务消费。
Dapr 提供了一种不同的 API 管理方法。有了 Dapr 边车,API 代理被分布,并与应用程序代码放在一起。与集中式代理相比,这是一个更有伸缩性的架构。由于 Dapr 边车在同一个集群中共享配置对象,你仍然可以通过把策略放到 Dapr 配置中来集中管理你的 API 政策。此外,通过利用 Dapr 的分布式跟踪能力,你仍然可以获得所有 API 消耗的单一功能视图。
当你把分布式 API 管理能力与本地缓存和自我托管能力相结合时,你就有了一个高效、可扩展的 API 管理解决方案,而没有任何可能成为瓶颈的集中式部分。
计算推动力
一种边缘计算模式是在适当的时候将计算从云端推到边缘。这种模式允许在边缘设备上进行更快的计算。例如,图 7-3 显示了一个容器化的网络应用被推送到电信边缘站点或进一步推送到企业内部的数据中心,假设所有环境中都有一个兼容的主机环境(如 Kubernetes)。客户现在可以选择根据他们的延迟、安全和数据主权政策连接到不同的托管环境,而 Dapr 的能力 API 可以隐藏所有的位置和认证差异。从客户的角度来看,它总是通过 localhost 调用网络应用,而不需要认证,或者通过 Dapr 提供的 TLS 认证。
图 7-3. 计算从云到边缘的推演
在一个更先进的系统中,网络应用实例可以动态扩展,以减少整体托管成本,同时保持用户满意度。例如,当很多体育迷聚集在一起参加大型足球比赛时,球队的网络应用实例可以在竞技场附近的电信边缘站点上动态扩展,以处理工作负荷的激增。在淡季,所有的站点部署可以缩减到零,所有的客户都被引导到云托管的实例上进行偶尔的访问。
另外值得注意的是,通过 Dapr 的能力交付机制,这样的计算推送不需要集中协调。如果你更新早期的能力元数据,使其更倾向于本地容器而不是远程 API 调用,API 代理可以根据需要拉下最新的图像并启动自己的本地副本。换句话说,计算被下拉到边缘,而不是从云端推送。
人工智能
微软的人工智能战略是让每个开发者和消费者都能接近人工智能。Dapr 的能力 API 可以将复杂的 AI 调用包装成简单的本地 API 调用。例如,为了检测图片中的物体,开发者可以简单地将图片发布到一个(虚拟的)URL:http://localhost:3500/v1/capabilities/detect/object?as=rectangles。
和 Dapr 将返回图片坐标系中的标记矩形。开发人员只关心物体检测能力。由运营团队来配置该能力的交付方式 —— 作为远程服务调用、本地容器或进程内调用 —— API代理可以根据需要在不同的产品中切换。例如,智能交通摄像头可以在连接允许的情况下依靠基于云的人工智能模块进行车牌识别,并提供高分辨率的图片。当连接下降时,它可以切换到边缘部署的低分辨率图片,以确保持续服务。这种变化对应用开发者来说是透明的。
Dapr 的能力交付机制使应用开发者能够使用面向能力的架构来设计他们的应用,这是一个值得自己出书的话题。接下来,我们将转向行为者模式,并研究 Dapr 能给这个流行的编程模型带来什么创新。
增强的行为体
正如 第 4 章 所介绍的,Dapr 支持功能齐全的虚拟行为体模式,具有本地 API 以及 .NET 和 Java SDK(在撰写本文时,Python SDK 正在积极开发中)。Dapr 在一个重要的方面与其他行为体框架不同:它允许你从定义的框架中突破出来。这反映了 Dapr 设计的一个关键原则 —— 要有帮助,但不是限制性的。Dapr 在那里提供帮助,但是当你对超越 Dapr 有信心时,我们欢迎你这样做。这种架构上的开放性也使得我们能够提供一些创新的行为者功能。下面几节将介绍其中的一些。
聚合器
从许多行为体实例中聚合状态可能是有问题的。因为一个角色封装了它的状态,你只能通过它定义的访问方法来查询一个角色的状态。这意味着,要聚合多个角色的状态,你必须一个一个地探测它们并聚合结果。聚合的结果并不是整个系统的一致快照,因为它被各行为体实例之间的民意调查的时间差异所歪曲。即使你试图启动数以千计的线程来并行地轮询所有连接的设备,你的快照仍然会有偏差,因为这些线程是不同步的。
因为 Dapr 不使用专有的行为者状态格式,你可以通过直接进入底层数据存储轻松查询行为体状态。此外,你可以使用数据库的原生事务隔离来实现所需的隔离级别,如未提交的读取和可序列化。通过直接查询数据库,你可以用一次数据库查询的费用获得所有角色状态的一致快照。
聚合器行为体将这种查询封装成一个容易消费的接口,可以执行常见的聚合,如 sum()、average(percentile p) 和 histogram(band b)。
查询接口
我们可以进一步推动聚合器的想法,允许更灵活的查询。聚合器行为体可以定义一个开放的查询接口,允许用户运行以标准格式(如 OData、T-SQL 和 Gremlin)编写的查询。
当然,我们需要设置约束条件,禁止运行任意的、有潜在危险的查询,比如删除数据库表。这可以通过配置行为体状态存储与适当的 RBAC 设置来实现,这些设置授予指定用户对特定数据库实体的只读访问权。
如果允许任意查询被认为太危险,我们可以通过元数据对行为体进行分组和过滤。例如,如果我们允许行为体实例与标签相关联,我们可以提供有限的查询能力,支持通过标签过滤。为了查询所有需要维修的电梯轿厢(由角色代表),用户可以运行一个查询,通过最后记录的维修日期来过滤电梯。
接下来,我们将扩展聚合行为体的想法,它定义了一个一对多的映射,以支持通用的图拓扑结构。
行为体图
行为体图允许将多个行为体实例动态地连接在一起,形成一个可以被视为单一行为体实例的图。例如,假设你有两个行为体实例,实例 A 和实例 B,你可以通过将实例 B 作为实例 A 的依赖关系将它们连接起来。然而,你可以把合并后的实例作为一个新的行为体实例进行操作。新的实例遵循一般的操作原则,比如单线程访问模式和事务性状态更新。合并的行为体实例暴露了与单个行为体实例相同的接口,当它收到一个调用时,它通过遵循依赖链接将调用分派给单个行为体实例。
让我们考虑一个具体的场景,以便更好地解释行为体图的可能应用。想象一下,你把一辆车的不同部分建模为单独的行为体。它们通过互相调用或通过中间总线交换消息来一起工作。现在你想把配置更新推送给一些或所有的部件。配置更新应该是事务性的,这意味着它应该同时应用于所有受影响的部件,或者根本就不应用。你将能够做的是把所有受影响的行为体加入到一个图中,并把新的配置发送到图实例中。图实例确保所有的配置更新被提交或回滚,然后你可以从图中释放这些部分。
聚合器、行为体图和查询接口允许你同时访问多个角色实例的信息 —— 这是许多物联网场景所需要的能力,但在角色框架中经常缺乏。
现在让我们把注意力转移到单个行为体实例上,看看我们可以做哪些方面的改进。
多版本行为体
在许多物联网场景中,我们希望保留行为者状态的简短历史。例如,我们可以通过读取一个小窗口的移动平均数来获得更平滑的读数,而不是仅仅读取传感器的最新读数。一个多版本的行为体会自动维护行为体状态的最后 N 个版本。除了常规的状态访问方法外,它还支持移动平均数、最大、最小和中位数等方法。
多版本行为体也可以被配置为根据历史窗口中的状态变化来触发行动。例如,一个声学传感器可以被配置成对读数的突然变化做出反应(例如,通过计算振幅变化的导数),以检测诸如枪声等脉冲式声音。这样的能力可以用来简化信号检测和异常检测,使其更容易实现某些物联网方案。例如,当多个声学传感器被触发时,你可以利用查询界面来寻找被激活的传感器。然后你可以利用它们的地理位置以及信号时间,根据到达时间差(TDoA),通过三角测量法计算出声源(射手)的确切位置。图 7-4 说明了四个传感器(A、B、C 和 D)如何合作检测二维空间中的枪击位置(S)。每一对传感器都可以根据 TDoA 产生一个双曲线;双曲线的共同交点决定了枪声的来源。
由于我们通过 Dapr 边车访问 Dapr 行为体实例,我们可以使用 Dapr 中间件动态地扩展或修改行为体,我们接下来将简要地讨论这个问题。
图 7-4. 枪击检测的例子
行为体中间件
正如我们可以使用中间件来修改 Dapr 边车的行为一样,我们可以在角色状态访问方法之上使用中间件。我们可以插入各种数据转换或规范化的中间件,以实现数据的互操作性,而无需修改行为体本身。中间件也可以被设计为支持多种转换。例如,它可以被设计成将相同的角色状态投射到不同的形状,这样它就可以被期望不同的数据格式或不同版本的数据格式的消费者所消费。
正如你所看到的,Dapr 行为体比其他一些行为体框架中的行为体更容易扩展 —— 这里提出的想法只是我们可以去的几个方向的例子。正如本书前面提到的,我们相信一个框架应该为开发者提供一条通往成功的黄金通道,但同时也允许他们探索和成长。当然,这对开发者提出了更高的期望,但我们认为为了整个行业的发展,鼓励成长和创新是一件健康的事情。
接下来,我们将继续讨论另一个迷人的话题:跨集群通信。
通用命名空间
当 Dapr 边车在同一个 Kubernetes 集群上时,它们可以相互通信。这是因为 Dapr 为每个边车自动创建了一个 ClusterIP 服务,其地址可以在同一集群的范围内被内置的 Kubernetes 名称解析机制解析。
如果一个集群上的 Dapr 边车想要与另一个不同集群上的 Dapr 边车进行通信,你可以使用消息传递(通过共享消息总线),或者通过给它分配一个公共 IP 地址来暴露目标边车。
通用命名空间的想法是允许 Dapr 边车使用扩展的 Dapr ID 和集群后缀来发现并相互通信。例如,在一个名为 cluster-a 的集群上运行的 ID 为 dapr-a 的 Dapr 边车可以用 dapr-b.cluster-b 的名字来称呼在 cluster-b 上运行的 dapr-b 边车。如果你有一个跨越基于云的集群和基于边缘的集群的混合解决方案,那么在任何一个集群上运行的服务都可以通过通用命名空间来相互交谈,就像它们在同一个集群上一样。
有许多潜在的方法来实现通用命名空间。下一节介绍了其中一种可能的架构。
架构
设计通用名称空间解决方案的最直接方法是使用反向代理,从外国集群接收请求并将请求转发到适当的服务。反向代理地址,连同它们所代表的站点名称,可以交叉输入到所有参与的集群中,或者代理地址可以作为 DNS 条目输入,在集群之间共享。
图 7-5 说明了一个带有反向代理的通用名称空间的可能实现。在这种情况下,我们有两个站点,site_a 和 site_b,它们通过一个私有的对等网络相互连接。site_a 托管了一个名为 service_a 的服务,site_b 托管了两个服务,service_b 和 service_c。在一个共享的 DNS 表中,带有站点后缀的远程名称被指向相应的代理地址。当一个服务调用一个本地服务时(如 service_b 调用 service_c),由于 service_c 解析到本地集群 IP(仅在集群内可路由),调用直接到另一个服务。当一个服务调用一个远程服务时,该调用会通过一个可跨集群路由的 IP 转发到目标集群上的反向代理。反向代理将请求转发给目标服务(通过剥离地址中的站点名称),并将结果发回给调用者。
图 7-5. 带有反向代理的通用名称空间
如果一个服务本身暴露了一个可公开路由的 IP,可以修改 DNS 记录,使远程服务名称直接解析到可路由的 IP,而不是通过反向代理。
当集群通过互联网连接时,该架构是有效的。在这种情况下,反向代理需要与公共 IP 地址相关联。因为 Dapr 支持双向 TLS,它可以在代理之间自动设置双向 TLS。此外,因为所有的跨集群通信都要通过反向代理(除了公开的服务),你可以很容易地在反向代理上设置必要的网络政策来保护跨集群通信。
另一个可能的解决方案是把跨集群路由留给服务网,如 Linkerd,但我们不会在这里进一步讨论这个问题。
应用场景
通用命名空间允许跨越多个集群的服务相互通信。在许多企业环境中,你可能想有意地将你的应用程序组件分布在各集群中。例如,你可能想把你的业务逻辑组件部署到一个安全区(DMZ),与驻扎在面向公众的网络上的前端组件分开。单一命名空间允许运行在不同网段上的 Dapr 边车相互通信。
当你有分布在多个集群上的服务时,这种能力也是非常有用的,例如在地理上分离的站点。图 7-6 显示了一个通过通用命名空间访问地理冗余服务的例子。在这种情况下,service_b 被部署在两个物理上独立的站点,site_b 和 site_c。在 site_a,DNS 表或路由表被配置为与目标站点的两个反向代理相关的远程服务名称(图中的 service_b.other)。当 service_a 试图调用 service_b.other 时,流量在两个代理之间被分割。当一个站点变得不可用时,路由表可以被更新,以便所有流量被路由到剩余的站点。
图 7-6. 通过通用名称空间访问一个地理冗余的服务
通用命名空间在一些云边缘混合场景中也有帮助。例如,现场网关主机可以是一个多节点的 Kubernetes 集群,作为高可用服务运行网关服务。网关服务可以使用通用命名空间来保持与云端服务的双向通信。
接下来,我们将看看 Dapr 在边缘计算场景中能做什么,我们在 第 6 章 中简要介绍了这一点。
Dapr 在边缘侧
Dapr 是为云和边缘设计的。然而,到目前为止,它的主要重点是实现云原生应用。要使 Dapr 也成为一个强大的边缘运行时,我们还有很长的路要走。本节涵盖了几个主题,展示了 Dapr 为边缘计算场景带来价值的不同方式。
Dapr 作为一个轻量级的函数运行时间
一个函数是一段可以被事件触发的代码。它接受一些输入,应用一些计算,并产生一些输出。Dapr 拥有所有必要的基本功能,可以作为一个函数运行时使用。它支持可以用来激活函数代码的触发器,并且支持函数代码可以用来向另一个系统发送数据的绑定。Dapr 还有其他一些功能,使它成为一个很好的函数运行时:
- 有状态的函数支持:一个函数通常是无状态的 —— 它在输入上应用一些转换,然后就离开了画面。然而,有状态的函数在某些情况下可以派上用场。例如,当你需要触发一个依赖于其他一组活动的完成的活动时,你可以把这个活动设计成一个有状态的函数。当一个先决条件函数完成时,它向有状态函数报告。有状态函数跟踪所有的先决条件,并在所有先决条件得到满足时触发自己的逻辑。
- 语言中立:许多功能框架只支持有限的编程语言。Dapr 支持所有的现代语言。这意味着你可以用你选择的语言编写你的函数代码。
- 简单的运行时栈:Dapr 运行时作为一个单一的进程运行。尽管它得到了其他 Dapr 服务的补充,如安置服务,但 Dapr 运行时本身就足以支持普通函数的执行。这使得为边缘部署打包和分发运行时变得简单明了。
要使用 Dapr 运行时作为一个函数运行时,你的函数代码需要被实现为一个 HTTP 或 gRPC 服务器。我们有理由期待 Dapr 在未来被扩展为动态加载函数代码,这样你就不必自己托管这些代码了。
一种可能的实现方式是使用一个进程管理器。基本上,你把你的函数代码实现并打包成一个可执行文件或批处理 /hello 脚本,然后你在进程管理器上注册,告诉它如何用输入参数开关启动你的可执行文件。下面的代码片段显示了一个注册模型的样本,它将 hello-world.sh 脚本注册到进程管理器中:
registration:name: hello-worldcmd: ~/scripts/hello-world.shinput-switch: inputid-switch: idinput-type: inlineruntime: bash
进程管理器反过来将 hello-world 注册为 Dapr 运行时的一个函数路由(作为一个函数运行时)。当函数被触发时,Dapr 运行时通过调用带有 --input 和 --id 开关的指定脚本来调用该函数代码。
$ ~/scripts/hello-world.sh --id <function id> --input <function input>
你可能已经注意到了注册中的运行时属性。这个想法是,你可以从几个支持的运行时中选择,比如 Docker 和 WebAssembly,在一个沙盒环境中运行你的函数代码,前提是你的函数代码已经为运行时打包。
进程管理器可以用来使传统的应用程序作为函数工作。例如,你可以写一个脚本来启动一个基于 UI 的传统应用程序,然后通过发送键盘和通过剪贴板复制数据来向 UI 句柄发送函数输入。然后你可以把脚本注册为 Dapr 的一个函数。
在边缘有一个轻量级的函数运行时间是对函数即服务(FaaS)平台的一个很好的扩展。同样的基于云的功能可以下拉到边缘机器,以获得更快的响应时间,并用于断开连接的场景。有不同的方法可以将功能包下拉到客户端,例如通过 Docker 注册表。最有趣的选择是使用 WebAssembly 运行时;我们接下来会看一下这个。
WebAssemly 中的 Dapr
WebAssembly(简称 Wasm)在前面的章节中已经偶然提到过几次。如果你对它不熟悉,WebAssembly 是一种基于堆栈的虚拟机的指令格式。例如,下面是一个简单的 WebAssembly 模块,它暴露了一个 add 函数,将两个整数加在一起(代码是以文本格式而不是二进制格式写的,以便更好地阅读):
(module(func (param $a i32) (param $b i32) (result i32)local.get $alocal.get $bi32.add))
语言本身并没有什么真正有趣的地方 —— 更有趣的是支持该语言的运行时间。具体来说,由于该运行时是万维网联盟(W3C)的建议,它被主要的现代浏览器所支持,如谷歌浏览器和微软 Edge。这使它具有巨大的影响力:它是一个普遍存在的运行时,在全世界数十亿台计算机中提供了一个沙盒式的执行环境。
我们都熟悉内容交付网络(CDN),它从分布式代理服务器而不是从原始服务器提供静态内容。CDN 为用户体验提供了极大的加速,它允许网站为更多的客户提供服务,超过了原来服务器的能力。有了 WebAssembly,我们可以把更复杂的计算推给浏览器,使我们能够在浏览器中提供流畅和复杂的用户互动,如游戏和在线编辑。
理论上,你可以把你的 Web 服务器逻辑打包成 WebAssembly,然后运送到用户浏览器。这意味着你可以向你的最终用户提供具有本地性能的高度互动的网站,而你的 Web 服务器所做的只是提供静态网页和 WebAssembly 模块(两者都可以在 CDN 中被缓存)。
进一步说,如果你能让 Dapr 作为 WebAssembly 模块工作,你的网页将能够使用该模块来消费 Dapr 提供的所有功能,如保存状态和连接到各种云服务。图 7-7 显示了一个 Web 服务器作为 WebAssembly 模块在 Dapr 边车后面运行在用户的浏览器上的例子。该图显示了多个浏览器如何运行它们自己的 Web 服务器的本地实例,并使用 Dapr 来共享状态和与消息骨干通信。该图还显示了当能力交付的想法被结合到这个设计中时,Dapr 边车可以获得额外的能力包,在这种情况下被打包成 WebAssembly 模块,以增强 Web 服务器的能力。
图 7-7. Web 服务器作为 WebAssembly 模块在浏览器中运行
这种设计也可以用在众包解决方案中。众包的关键挑战之一是说服你的潜在用户安装你的本地计算包。通过这种设计,用户需要做的就是同意运行你的计算包;然后他们可以直接在浏览器中动态地获取和运行计算包,并使用 Dapr 将计算结果发回你的服务器。我们可以想象有一个共同的网站,让用户只需点击几下就可以浏览和参与各种众包项目。
互联网运营商也可以通过在这些设备上运行 Dapr,对用户家中的互联网连接设备进行非常有创意的处理。例如,如果你同意为你的调制解调器或电视机顶盒提供额外的计算能力,你可以通过允许你的运营商在你的设备上运行沙盒网络服务器来获得积分。这一协议将允许互联网运营商使用分布式设备来托管网络服务。如果仔细选择,这些设备可以为目标用户提供更好的延迟和吞吐量,而不需要复杂的基础设施。像这样的发展可能会迎来一个 “边缘云” 的新时代,这可能是对传统云平台的真正挑战。
在撰写本文时,越来越多的浏览器外 WebAssembly 运行时正在出现。这些轻量级的运行时,有时只有几十千字节大小,是为低功耗设备设计的。而如果我们在 WebAssembly 之上加入一些通用的接口,如开放容器倡议(OCI)、容器运行时接口(CRI),甚至是 kubelet,这些低功耗设备就可以加入到 Kubernetes 集群中,并对它们进行工作调度。这使得你可以用低功耗的设备形成一个高可用的托管环境。
接下来,我们将继续我们对低功耗设备的思考,看看 Dapr 如何适应这种环境。
Dapr 作为脚本
正如前一章所讨论的,在低功率设备上运行 Dapr 边车可能太昂贵了。一个可能的解决方案是在现场网关上运行 Dapr 边车,让设备通过一个预定义的 IP 地址而不是 localhost 与边车进行通信。
如果现场网关不可用怎么办?在这种情况下,Dapr 可以被打包成一个脚本,低功率设备可以调用它。使用脚本而不是库的原因是,它更符合 Dapr 的语言无关性和动态可组合性。例如,当设备想保存状态时,它可以用必要的命令行参数运行 Dapr 脚本。
$ dapr.sh --capability state --action save --key my-key --value "Hello, World!" --store my-store
Dapr CLI 已经有一些内置的功能来调用 Dapr 运行时,比如向主题发布消息。如果它被扩展为自我托管 Dapr 运行时,它就可以作为一个全功能的 Dapr 脚本使用。
作为一个脚本运行 Dapr 的主要好处是避免保持 Dapr 边车的运行,这可能会增加功耗压力。然而,反复启动 Dapr 运行时对一个低功耗设备来说也是很昂贵的。如果这成为一个问题,将 Dapr 实现为一个库可能是一个出路 —— 这将使你与特定的编程语言绑定,但保留所有有意义的抽象和 Dapr 的动态配置性。因此,将 Dapr 运行时作为流行的边缘计算语言(如 Python、C++ 和 Rust)的进程内库来实现,可能是对边缘 Dapr 生态系统的一个良好补充。这种努力也将有助于实现 Dapr 运行时间和通信协议之间的分离,从而实现与设备通信协议的整合,如数据分发服务(DDS)的线上协议。
正如你所看到的,Dapr 可以采取多种途径来实现其边缘化 —— 而且我们确信社区会想出更多创造性的方法。边缘计算将是未来 10 年内计算机科学的中心。如果 Dapr 能够成为这一运动的一部分,那将是非常好的。
在本章的最后一节,我们将继续讨论一些难以精确归类的改进想法。同样,这只是选择了一些已经被建议的或可能即将出现的发展的例子。
其它 Dapr 改进
我们已经在 Dapr 核心团队中讨论了许多关于 Dapr 未来的想法。这一部分记录了一些在过去的讨论中出现的想法,但还没有进入路线图。很明显,当你阅读这段文字的时候,情况可能已经发生了变化;我们邀请你查阅最新的在线 Dapr 文档以获得最新的信息。以下主题没有进行任何分类。
Dapr 组件投影
Dapr 组件被描述为 YAML 文件,在 Kubernetes 上运行时,它们被表示为 Kubernetes 自定义资源。这些组件捕获你的应用程序的外部依赖性,如管理数据库或负载平衡器。然而,Dapr 并没有提供一个内置的方法来管理这些外部依赖,作为一个完整的技术栈,你的应用程序依赖于此。如果有一种方法可以轻松地描述和管理这些依赖关系,以确保你的应用程序得到良好的支持,那不是很好吗?
如果你在想,“等一下,这是个已解决的问题”,你是对的。云平台已经提供了资源管理系统,如 Azure Resource Manager(ARM)和 AWS CloudFormation,还有一些跨平台的解决方案,如 HashiCorp 的 Terraform。这些系统为你提供了描述语言,你可以用它来描述你的云资源的理想状态,它们试图把所有需要的资源带到理想状态。当任何资源偏离理想状态时,系统可以采取纠正措施,使资源恢复到该状态。
如果 Dapr 不想重新发明任何轮子,我们是不是应该使用现有的解决方案之一?答案是肯定的!我们当然不希望再次解决资源管理问题。然而,我们仍然缺少一块拼图 —— 如何将云资源投射到 Kubernetes 定制资源中。如果有自动投射,我们就不需要再手动定义 Dapr 组件。也就是说,我们不需要查找如何描述 Dapr 组件;相反,我们可以使用任何可用的资源管理解决方案,并将选定的资源作为 Dapr 组件自动投影到 Kubernetes。这个解决方案有几个好处:
- 无人工组件配置:这减少了管理基于 Dapr 系统的负担。
- 可扩展性:一旦投射到位,任何云资源都可以作为组件投射到 Dapr 中。
- 自动组件配置更新:投影系统可以与底层资源管理系统挂钩,并在原始云资源发生变化时(如发生密钥轮换时)根据需要自动更新组件定义。这再次减少了管理负担和由基础设施配置不匹配引起的错误风险。
实现这样一个投影系统并不难,因为这些云资源有定义明确的清单模式和 API 接口。理论上,自动生成 Dapr 组件代码是可行的,这样你就不需要编写任何自定义代码。当然,资源 API 的直接穿透没有引入什么额外的价值。Dapr 仍然可以为某些资源类型定义一个通用的 API。因为 API 将是资源特性的一个子集,这部分也可以作为某种清单被捕获并自动生成。
最佳实践和经过验证的模式
Dapr 在它的组件中实现了一些最佳实践,比如在发生瞬时错误时自动重试的指数退避,可配置的并发控制,内置的秘密管理,以及双向的 TLS 支持。正如你所看到的,你可以使用 Dapr 轻松地实现一些企业模式,如竞争的消费者和分布式工作流。
然而,Dapr 可以做得更多。Dapr 有能力以一种自然的、无摩擦的方式为应用程序带来更多经过验证的原则和最佳实践。例如,我们可以扩展重试模式以支持自动断路。当一个远程服务被破坏时,在尝试了几次后继续尝试是没有意义的。相反,一个断路器可以被跳闸,导致任何未来的调用失败,而没有任何尝试去联系服务器,直到配置的时间窗口过去(此时断路器被重置)。
另一个值得支持的模式是 第 6 章 中讨论的事件源。事件源是一个强大的模式,但是一个合适的实现却很难得到。我们希望 Dapr 最终能够利用其强大的社区力量,提供一个可靠的事件源实现。
关于 Dapr 的一个有趣的事情是,它允许其中一些模式被引入并独立于应用程序代码进行重新配置。如果你利用一个通用的模式和实践库,应用程序代码可能会滥用该库,导致不一致或意外的行为。当模式通过 Dapr 边车应用时,使用模式的刚性确保了一致性,而且你可以看到这些模式是如何配置和应用的。能够在不修改代码的情况下加强最佳实践是相当强大的。当一个公司的政策发生变化时,也许是因为合规性要求的变化,运营团队可以重新配置应用程序,使其符合要求,而不需要触及代码。
Dapr 描述符
Dapr 边车为客户端定义了一个简单而清晰的 API。它还定义了一个清晰的 API 来与应用程序代码交互。这意味着你可以在一个描述符文件中描述你的应用程序的 API 形状以及它所需要的组件,你可以使用自动生成工具来构建你的应用程序的骨架,以及它的 Dockerfile、部署工件和构建脚本。然后你可以在生成的 HTTP 处理程序中插入你的业务逻辑。下面的代码片段显示了这样一个描述符的模样:
services:- name: my-serviceroutes:- say-hello- say-goodbyecomponents:- name: my-storetype: state.redismetadata:- name: redisHostvalue: <Redis host>- name: redisPasswordvalue: <Redis password>
一些独立的软件供应商创建了大量的应用程序。这些应用程序遵循类似的设计和结构,但都是为特定客户设计的。使用这样的描述符可以让他们以极大的一致性创建这些应用程序,而不需要重复模板代码。脚手架是共享库的一个替代方案。它们的目的都是为了确保跨项目的一致性。然而,设计和维护一个高效的共享库并不是一件容易的事,因为这个库必须是可重用的,但又要保持可定制性以适应不同的项目。棚架式代码不依赖于共享库,所以它有更大的灵活性,可以为特定的项目进行深度定制。
Dapr 为多方计算提供便利
Dapr 边车可用于封装复杂的多方计算程序,如区块链智能合约和各种流言协议。对于区块链的情况,开发人员将能够通过简单的 Dapr POST 调用智能合约,或者配置智能合约,使其由任何超级端口的事件源自动触发。这将区块链带到了更广泛的开发者社区,没有任何摩擦。
总结
正如我们已经提到的,Dapr 是一个年轻的项目。我们很高兴能把它带到今天的位置,我们也渴望看到 Dapr 在未来的发展。无论它的发展方向如何,我们希望它仍然是一个有用的、谦逊的、高效的运行时,使开发者能够为云计算和边缘计算编写可扩展和可靠的分布式应用。
这一章涵盖了关于 Dapr 如何发展的一小部分想法。我们希望它能为你提供一些灵感,我们也期待着看到社区会有什么其他的想法 —— 来加入我们吧!
