Azure Function(TimerTrigger):Azure文件共享文件复制的并发锁实现
解决Azure Function并发处理文件的锁定方案
针对Azure文件共享中多Function并发处理文件的问题,推荐两种可靠的锁定机制,避免重复处理同一文件:
方案一:使用Azure Files租约(Lease)锁定文件
Azure Files支持排他租约机制,获取租约后,其他进程无法修改、删除或获取该文件的租约,直到租约释放或过期。这是最官方的文件锁定方案。
修改后的代码示例:
for (int i = 0; i < 5; i++) { var file = results2.ToList()[i]; if (file is CloudFile cloudFile) { var fileName = cloudFile.Name; log.LogInformation($"Function({invocationId}) | Trying to acquire lease for File: {fileName}"); string leaseId = null; try { // 获取15分钟排他租约(时长可根据操作耗时调整) leaseId = await cloudFile.AcquireLeaseAsync(TimeSpan.FromMinutes(15), null); log.LogInformation($"Function({invocationId}) | Acquired lease for File: {fileName}, LeaseId: {leaseId}"); } catch (StorageException ex) { // 409状态码表示租约已被占用,直接跳过该文件 if (ex.RequestInformation.HttpStatusCode == 409) { log.LogInformation($"Function({invocationId}) | File {fileName} is locked by another process, skipping"); continue; } throw; // 其他异常正常抛出 } try { log.LogInformation($"Function({invocationId}) | Starting copy for File: {fileName}"); var copyFile = folderToDir.GetFileReference(fileName); await Task.WhenAll(copyFile.CreateAsync(cloudFile.Properties.Length), copyFile.StartCopyAsync(cloudFile)); log.LogInformation($"Function({invocationId}) | Copied File: {fileName}"); await cloudFile.DeleteAsync(); } finally { // 无论成功失败,确保释放租约,避免永久锁定 if (!string.IsNullOrEmpty(leaseId)) { try { await cloudFile.ReleaseLeaseAsync(leaseId); log.LogInformation($"Function({invocationId}) | Released lease for File: {fileName}"); } catch (Exception ex) { log.LogWarning($"Function({invocationId}) | Failed to release lease for {fileName}: {ex.Message}"); } } } } }
关键说明:
- 租约获取失败(409冲突)直接跳过,避免重复处理
finally块强制释放租约,防止因异常导致文件永久锁定- 租约时长需设置为足够完成复制+删除操作的时间
方案二:临时重命名文件(轻量替代方案)
利用Azure Files重命名操作的原子性,处理前将文件重命名为带标记的临时名称,重命名成功则表示获得处理权,失败则说明文件正在被其他进程处理。
修改后的代码示例:
for (int i = 0; i < 5; i++) { var file = results2.ToList()[i]; if (file is CloudFile cloudFile) { var fileName = cloudFile.Name; var tempFileName = $"{fileName}.processing"; var tempFile = cloudFile.ParentReference.GetFileReference(tempFileName); bool renameSuccess = false; log.LogInformation($"Function({invocationId}) | Trying to lock File: {fileName} via rename"); try { // 原子性重命名,成功则获得处理权 await cloudFile.RenameAsync(tempFileName); renameSuccess = true; log.LogInformation($"Function({invocationId}) | Locked File: {fileName} as {tempFileName}"); } catch (StorageException ex) { if (ex.RequestInformation.HttpStatusCode == 409) { log.LogInformation($"Function({invocationId}) | File {fileName} is being processed, skipping"); continue; } throw; } try { log.LogInformation($"Function({invocationId}) | Starting copy for File: {tempFileName} (original: {fileName})"); var copyFile = folderToDir.GetFileReference(fileName); await Task.WhenAll(copyFile.CreateAsync(tempFile.Properties.Length), copyFile.StartCopyAsync(tempFile)); log.LogInformation($"Function({invocationId}) | Copied File: {fileName}"); await tempFile.DeleteAsync(); } catch (Exception ex) { log.LogError($"Function({invocationId}) | Failed to process {fileName}: {ex.Message}"); // 处理失败时,将临时文件改回原名称,方便后续重试 if (renameSuccess) { await tempFile.RenameAsync(fileName); } } } }
关键说明:
- 重命名操作是原子性的,同一时间只有一个进程能成功
- 处理失败需将临时文件改回原名称,避免文件丢失
- 实现简单,无需管理租约,适合轻量级场景
额外注意事项
- 两种方案都需处理异常,确保资源(租约/文件状态)被正确恢复
- 可结合重试机制,但需限制重试次数,避免无限循环
- 若使用Azure Function消费计划,需确保函数执行超时时间覆盖操作总耗时
内容的提问来源于stack exchange,提问作者Peace and love
相关产品推荐
相关产品推荐

