dapr v1.17.14 版本更新介绍
发布日期: 2026-09-10
版本号: v1.17.14
Dapr 1.17.14 修复了一个问题:可插拔发布/订阅组件接收到的消息原先会逐条串行分发给应用,接收循环阻塞在处理函数上,必须等上一条消息处理并确认后才接收下一条,导致每个订阅同时在途的消息只有一条,吞吐上限等于单条消息延迟的倒数。发布端不受影响,因此订阅者会逐渐落后于发布者,积压持续增长却不记录任何错误。批量订阅也随之退化,因为运行时是靠并发处理调用来聚合批量的,同一时刻只有一个调用方,配置的 maxMessagesCount 永远达不到,每条消息都变成独立的应用请求。受影响的是 v1.16.0 及以后通过可插拔组件消费消息的用户:以 RabbitMQ 可插拔组件按每秒 5000 条发布 30 万条消息实测,消费从每秒 5004 条降至 3026 条,p95 端到端延迟从 15 毫秒升到 37945 毫秒,批量批均大小从 10.1 条塌缩到 1 条;吞吐上限随处理耗时变化,处理函数耗时 60 毫秒时上限约每秒 16 条,与 broker 和组件能力无关。内置发布/订阅组件不受影响。根因是一次原本为防止优雅停机时在途输入绑定事件被取消的改动带来的连带损伤:该改动删除了 pullMessages 用来等待在途处理函数的 WaitGroup(当时运行时已在 pkg/runtime/subscription 自行跟踪在途消息),连同分发 goroutine 一起合并掉了,而输入绑定保留了并发分发及其用于保证并发安全的 safeSend 互斥锁。修复后消息恢复并发分发,沿用输入绑定已有的方式,使单个订阅可同时有多条消息在途;流上的发送仍由现有互斥锁串行;关闭流之前会先等待在途处理函数结束,使订阅拆除时仍在运行的处理函数能确认已处理完的消息,而不是留给 broker 重新投递。优雅停机语义不变:排空、暂停和截止时间仍由 pkg/runtime/subscription 负责,按处理函数而非流 goroutine 计数。
更新内容 (中文)
Dapr 1.17.14
本次更新包含以下问题修复:
可插拔发布/订阅组件一次向应用投递一条消息
问题
从可插拔发布/订阅组件接收到的消息被串行分发给应用。 接收循环阻塞在处理器上——处理器调用应用、等待应用响应,然后才确认消息,因此在前一条消息被完全处理之前无法接收下一条消息。 每个订阅同时在途的消息被限制为一条,其吞吐上限变成了单条消息延迟的倒数。
发布不受影响,因此订阅者会逐渐落后于它此前还能跟上的发布者,且队列不断增长而没有任何错误日志。
批量订阅也随之退化。
运行时是通过并发的处理器调用来组装批量批次的,因此一次只有一个调用者时,配置的 maxMessagesCount 永远无法达到,每条消息都变成了单独的一次应用请求。
影响
如果你在 v1.16.0 或更高版本上通过可插拔发布/订阅组件消费消息,就会受到影响。 以一个 RabbitMQ 可插拔组件按 5,000 msg/s 发布 300,000 条消息进行测量,消费速率从 5,004 msg/s 下降到 3,026 msg/s,p95 端到端延迟从 15 ms 上升到 37,945 ms,批量批次从平均 10.1 条消息坍缩到恰好 1 条。 吞吐上限与处理器延迟成正比,因此应用处理器越慢,影响越严重:一个耗时 60 ms 的处理器无论 broker 或组件容量多大,吞吐都停在约 16 msg/s 附近。 内置发布/订阅组件不受影响。
根本原因
这种串行化是一次改动的附带损伤,该改动原本旨在防止在优雅关闭期间正在处理中的输入绑定事件被取消。
那次改动移除了 pullMessages 用来等待在途处理器的 WaitGroup——当时它已冗余,因为运行时自身会在 pkg/runtime/subscription 中跟踪在途消息——并顺带合并了分发 goroutine。
输入绑定在这同一改动中保留了其并发分发,而为保证并发分发安全而存在的 safeSend 互斥锁也在这里被保留了下来。
解决方案
消息重新恢复为并发分发,沿用输入绑定已有的方式,因此每个订阅可以同时有多条消息在途。
流上的发送仍由现有的互斥锁串行化。
在关闭流之前会先等待在途处理器完成,这样在订阅被拆除时仍在运行的处理器仍可确认它已处理过的消息,而不是将其留待重新投递。
优雅关闭语义保持不变:排空、暂停和截止时间都位于 pkg/runtime/subscription 中,统计的是处理器而非流 goroutine。
更新内容 (原始)
Dapr 1.17.14
This update contains the following bug fixes:
Pluggable pub/sub components delivered messages to the application one at a time
Problem
Messages received from a pluggable pub/sub component were dispatched to the application serially. The receive loop blocked on the handler, which invokes the application, waits for its response and only then acknowledges, so the next message could not be received until the previous one was fully handled. Each subscription was limited to a single message in flight and its ceiling became the inverse of the per-message latency. Publishing was unaffected, so a subscriber fell steadily behind a publisher it had previously kept up with, and the queue grew without any error being logged.
Bulk subscribe degraded along with it.
The runtime assembles bulk batches out of concurrent handler calls, so with one caller at a time the configured maxMessagesCount could never be reached and every message became its own application request.
Impact
You were affected if you consumed messages through a pluggable pub/sub component on v1.16.0 or later. Measured against a RabbitMQ pluggable component publishing 300,000 messages at 5,000 msg/s, consumption fell from 5,004 to 3,026 msg/s, p95 end-to-end latency rose from 15 ms to 37,945 ms, and bulk batches collapsed from a mean of 10.1 messages to exactly 1. The throughput ceiling tracks handler latency, so the slower the application handler, the more severe the effect: a handler taking 60 ms tops out near 16 msg/s regardless of broker or component capacity. Built-in pub/sub components were not affected.
Root Cause
The serialization was collateral damage of a change that set out to stop in-flight input binding events from being cancelled during graceful shutdown.
That change removed the WaitGroup pullMessages used to wait on in-flight handlers, by then redundant because the runtime tracks in-flight messages itself in pkg/runtime/subscription, and collapsed the dispatching goroutine along with it.
Input bindings kept their concurrent dispatch in that same change, and the safeSend mutex that exists to make concurrent dispatch safe was left in place here.
Solution
Messages are dispatched concurrently again, following the shape input bindings already use, so more than one message can be in flight per subscription.
Sends on the stream remain serialized by the existing mutex.
In-flight handlers are now waited on before the stream is closed, so a handler that is still running when the subscription is torn down can still acknowledge the message it has already processed instead of leaving it to be redelivered.
Graceful shutdown semantics are unchanged: draining, pausing and the deadline live in pkg/runtime/subscription and count handlers rather than stream goroutines.
下载链接
- daprd_darwin_amd64.tar.gz
- daprd_darwin_amd64.tar.gz.sha256
- daprd_darwin_arm64.tar.gz
- daprd_darwin_arm64.tar.gz.sha256
- daprd_linux_amd64-stablecomponents.tar.gz
- daprd_linux_amd64-stablecomponents.tar.gz.sha256
- daprd_linux_amd64.tar.gz
- daprd_linux_amd64.tar.gz.sha256
- daprd_linux_arm-stablecomponents.tar.gz
- daprd_linux_arm-stablecomponents.tar.gz.sha256
- daprd_linux_arm.tar.gz
- daprd_linux_arm.tar.gz.sha256
- daprd_linux_arm64-stablecomponents.tar.gz
- daprd_linux_arm64-stablecomponents.tar.gz.sha256
- daprd_linux_arm64.tar.gz
- daprd_linux_arm64.tar.gz.sha256
- daprd_windows_amd64.zip
- daprd_windows_amd64.zip.sha256
- grafana-actor-dashboard.json
- grafana-actor-dashboard.json.sha256
- grafana-sidecar-dashboard.json
- grafana-sidecar-dashboard.json.sha256
- grafana-system-services-dashboard.json
- grafana-system-services-dashboard.json.sha256
- injector_darwin_amd64.tar.gz
- injector_darwin_amd64.tar.gz.sha256
- injector_darwin_arm64.tar.gz
- injector_darwin_arm64.tar.gz.sha256
- injector_linux_amd64.tar.gz
- injector_linux_amd64.tar.gz.sha256
- injector_linux_arm.tar.gz
- injector_linux_arm.tar.gz.sha256
- injector_linux_arm64.tar.gz
- injector_linux_arm64.tar.gz.sha256
- injector_windows_amd64.zip
- injector_windows_amd64.zip.sha256
- operator_darwin_amd64.tar.gz
- operator_darwin_amd64.tar.gz.sha256
- operator_darwin_arm64.tar.gz
- operator_darwin_arm64.tar.gz.sha256
- operator_linux_amd64.tar.gz
- operator_linux_amd64.tar.gz.sha256
- operator_linux_arm.tar.gz
- operator_linux_arm.tar.gz.sha256
- operator_linux_arm64.tar.gz
- operator_linux_arm64.tar.gz.sha256
- operator_windows_amd64.zip
- operator_windows_amd64.zip.sha256
- placement_darwin_amd64.tar.gz
- placement_darwin_amd64.tar.gz.sha256
- placement_darwin_arm64.tar.gz
- placement_darwin_arm64.tar.gz.sha256
- placement_linux_amd64.tar.gz
- placement_linux_amd64.tar.gz.sha256
- placement_linux_arm.tar.gz
- placement_linux_arm.tar.gz.sha256
- placement_linux_arm64.tar.gz
- placement_linux_arm64.tar.gz.sha256
- placement_windows_amd64.zip
- placement_windows_amd64.zip.sha256
- scheduler_darwin_amd64.tar.gz
- scheduler_darwin_amd64.tar.gz.sha256
- scheduler_darwin_arm64.tar.gz
- scheduler_darwin_arm64.tar.gz.sha256
- scheduler_linux_amd64.tar.gz
- scheduler_linux_amd64.tar.gz.sha256
- scheduler_linux_arm.tar.gz
- scheduler_linux_arm.tar.gz.sha256
- scheduler_linux_arm64.tar.gz
- scheduler_linux_arm64.tar.gz.sha256
- scheduler_windows_amd64.zip
- scheduler_windows_amd64.zip.sha256
- sentry_darwin_amd64.tar.gz
- sentry_darwin_amd64.tar.gz.sha256
- sentry_darwin_arm64.tar.gz
- sentry_darwin_arm64.tar.gz.sha256
- sentry_linux_amd64.tar.gz
- sentry_linux_amd64.tar.gz.sha256
- sentry_linux_arm.tar.gz
- sentry_linux_arm.tar.gz.sha256
- sentry_linux_arm64.tar.gz
- sentry_linux_arm64.tar.gz.sha256
- sentry_windows_amd64.zip
- sentry_windows_amd64.zip.sha256