news 2026/9/10 22:41:42

Dapr 内部 API(dapr/proto/internals/v1)协议深度解析:sidecar 间通信的 gRPC 契约与 Proto 客户端生成指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Dapr 内部 API(dapr/proto/internals/v1)协议深度解析:sidecar 间通信的 gRPC 契约与 Proto 客户端生成指南

Dapr 内部 API(dapr/proto/internals/v1)协议深度解析:sidecar 间通信的 gRPC 契约与 Proto 客户端生成指南

【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr

本文以 Dapr 仓库中的 dapr/proto/internals/v1/README.md 为骨架,完整解析 Dapr 内部 API 协议包的定位、五个 .proto 文件的契约细节(服务调用、Actor 提醒、Job 调度),并逐条还原官方推荐的 Proto 客户端生成流程(make init-proto/make gen-proto)。读完本文,你将能够理解daprdsidecar 之间"零 HTTP 中间层"的直连通信模型,掌握从.protopkg/proto生成代码的完整工具链,并能在源码中定位这些内部 RPC 的实际调用点。

Internal APIs 在 Dapr 协议体系中的定位

Dapr 仓库将全部 Protobuf 定义按职责划分为若干包,dapr/README.md 中的总览表给出了它们的定位:

packagesdescription
commoncommon protos that are imported by multiple packages
internalsinternal gRPC and protobuf definitions which is used for Dapr internal
runtimeDapr and App Callback services and its associated protobuf messages
operatorDapr Operator gRPC service
placementDapr Placement service
sentryDapr Sentry for CA service
componentsDapr gRPC-based components services

internals包正是本文的主角。dapr/proto/internals/v1/README.md 第一句话就给出了它的使命:

This folder is intended for the Internal APIs that thedaprdsidecars use to communicate with each other.

也就是说,这里的协议不面向用户应用,而是用于 Dapr sidecar(daprd)之间的"内部互通"。典型场景是:服务 A 的 sidecar 在收到用户对服务 B 的调用请求后,需要把请求转交给服务 B 的 sidecar——这条 sidecar-to-sidecar 链路走的就是internals包定义的 gRPC 服务ServiceInvocation

该目录下共包含 5 个.proto文件,覆盖四类内部能力:

  • service_invocation.proto:sidecar 间的服务调用与 Actor 调用(2021 年引入,最核心);
  • apiversion.proto:内部 API 版本枚举;
  • status.proto:HTTP/gRPC 应用通道的响应状态;
  • reminders.proto:Actor 提醒/定时器事件(2023 年引入);
  • jobs.proto:Job 调度请求与事件(2024 年引入)。

所有文件统一使用syntax = "proto3",包名为dapr.proto.internals.v1,并声明 Go 包映射github.com/dapr/dapr/pkg/proto/internals/v1;internals——这正是生成代码落盘位置的依据。

ServiceInvocation:sidecar 间服务调用的核心契约

RPC 方法总览

service_invocation.proto 定义了唯一的 gRPC 服务ServiceInvocation(第 36–60 行),包含 5 个 RPC 方法:

RPC 方法类型用途
CallActorUnary调用指定 Actor 的方法
CallLocalUnary调用指定服务(应用)的方法
CallActorReminderUnary触发远程内部 Actor 的 Reminder
CallLocalStreamBidi-streaming以数据流方式调用服务方法(请求分块上行、响应分块下行)
CallActorStreamServer-streaming调用 Actor 方法并流式返回响应

.proto注释中有一个非常关键的设计说明:这些 RPC 的 gRPC 响应状态只代表内部连接状态,不代表被调用方的业务响应状态。也就是说,CallLocal/CallActor的 gRPC 层在大多数情况下都返回OK,真正的 HTTP 状态码/业务错误被封装在InternalInvokeResponse.status字段中带回调用方,由调用方 sidecar 再翻译成对用户请求的响应。这一点从消息结构上可以看得很清楚(见下文Status消息)。

核心消息结构

InternalInvokeRequest(第 73–86 行)是调用方 sidecar 向被调用方 sidecar 传输的完整请求载体:

message InternalInvokeRequest { APIVersion ver = 1; // 必填,Dapr Runtime API 版本 map<string, ListStringValue> metadata = 2; // 必填,调用方的 HTTP header 或 gRPC metadata common.v1.InvokeRequest message = 3; // 必填,调用方的调用请求 Actor actor = 4; // 仅 Actor 调用时使用 }

InternalInvokeResponse(第 90–103 行)则反向携带被调用方的响应:

message InternalInvokeResponse { Status status = 1; // 必填,HTTP/gRPC 状态 map<string, ListStringValue> headers = 2; // 必填,应用回调的响应头 map<string, ListStringValue> trailers = 3; // 仅 gRPC 应用回调使用 common.v1.InvokeResponse message = 4; // 被调用方的响应消息 }

两个辅助消息也很常用:

  • Actor(第 63–69 行):由actor_type(必填)与actor_id(必填)唯一标识一个 Actor 实例。在 pkg/proto/internals/v1/service_invocation_additional.go 中,手写辅助方法GetActorKey()||分隔符把二者拼成 Actor 的 FQDN key(actor_type || actor_id),这个 key 会被 Reminder、定时器等模块复用。
  • ListStringValue(第 126–129 行):repeated string values,用于承载可重复的 header/metadata 值。

流式 RPC 的分块语义

CallLocalStreamCallLocal的数据流版本。虽然它声明为双向流,但.proto注释明确要求其行为等价于"简单 RPC":调用方先发送完整请求(按块分多条消息),再读取完整响应(同样按块分条)。协议对消息编排有严格要求:

  • 流中第一条消息必须携带request(调用方)或response(被调用方),且所有必填属性齐全;该消息可以携带一个payload,也可以为空;
  • 后续所有消息只能携带payload,不得再出现request/response等其他属性;
  • 携带payload的每条消息必须带序号seq:从 0 开始、每个分块递增 1;不带payload的消息不得出现seq
  • 发送方发完数据后必须调用CloseSend
  • 每个方向上至少要发送一条消息;若只发一条,则该消息必须同时携带request/responsepayload允许为空)。

对应的流消息定义(第 106–123 行)在结构上刻意与 Unary 版本错开:InternalInvokeRequestStream内部分别持有InternalInvokeRequest request(其message.data不携带数据)和common.v1.StreamPayload payload(数据分块),InternalInvokeResponseStream同理。

从源码可以看到该协议在流式调用时的工程约束:pkg/messaging/v1/util.go 中注释说明CallLocalStream的缓冲上限为 2KB;pkg/messaging/direct_messaging.go 发起CallLocalStream调用,并在 第 489 行附近 对"对端 sidecar 不支持CallLocalStream"(返回Unimplemented)做了降级回退处理——这是新旧 sidecar 混布(版本倾斜)场景下的兼容性设计。单测 pkg/messaging/direct_messaging_test.go 中还分别 mock 了CallLocalStream的客户端与服务端流,验证了这套流式消息编排。

三个轻量辅助协议:版本、状态与事件

APIVersion:内部 API 的版本门禁

apiversion.proto(第 21–27 行)只定义了一个枚举:

enum APIVersion { APIVERSION_UNSPECIFIED = 0; // 未指定 V1 = 1; // Dapr API v1 }

InternalInvokeRequest.ver字段使用该枚举,保证调用双方 sidecar 对内部 API 版本的语义一致。目前只有V1一个正式版本。

Status:业务状态与 gRPC 状态解耦

status.proto(第 23–32 行)定义了Status

message Status { int32 code = 1; // 必填,状态码 string message = 2; // 错误信息 repeated google.protobuf.Any details = 3; // 错误详情列表 }

该消息承载的是"被调用方应用的响应状态"(HTTP 状态码或 gRPC 状态),与ServiceInvocation服务自身的 gRPC 连接状态相互独立——这正是前文"内部 RPC 大多返回 OK"设计落地的关键。

Reminder 与 TimerFiredEvent:Actor 提醒/定时器事件

reminders.proto(2023 年引入)定义了存储于 Dapr Actor 状态存储中的提醒结构:

message Reminder { string actor_id = 1; string actor_type = 2; string name = 3; google.protobuf.Any data = 4; string period = 5; google.protobuf.Timestamp registered_time = 6; string due_time = 7; google.protobuf.Timestamp expiration_time = 8; bool is_timer = 9; bool skip_lock = 10; } message Reminders { repeated Reminder reminders = 1; } message TimerFiredEvent { google.protobuf.Timestamp fire_at = 1; int32 timerId = 2; uint64 generation = 3; }

Reminder统一了"提醒(reminder)"与"定时器(timer)"两种模型(is_timer区分),并支持周期(period)、到期时间(expiration_time)与锁跳过(skip_lock)。TimerFiredEvent描述了定时器触发时发送给 Actor 的事件(含触发时刻、timerId 与代际generation)。在源码中,Reminder会被直接作为远程 RPC 的入参:调用方侧 pkg/actors/router/router.go 通过CallActorReminder(ctx, &internalv1pb.Reminder{...})触发对端 sidecar 的 Actor 提醒;接收方侧 pkg/api/grpc/daprinternal.go 的CallActorReminder实现会校验actor_typeactor_id并交由 Actor 运行时投递。

JobHTTPRequest 与 JobEvent:Job 调度协议

jobs.proto(2024 年引入)定义了两个消息:

// 用于 HTTP Dapr Job API,保证 data 始终以 JSON 对象(google.protobuf.Struct)序列化 message JobHTTPRequest { string name = 1 [json_name = "name"]; optional string schedule = 2 [json_name = "schedule"]; optional uint32 repeats = 3 [json_name = "repeats"]; optional string due_time = 4 [json_name = "dueTime"]; optional string ttl = 5 [json_name = "ttl"]; google.protobuf.Value data = 6 [json_name = "data"]; optional bool overwrite = 7 [json_name = "overwrite"]; optional common.v1.JobFailurePolicy failure_policy = 8 [json_name = "failurePolicy"]; } message JobEvent { string key = 1; // Job 的 FQDN key string name = 2; // Job 名称 scheduler.v1.JobMetadata metadata = 3; google.protobuf.Any data = 4; }

JobHTTPRequest的注释特意说明:其字段文档以dapr/proto/runtime/v1/dapr.proto中的对应定义为准,而之所以在内部包中单独定义一份,是为了保证 HTTP 侧data始终以 JSON 对象(google.protobuf.Struct)形态序列化。该消息被 HTTP API 层直接复用:pkg/api/http/jobs.go 将JobHTTPRequest作为 HTTP 处理器与 Universal 层之间的请求载体。JobEvent则是 Job 到期时交由 Scheduler 处理的事件,携带 FQDN key、元数据与数据负载。

Proto 客户端生成:从 .proto 到 pkg/proto

README 给出了完整的生成流程,核心是两条make命令。下面是逐步拆解,并与当前仓库 Makefile 的实际实现做了对齐。

步骤 1:安装 protoc

按 README 要求,首先需要安装指定版本的 protoc:

  1. Install protoc version: v4.25.4

protobuf v4.25.4对应的 protoc 编译器。需要特别说明版本一致性:当前仓库 Makefile 中实际固定的是PROTOC_VERSION = 34.1PROTOBUF_SUITE_VERSION = 34.1),而gen-proto依赖的check-proto-version目标(Makefile)会严格校验protoc --version输出是否为libprotoc 34.1,版本不匹配会直接报错退出。因此:如果你使用较新版本的 Dapr 源码,建议以 Makefile 中PROTOC_VERSION/PROTOC_GEN_*_VERSION固定的版本为准;README 中的 v4.25.4 是编写该文档时的基线。dapr/README.md还提供了 Windows(WSL2 + Ubuntu 24.04)下通过wget下载 protoc 压缩包、解压并放入/usr/local/bin的替代安装步骤。

步骤 2:安装生成插件

make init-proto

init-proto目标(Makefile)通过go install安装三个官方插件:

init-proto: go install google.golang.org/protobuf/cmd/protoc-gen-go@$(PROTOC_GEN_GO_VERSION) go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v$(PROTOC_GEN_GO_GRPC_VERSION) go install connectrpc.com/connect/cmd/protoc-gen-connect-go@v$(PROTOC_GEN_CONNECT_GO_VERSION)

当前仓库固定版本为:protoc-gen-go v1.32.0protoc-gen-go-grpc v1.3.0protoc-gen-connect-go v1.18.1(见 Makefile)。三个插件各司其职:protoc-gen-go生成消息的 Go 结构体(*.pb.go),protoc-gen-go-grpc生成 gRPC 服务代码(*_grpc.pb.go),protoc-gen-connect-go生成 Connect RPC 客户端/服务端代码(*connect/目录)。

步骤 3:生成 gRPC Proto 客户端

在仓库根目录执行:

make gen-proto

gen-proto目标(Makefile)先运行check-proto-version校验工具链版本,然后为dapr/proto下的每个子包(GRPC_PROTOS,即 operator、placement、sentry、common、runtime、internals、components 等)逐一执行生成。每个包的实际 protoc 命令模板(Makefile)为:

protoc -I . ./dapr/proto/$(1)/v1/*.proto \ --go_out=. --go_opt=module=github.com/dapr/dapr \ --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false,module=github.com/dapr/dapr \ --connect-go_out=. --connect-go_opt=module=github.com/dapr/dapr

几个值得注意的选项:

  • module=github.com/dapr/dapr:让生成文件按 Go module 路径布局,直接落入仓库内的pkg/proto/...,而不是产生github.com/dapr/dapr的冗余目录层级;
  • require_unimplemented_servers=false:生成的服务接口不强制内嵌UnimplementedXxxServer,便于测试代码用轻量 mock 直接实现接口(pkg/messaging/direct_messaging_test.go 中的mockGRPCServerUnary就是直接实现CallLocal的例子);
  • 生成完毕后gen-proto还会执行modtidygo mod tidy)保持依赖一致。

步骤 4:查看生成产物

README 指出生成结果位于pkg/proto。对 internals 包而言,生成产物在 pkg/proto/internals/v1 下:

pkg/proto/internals/v1/ ├── apiversion.pb.go ├── jobs.pb.go ├── reminders.pb.go ├── service_invocation.pb.go ├── service_invocation_grpc.pb.go ├── service_invocation_additional.go # 手写扩展(非生成) ├── service_invocation_additional_test.go # 手写扩展的单元测试 ├── status.pb.go └── internalsconnect/ └── service_invocation.connect.go # Connect RPC 生成代码

其中service_invocation_additional.go人工编写的辅助代码(文件头注释明确说明"contains additional, hand-written methods added to the generated objects"),定义了GRPCContentTypeJSONContentTypeProtobufContentTypeOctetStreamContentType等媒体类型常量(第 25–34 行),以及Actor.GetActorKey()NewInternalInvokeRequest(method)等便捷构造器(第 46–60 行)。NewInternalInvokeRequest会自动填充Ver: APIVersion_V1并初始化common.v1.InvokeRequest{Method: method},是调用方 sidecar 构造内部请求的标准入口。

在源码中验证内部 RPC 的真实调用链

生成代码最终服务于 sidecar 的运行时逻辑,以下是三条可以对照阅读的核心调用链:

  1. 服务调用的主路径:pkg/messaging/direct_messaging.go 是direct messaging的枢纽。Unary 调用在 第 385 行 通过clientV1.CallLocal(ctx, pd, opts...)把用户请求转发给目标应用的 sidecar;需要传输大请求时则切换为 第 400 行 的CallLocalStream。配合 pkg/messaging/v1/util.go 的 2KB 缓冲注释,可以看到流式调用专为突破 Unary 消息大小限制而设计。

  2. Actor 提醒的远程触发:pkg/actors/router/router.go 在确认目标 Actor 不在本地后,构造internalv1pb.Reminder并调用client.CallActorReminder(...);对端 pkg/api/grpc/daprinternal.go 的CallActorReminder实现接收后投递给 Actor 运行时。这条链路完整复现了reminders.protoservice_invocation.proto的协作。

  3. Job 的 HTTP 入口:pkg/api/http/jobs.go 直接把internalsv1pb.JobHTTPRequest作为 HTTP→Universal 层的中介类型,印证了jobs.proto"HTTP 侧 data 恒为 JSON 对象" 的设计初衷;而 dapr/proto/scheduler/v1/scheduler.proto 则是JobEvent.metadataJobMetadata的来源,二者共同构成"Job API → 内部事件 → Scheduler"的链路。

小结与扩展阅读

dapr/proto/internals/v1是 Dapr sidecar 之间"不打用户 HTTP 网关"的内部通信契约:ServiceInvocation承载服务调用与 Actor 调用(含流式分块),Reminder/JobEvent承载 Actor 提醒与 Job 调度事件,APIVersionStatus则保障版本语义与"业务状态/连接状态解耦"。配合make init-proto+make gen-proto的工具链,任何.proto修改都能稳定地同步到pkg/proto下的 Go 代码。

如需继续深入,推荐按以下路径阅读当前仓库:

  • 协议定义:继续阅读 dapr/proto/internals/v1/service_invocation.proto 与 dapr/proto/internals/v1/reminders.proto 的完整注释;
  • 生成工具链:Makefile 的init-proto/gen-proto/check-proto-version三个目标,以及 tools/codegen.mk 中面向各 SDK 的旧版生成模板;
  • 调用方实现:pkg/messaging/direct_messaging.go 与 pkg/messaging/direct_messaging_test.go;
  • 接收方实现:pkg/api/grpc/daprinternal.go(含CallActorReminder等内部 API 的落地);
  • 跨包协议总览:dapr/README.md(含 Windows/WSL2 下的 protoc 安装变体与 e2e 测试应用依赖更新说明)。

【免费下载链接】daprDapr is a portable runtime for building distributed applications across cloud and edge, combining event-driven architecture with workflow orchestration.项目地址: https://gitcode.com/GitHub_Trending/da/dapr

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 22:40:43

自由学习记录:构建个性化知识管理系统的实践指南

1. 项目概述&#xff1a;自由学习记录的本质与实践价值 "自由学习记录&#xff08;144&#xff09;"这个看似简单的标题背后&#xff0c;隐藏着一个持续学习者的完整知识管理体系。作为坚持到第144篇的学习记录&#xff0c;它代表着一种持续性的个人成长方法论。这类…

作者头像 李华
网站建设 2026/9/10 22:40:30

AI时代工程师必备的6大软技能与实战方法论

1. 项目概述&#xff1a;为什么我们需要重新审视软技能&#xff1f;最近在给团队做年度复盘时发现一个有趣现象&#xff1a;那些在技术评审中得分最高的工程师&#xff0c;往往不是项目推进最顺利的负责人。反倒是几个技术评分中等的同事&#xff0c;他们负责的项目总能按时交付…

作者头像 李华
网站建设 2026/9/10 22:38:51

交易策略可视化与风险控制实战解析

1. 项目概述&#xff1a;可视化交易策略执行路径实战2026年1月27日这个交易日&#xff0c;我的实盘账户实现了1.73%的收益。这个数字背后是一套完整的可视化交易策略执行体系在发挥作用。作为从业十年的交易系统开发者&#xff0c;我越来越深刻地体会到&#xff1a;策略可视化不…

作者头像 李华
网站建设 2026/9/10 22:38:15

「AI Agent 全栈开发 50 讲」——从本地模型部署到多智能体系统,一年省 87 万 第 20 课 | 场景一交付:效果验证与价值复盘

第 20 课 | 场景一交付&#xff1a;效果验证与价值复盘从第 13 课到第 19 课&#xff0c;我们完成了场景一「竞品监控」的完整闭环。这节课&#xff0c;我们不写代码&#xff0c;而是算一笔账——这套系统到底值多少钱&#xff1f;省了多少时间&#xff1f;未来还能怎么进化&am…

作者头像 李华
网站建设 2026/9/10 22:38:00

CANN/GE 图编译公共基础结构约束文档

图编译公共基础结构约束文档 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch…

作者头像 李华
网站建设 2026/9/10 22:37:15

云边端一体化架构:低延迟与高可靠性的技术实现

1. 云边端一体化的架构本质云边端一体化架构正在重塑现代计算范式&#xff0c;这种三层协同模式从根本上改变了传统云计算中心化处理的局限性。其核心在于将计算能力按照业务需求动态分布在云端、边缘节点和终端设备上&#xff0c;形成弹性可扩展的分布式算力网络。从技术实现来…

作者头像 李华