问题描述
把 Azure App Service 扩展到 3 个实例后,WebJob 会不会也同时运行 3 份?
如果每天定时生成一份报表,会不会生成 3 份?
在门户里手动点击一次 Run,又会由哪个实例执行?
答案取决于 WebJob 的类型,不能仅根据 App Service 的实例数量判断。
持续型默认在所有应用实例上运行;
手动触发和定时触发每次由一个实例执行,不会因为应用有 3 个实例就广播执行 3 次。
这篇文章用同一个 PowerShell 脚本做对照实验,记录实例 ID、UTC 时间和运行批次,让"在哪个实例运行"可以直接从日志判断。
问题解答
门户中的三种常见用法是 Continuous 、Triggered /Manual 和 Triggered /Scheduled。
微软文档明确说明,Scheduled 也是 Triggered,只是触发来源从手动操作变成了时间计划。
假设目标应用稳定运行在 3 个实例上,行为如下:
| 使用方式 | 底层类型 | 多实例下的运行方式 | 3 个实例时的稳定状态预期 |
|---|---|---|---|
| 持续运行,Multi Instance | Continuous | 默认在应用的每个实例上启动任务 | 3 份任务进程持续运行 |
| 持续运行,Single Instance | Continuous | 配置 is_singleton: true 后,只在一个实例运行该任务 |
1 份任务进程持续运行 |
| 手动触发 | Triggered | 每次触发由平台选择一个实例执行 | 点击一次 Run,启动一份执行,不是三份 |
| 定时触发 | Triggered | 每个计划触发点由一个实例执行 | 正常情况下,每个触发点启动一份执行,不是三份 |
这里的"一个实例"不意味着"永远固定在同一台机器"。下一次触发、实例替换或持续型单实例任务接管后,执行实例可能发生变化。
平台也不承诺手动触发会依次轮询 A、B、C,所以连续点击几次却一直落在 A 上,并不违反上述规则。
实验准备
在同一段PowerShell脚本中,输出当前webjob运行的时间,实例ID等信息,以判断它是多实例运行还是单实例运行。
param(
[ValidateRange(1, 3600)][int]$HeartbeatSeconds = 10,
[ValidateRange(0, 3600)][int]$WorkSeconds = 10,
[ValidateRange(0, 10000)][int]$MaxHeartbeats = 0,
[string]$OutputRoot
)
$ErrorActionPreference = 'Stop'
$instance = $env:WEBSITE_INSTANCE_ID
if (-not $instance) { $instance = 'local-not-azure' }
$job = $env:WEBJOBS_NAME
if (-not $job) { $job = 'local-demo' }
$type = $env:WEBJOBS_TYPE
if (-not $type) { $type = 'triggered' }
$session = [guid]::NewGuid().ToString('N')
if (-not $OutputRoot) {
if ($env:WEBSITE_INSTANCE_ID -and $env:HOME) {
$OutputRoot = Join-Path $env:HOME 'data\webjob-instance-demo'
} else {
$OutputRoot = Join-Path ([IO.Path]::GetTempPath()) 'webjob-instance-demo'
}
}
$safeJob = $job -replace '[^a-zA-Z0-9_-]', '_'
$safeInstance = $instance -replace '[^a-zA-Z0-9_-]', '_'
$directory = Join-Path (Join-Path $OutputRoot $safeJob) $safeInstance
$null = New-Item -ItemType Directory -Path $directory -Force
$logPath = Join-Path $directory "$session.jsonl"
function Write-Record([string]$EventName) {
$record = [ordered]@{
utc = [DateTime]::UtcNow.ToString('o')
event = $EventName
job = $job
type = $type
instance = $instance
computer = $env:COMPUTERNAME
processId = $PID
session = $session
platformRunId = $env:WEBJOBS_RUN_ID
}
$line = $record | ConvertTo-Json -Compress
Write-Output $line
# One file per process session: instances never append to the same file.
Add-Content -LiteralPath $logPath -Value $line -Encoding UTF8
}
function Test-Shutdown {
return ($env:WEBJOBS_SHUTDOWN_FILE -and
(Test-Path -LiteralPath $env:WEBJOBS_SHUTDOWN_FILE))
}
function Wait-OrShutdown([int]$Seconds) {
for ($i = 0; $i -lt $Seconds; $i++) {
if (Test-Shutdown) { return }
Start-Sleep -Seconds 1
}
}
Write-Record 'START'
if ($type -eq 'continuous') {
$count = 0
while (-not (Test-Shutdown)) {
Write-Record 'HEARTBEAT'
$count++
# A finite limit is for LOCAL tests only. Azure continuous jobs restart.
if ($MaxHeartbeats -gt 0 -and $count -ge $MaxHeartbeats) { break }
Wait-OrShutdown $HeartbeatSeconds
}
} else {
# Simulate a short task. This delay is not an exactly-once guarantee.
Wait-OrShutdown $WorkSeconds
}
if (Test-Shutdown) { Write-Record 'STOP_REQUESTED' }
else { Write-Record 'END' }
把以上内容复制到文件 run.ps1 中,然后分别打包为四个zip文件:continuous-multi.zip , continuous-single.zip ,manual.zip 和 scheduled.zip。
然后通过App Service的Web Job门户分别创建四个web job,参考在门户上创建WebJob的文档:https://docs.azure.cn/zh-cn/app-service/quickstart-webjobs?tabs=windows#add-the-webjob-in-the-azure-portal
实验结果
1:**continuous-multi ,**连续运行的多实例部署结果为,在3个实例上,这个webjob在每一个实例上都在执行。
2:**continuous-single,**连续运行的单实例部署结果为,在3个实例的环境中,这个webjob只在一个实例上执行。
3:manual,手动触发的webjob,在3个实例的环境中,每次只在一个实例上执行。
4:scheduled,按时触发的webjob,在3个实例的环境中,每次只在一个实例上执行。只是,后一次执行的实例可能发生变化。
参考资料
应用服务 WebJobs 概述 : https://docs.azure.cn/zh-cn/app-service/overview-webjobs
How would I scale a timer triggered webjob on an Azure App service to multiple app service instances? https://learn.microsoft.com/en-us/answers/questions/1689283/how-would-i-scale-a-timer-triggered-webjob-on-an-a