根本原因是未阻塞主goroutine,dapr.run()仅注册组件并启动服务但不阻塞主线程,需用select{}或signal.notify显式等待;业务逻辑应作为回调传入或在独立goroutine中启动。

为什么 dapr.Run() 启动后服务立刻退出
根本原因是没阻塞主 goroutine,Dapr 的 dapr.Run() 只注册组件、启动 gRPC/HTTP 服务,但不接管主线程生命周期。常见现象是日志里看到 Starting Dapr Runtime 然后进程直接退出。
- 必须显式阻塞:用
select{}或signal.Notify(<ch>, os.Interrupt, syscall.SIGTERM)</ch>等待信号 - 别在
dapr.Run()后直接写业务逻辑——它不会等你;业务应作为回调传入(如app.Run())或在独立 goroutine 中启动 - 若用
github.com/dapr/go-sdk的client.NewClient(),注意它不启动 runtime,只是 SDK 客户端,和dapr.Run()是两回事
调用其他服务时 client.InvokeMethod() 报错 rpc error: code = Unavailable desc = connection closed
这不是网络不通,而是 Dapr sidecar 没就绪或地址不对。Dapr SDK 默认连本地 localhost:3500,但实际 sidecar 地址由 DAPR_HTTP_PORT 或 --dapr-http-port 决定,K8s 环境下更可能是 http://localhost:<port></port> 被 DNS 解析失败。
- 启动服务前先确认 sidecar 已运行:
dapr run --app-id order --app-port 8080 -- go run main.go,不要跳过dapr run - 代码里初始化 client 时,显式指定地址:
client.NewClientWithPort("3501")(对应--dapr-http-port 3501) - K8s 下别硬编码
localhost,改用fmt.Sprintf("http://%s:%s", os.Getenv("DAPR_HTTP_HOST"), os.Getenv("DAPR_HTTP_PORT")) - 错误信息里的
Unavailable通常意味着 HTTP 连接被拒绝,不是业务层报错,优先查 sidecar 日志:dapr logs -k -a order
状态存储用 client.SaveState() 写进去,但 client.GetState() 总返回空
有三个高频原因:store 名字拼错、key 不一致、state store 组件没正确定义。Dapr 不校验 store 是否真实存在,写操作会静默成功,读则返回空字节。
- 检查
components/statestore.yaml中的metadata.name,确保和SaveState(ctx, "mystore", key, ...)第二个参数完全一致(大小写敏感) - key 是字符串,不是 struct;如果传了 struct,SDK 会序列化成 JSON 字符串,但读的时候用原始 key 就对不上
- 本地开发用 Redis 作 state store 时,确认 Redis 容器已启动且
redis.yaml里redisHost指向host.docker.internal:6379(Mac/Win),Linux 需用docker network inspect dapr_default查宿主机 IP - 默认使用
ETAG并发控制,首次写无 etag 可忽略,但后续更新需带上上次返回的Etag字段,否则可能被拒绝
Pub/Sub 订阅消息后,handler 函数没被调用
不是代码没写对,而是 topic 名称、订阅配置、Dapr CLI 启动参数三者没对齐。Dapr 的 pub/sub 是声明式订阅:应用只暴露 HTTP handler,真正绑定靠 component 文件 + CLI 参数驱动。
立即学习“go语言免费学习笔记(深入)”;
- handler 路由必须是
/dapr/subscribe(GET),返回 JSON 数组,内容是{"pubsubname":"pubsub","topic":"orders","route":"/orders"},少一个字段都不行 - CLI 启动时必须加
--components-path ./components,且components/pubsub.yaml中metadata.name必须和上面 JSON 里的pubsubname一致 - handler 接收 POST 的路径(如
/orders)必须和订阅声明中route完全相同,且应用要监听:3000(默认)并允许 Dapr sidecar 调用 - 别在 handler 里 panic 或没 return response,Dapr 收不到 200 就认为消费失败,会反复重投
Dapr 的“自动”背后全是显式契约:sidecar 地址、store 名、topic 名、路由路径,任何一处大小写或拼写偏差都会让整条链路静默失效。最省时间的做法是每次改完 component 或代码,先跑一遍 dapr status -k(K8s)或 dapr list(本地),盯着 sidecar 状态和组件加载日志看。










