多区域Azure WebJob TimerTrigger单实例锁实现方案咨询
多区域Azure WebJob单实例定时触发解决方案
我在多区域部署了Timer触发的Azure WebJob,指定调度时间到后所有区域实例会同时触发。试过用Singleton特性和"is_singleton": true配置,但问题没解决,各区域实例还是同时跑。另外发现Singleton特性已经不适用于这个场景,而且Azure Blob存储里也看不到生成的锁文件,想找其他实现方式,或者Azure WebJob SDK有没有内置的解决方案。
下面是可行的解决办法:
一、手动用Azure Blob存储实现分布式锁
WebJob内置的Singleton在多区域场景下失效,我们可以借助Azure Blob的租约机制自己实现分布式锁,保证同一时间只有一个实例能拿到锁并执行任务。
实现步骤:
- 先添加
Azure.Storage.BlobsNuGet包,用来操作Blob存储。 - 在Function里注入
BlobContainerClient,所有区域的WebJob要共用同一个Azure存储账户,这样锁才能跨区域生效。 - 执行任务前,尝试获取指定Blob的排他租约,租约时长设为任务预计执行时间加缓冲时间。
- 拿到租约就执行任务,执行完释放租约;拿不到就直接退出,不执行任务。
示例代码:
修改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("启动了单实例定时任务编排器"); } }
三、排查原有配置失效的原因
settings.job里的is_singleton: true只对连续运行的WebJob生效,如果你部署的是触发式WebJob,这个配置根本不起作用。你的代码是手动调用CallAsync,属于触发式,所以这个配置无效。- WebJob的
[Singleton]特性默认是进程内的锁,只能限制同一机器上的多个实例,跨区域场景下完全没用,这也是你看不到Blob锁文件的原因——它本来就不会生成跨区域的锁文件。
内容的提问来源于stack exchange,提问作者Mahindar Boregam
相关产品推荐
相关产品推荐

