并发节流靠 host.json
的 extensions.queues
和 functions
配置
Azure Functions 默认不限制单实例内的并发执行数,尤其对队列触发器(如
QueueTrigger)容易因消息积压导致线程耗尽或下游服务被打爆。节流必须显式配置,核心是两个地方:
host.json中的
extensions.queues.maxPollingInterval控制轮询频率,但不直接限并发 真正限制并发的关键是
extensions.queues.batchSize(默认 16)和
extensions.queues.newBatchThreshold(默认 8)——前者决定每次拉取多少条消息进内存,后者决定剩余多少条时才拉下一批;二者共同影响实际并行处理的消息数 函数级并发上限由
functions.[functionName].configuration.maxConcurrentCalls控制(仅适用于 Service Bus 和 Event Hubs 触发器),
QueueTrigger不支持该字段,只能靠
batchSize间接控制 若用的是 .NET Isolated 进程模型,还要注意
WorkerOptions.MaxDegreeOfParallelism(默认 -1,即不限),它会覆盖 host.json 中的部分行为,需统一设为合理值(如 4–8)
实例扩展由 Consumption / Premium / Dedicated 三种计划决定
并发节流只是单实例内的“软控制”,真正影响吞吐的是底层实例数量——这完全取决于你选的托管计划:
Consumption Plan:自动扩缩容,但冷启动明显;最大实例数受区域配额限制(如 East US 默认 200 实例),且每实例最多运行batchSize条队列消息(即并发上限 = 实例数 ×
batchSize) Premium Plan:支持预热实例(
preWarmedInstanceCount)、VNET 集成、更高内存/CPU;扩缩逻辑更平滑,但扩缩延迟仍存在(约 10–30 秒),且
maxScaleOutInstances可手动设上限(避免突发账单) Dedicated (App Service) Plan:无自动扩缩,全靠你手动调实例数;适合稳定流量或需长连接/状态保持的场景;此时并发能力 = 实例数 × 单实例并发数(由
batchSize+ 线程池大小共同决定)
注意:所有计划下,函数实例的生命周期都与请求无关——Consumption Plan 的实例可能在空闲 20 分钟后被回收,而 Dedicated Plan 的实例常驻。
host.json
示例:平衡吞吐与稳定性
以下配置适用于中等负载的队列函数(.NET 6+ Isolated 模式),兼顾响应速度与下游压力:
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
},
"extensions": {
"queues": {
"maxPollingInterval": "00:00:02",
"visibilityTimeout": "00:00:30",
"batchSize": 4,
"newBatchThreshold": 2,
"maxDequeueCount": 5
}
},
"functions": {
"ProcessOrder": {
"configuration": {
"maxConcurrentCalls": 4
}
}
}
}
说明:
batchSize设为 4 是为了降低单实例资源争用;
maxPollingInterval缩短到 2 秒加快响应;
maxConcurrentCalls对 QueueTrigger 无效,但保留可避免未来迁移到 Service Bus 时遗漏配置。
容易被忽略的线程池与异步陷阱
即使配置了
batchSize,若函数体内用了
Task.Wait()、
Result或未 await 的 I/O 操作,会阻塞线程池线程,导致后续消息无法及时处理——这不是配置问题,而是代码写法问题: 所有 I/O 操作(HTTP 调用、数据库查询、Blob 存储)必须用
await,禁止同步等待 .NET Isolated 模式下,
ThreadPool.SetMinThreads无效,不能靠调大最小线程数来“补救”阻塞行为 若必须调用同步 SDK(如旧版 Storage SDK),应包装为
Task.Run(() => { ... }),但这是权宜之计,优先升级到异步 SDK
使用 Application Insights 查看 requests/dependencies的平均持续时间与失败率,比单纯看实例数更能暴露真实瓶颈
最棘手的情况是:你调高了
batchSize,但函数里一个
HttpClient实例被多消息复用且没设超时,结果所有并发请求卡在 DNS 解析或连接池等待上——这种问题不会报错,只会让吞吐骤降。
