You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多区域Azure WebJob TimerTrigger单实例锁实现方案咨询

多区域Azure WebJob单实例定时触发解决方案

我在多区域部署了Timer触发的Azure WebJob,指定调度时间到后所有区域实例会同时触发。试过用Singleton特性和"is_singleton": true配置,但问题没解决,各区域实例还是同时跑。另外发现Singleton特性已经不适用于这个场景,而且Azure Blob存储里也看不到生成的锁文件,想找其他实现方式,或者Azure WebJob SDK有没有内置的解决方案。

下面是可行的解决办法:

一、手动用Azure Blob存储实现分布式锁

WebJob内置的Singleton在多区域场景下失效,我们可以借助Azure Blob的租约机制自己实现分布式锁,保证同一时间只有一个实例能拿到锁并执行任务。

实现步骤:

  1. 先添加Azure.Storage.Blobs NuGet包,用来操作Blob存储。
  2. 在Function里注入BlobContainerClient,所有区域的WebJob要共用同一个Azure存储账户,这样锁才能跨区域生效。
  3. 执行任务前,尝试获取指定Blob的排他租约,租约时长设为任务预计执行时间加缓冲时间。
  4. 拿到租约就执行任务,执行完释放租约;拿不到就直接退出,不执行任务。

示例代码:

修改Function.cs:

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Specialized;

public class Functions
{
    private readonly BlobContainerClient _blobContainerClient;
    // 锁Blob的名称,固定一个就行
    private const string LockBlobName = "webjob-distributed-lock";
    // 租约时长,根据任务实际运行时间调整,这里设5分钟
    private const int LeaseDurationSeconds = 300;

    public Functions(BlobContainerClient blobContainerClient)
    {
        _blobContainerClient = blobContainerClient;
    }

    [NoAutomaticTrigger]
    public async Task Run()
    {
        var lockBlobClient = _blobContainerClient.GetBlobClient(LockBlobName);
        var leaseClient = lockBlobClient.GetBlobLeaseClient();
        string leaseId = null;

        try
        {
            // 尝试10秒内获取租约,拿到后锁定5分钟
            leaseId = await leaseClient.AcquireAsync(TimeSpan.FromSeconds(LeaseDurationSeconds), null, TimeSpan.FromSeconds(10));
            
            if (!string.IsNullOrEmpty(leaseId))
            {
                Console.WriteLine("拿到分布式锁了,开始执行任务");
                // 这里写你的任务逻辑
            }
            else
            {
                Console.WriteLine("没拿到锁,直接退出");
                return;
            }
        }
        catch (Azure.RequestFailedException ex)
        {
            // 409错误表示锁已经被其他实例持有
            if (ex.Status == 409)
            {
                Console.WriteLine("锁被别的实例占了,退出");
                return;
            }
            // 其他异常正常抛出
            throw;
        }
        finally
        {
            // 不管任务成功失败,都尝试释放租约
            if (!string.IsNullOrEmpty(leaseId))
            {
                try
                {
                    await leaseClient.ReleaseAsync();
                }
                catch
                {
                    // 释放失败也没关系,租约到期会自动释放
                }
            }
        }
    }
}

修改Program.cs,配置Blob容器客户端:

var builder = new HostBuilder();
builder
    .ConfigureWebJobs((context, b) =>
    {
        b.AddAzureStorageCoreServices();
    })
    .ConfigureServices(services =>
    {
        // 注册BlobContainerClient,用WebJobs的存储连接字符串
        services.AddSingleton(sp =>
        {
            var config = sp.GetRequiredService<IConfiguration>();
            var connectionString = config.GetConnectionString("AzureWebJobsStorage");
            // 创建名为webjob-locks的容器,提前在存储账户建好也行
            var containerClient = new BlobContainerClient(connectionString, "webjob-locks");
            containerClient.CreateIfNotExists();
            return containerClient;
        });
    });

var host = builder.Build();
using (host)
{
    var jobHost = host.Services.GetService(typeof(IJobHost)) as JobHost;
    await host.StartAsync().ConfigureAwait(false);
    await jobHost.CallAsync("Run").ConfigureAwait(false);
    await host.StopAsync().ConfigureAwait(false);
}

二、迁移到Azure Functions + Durable Functions

如果可以切换到Azure Functions,Durable Functions的Singleton特性支持跨区域场景,能确保只有一个实例执行定时任务。

核心思路:

用Durable Orchestrator处理任务,然后用带[Singleton]特性的Timer Trigger函数启动Orchestrator,Durable的Singleton基于Azure存储实现分布式锁,天生支持跨区域。

示例代码:

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Extensions.Logging;

public class SingletonTimerFunctions
{
    // 编排器函数,执行任务逻辑
    [FunctionName("SingletonTaskOrchestrator")]
    public static async Task RunOrchestrator(
        [OrchestrationTrigger] IDurableOrchestrationContext context)
    {
        // 调用活动函数执行具体任务
        await context.CallActivityAsync("ExecuteTask", null);
    }

    // 活动函数,实际任务逻辑
    [FunctionName("ExecuteTask")]
    public static void ExecuteTask([ActivityTrigger] object input, ILogger log)
    {
        log.LogInformation("开始执行定时任务");
        // 这里写你的任务代码
    }

    // 定时触发函数,启动编排器,Singleton确保多区域仅一个实例触发
    [FunctionName("TimerTrigger_Singleton")]
    [Singleton]
    public static async Task TimerTrigger(
        [TimerTrigger("0 */5 * * * *")] TimerInfo myTimer,
        [DurableClient] IDurableOrchestrationClient starter,
        ILogger log)
    {
        await starter.StartNewAsync("SingletonTaskOrchestrator", null);
        log.LogInformation("启动了单实例定时任务编排器");
    }
}

三、排查原有配置失效的原因

  1. settings.job里的is_singleton: true只对连续运行的WebJob生效,如果你部署的是触发式WebJob,这个配置根本不起作用。你的代码是手动调用CallAsync,属于触发式,所以这个配置无效。
  2. WebJob的[Singleton]特性默认是进程内的锁,只能限制同一机器上的多个实例,跨区域场景下完全没用,这也是你看不到Blob锁文件的原因——它本来就不会生成跨区域的锁文件。

内容的提问来源于stack exchange,提问作者Mahindar Boregam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 22:20:41