☰
Workflow Core 基于 SQL Server sp_getapplock 的分布式锁提供程序实战指南
2026/10/12 1:33:58 网站建设 项目流程
  • 后端
  • 工作流自动化
  • 流程编排

【免费下载链接】workflow-core

Lightweight workflow engine for .NET Standard

项目地址:https://gitcode.com/gh_mirrors/wo/workflow-core
点击查看免费下载

本指南围绕 Workflow Core 官方提供程序WorkflowCore.LockProviders.SqlServer展开,讲解如何利用 SQL Server 内置的sp_getapplock/sp_releaseapplock存储过程实现分布式锁管理器(DLM,Distributed Lock Manager),从而支撑多节点集群并发处理工作流。读完本文,你将掌握该提供程序的安装、配置、底层锁机制、资源命名与返回码语义,并能在自己的 ASP.NET Core / 控制台应用中组合队列提供程序搭建真正可水平扩展的 Workflow Core 集群。

为什么需要分布式锁:从单节点到集群

Workflow Core 默认在单节点模式下运行。从 WorkflowOptions 的源码可以看到,其内置的锁与队列均为进程内实现:LockFactory默认构造SingleNodeLockProvider,QueueFactory默认构造SingleNodeQueueProvider。SingleNodeLockProvider 内部只是一个受lock保护的HashSet<string>,锁只在当前进程内有效。

这意味着:当多个节点同时处理队列中的同一个工作流时,内存锁无法跨进程互斥,两个节点可能同时取出并执行同一个工作流实例,导致状态竞争、重复执行与数据损坏。而 multi-node-clusters 文档 明确指出:要运行多节点集群,必须同时配置外部队列提供程序与分布式锁管理器来协调各节点。

WorkflowCore.LockProviders.SqlServer正是官方提供的分布式锁管理器实现:它以 SQL Server 为锁的权威存储,通过应用锁(Application Lock)实现跨节点互斥,让多个节点可以安全地共同消费同一个工作流队列。

安装提供程序

通过 NuGet 安装官方包:

PM> Install-Package WorkflowCore.LockProviders.SqlServer

该包面向netstandard2.0(见 WorkflowCore.LockProviders.SqlServer.csproj),因此可被 .NET Standard 2.0 及以上版本的宿主项目引用。它依赖Microsoft.Data.SqlClient(当前仓库锁定版本为 5.1.5),并通过项目引用依赖核心库WorkflowCore,无需额外手工配置。

快速配置:一行代码接入

在构建服务提供程序(通常为依赖注入容器)时,调用UseSqlServerLocking扩展方法即可替换默认的锁实现:

services.AddWorkflow(x => x.UseSqlServerLocking("connection string"));

这里的"connection string"即指向你 SQL Server 实例的 ADO.NET 连接字符串。扩展方法定义在 ServiceCollectionExtensions.cs,其实现非常简洁:

public static WorkflowOptions UseSqlServerLocking(this WorkflowOptions options, string connectionString) { options.UseDistributedLockManager(sp => new SqlLockProvider(connectionString, sp.GetService<ILoggerFactory>())); return options; }

它通过 WorkflowOptions.UseDistributedLockManager 将LockFactory替换为SqlLockProvider;随后在 ServiceCollectionExtensions.AddWorkflow 中,该工厂被注册为IDistributedLockProvider单例。所有后台任务与控制器都会从容器解析这个单例并使用它。

配合持久化提供程序(例如 WorkflowCore.Persistence.SqlServer)和队列提供程序(例如 WorkflowCore.QueueProviders.SqlServer 或 RabbitMQ)一起注册,即可形成完整的集群化配置:

services.AddWorkflow(x => { x.UseSqlServerLocking("Server=...;Database=...;User Id=...;Password=...;"); x.UseSqlServerPersistence("Server=...;Database=...;User Id=...;Password=...;"); // x.UseQueueProvider(...); // 外部队列,如 SQL Server / RabbitMQ / Redis 队列 });

底层原理:SqlLockProvider 如何工作

锁接口契约

所有分布式锁提供程序都实现 IDistributedLockProvider 接口,其核心方法为:

  • Task<bool> AcquireLock(string Id, CancellationToken cancellationToken):尝试获取指定资源 ID 的锁,成功返回true;
  • Task ReleaseLock(string Id):释放指定资源 ID 的锁;
  • Task Start()/Task Stop():生命周期钩子,由WorkflowHost在启动/停止时调用(见 WorkflowHost.cs 与#L139)。SqlLockProvider对这两个方法直接返回已完成的任务,无需额外初始化。

sp_getapplock 的参数语义

SqlLockProvider.cs 是核心实现,它把每个工作流资源映射为一条 SQL Server 应用锁。获取锁时执行sp_getapplock存储过程,关键参数如下:

参数取值说明
@Resource"wfc:{Id}"锁资源名称,以固定前缀wfc(Workflow Core 缩写)加冒号再拼接资源 ID,避免与其他应用的应用锁冲突
@LockOwner"Session"锁归属范围为当前数据库会话;连接断开时锁自动释放,防止节点崩溃后锁永久残留
@LockMode"Exclusive"排他锁,同一时刻只能有一个节点持有
@LockTimeout0获取锁的等待超时(毫秒)。为0表示不等待,立即返回获取结果

构造函数中还通过SqlConnectionStringBuilder对连接字符串做了两处增强(见 SqlLockProvider.cs):强制开启连接池Pooling = true,并把ApplicationName设置为"Workflow Core Lock Manager",便于在 SQL Server 侧诊断连接来源。

返回码语义:谁抢到了锁?

sp_getapplock通过RetVal返回码告知结果,SqlLockProvider逐一处理(对应 SqlLockProvider.cs):

返回码含义提供程序行为
>= 0锁获取成功记录连接并返回true(表示本次会话持锁成功)
-1请求超时(本次因LockTimeout = 0通常表现为立即失败)记录 Debug 日志,返回false
-2请求被取消记录 Debug 日志,返回false
-3被选为死锁牺牲者记录 Debug 日志,返回false
-999参数错误或其他内部错误记录 Error 日志,返回false

只有返回码非负时才认为持锁成功,否则关闭连接并放弃本次处理。

锁的生命周期:从持锁到释放

  • 持锁期间:成功的连接被保存在进程内字典_locks[Id]中,后续释放锁时直接复用同一连接调用sp_releaseapplock(同样以Session为@LockOwner)。若sp_releaseapplock返回码为负,则记录错误日志。
  • 进程内互斥:AcquireLock与ReleaseLock外围均有AutoResetEvent _mutex保护,保证同一节点内对锁字典与连接集合的并发访问是线程安全的。
  • 容错设计:由于锁归属于数据库会话,即使节点进程崩溃、连接断开,SQL Server 也会自动释放该锁,不会出现"锁永远无法释放"的僵局;同时每次获取锁都新建独立连接,天然避免了会话复用带来的误释放风险。

分布式锁在 Workflow Core 中的实际应用场景

从源码结构看,IDistributedLockProvider被注入到多个核心服务中,锁的粒度与资源 ID 设计如下:

  • WorkflowConsumer:工作流队列消费者。出队后先AcquireLock(itemId)(即工作流实例 ID)再执行;若获取失败则直接跳过本次,说明该实例正被其他节点处理。执行完成后在finally中释放锁并清理灰名单。
  • EventConsumer:事件队列消费者。事件出队时对"evt:{itemId}"加锁,唤醒订阅时对订阅所属的sub.WorkflowId加锁,防止多个节点同时投递同一事件。
  • RunnablePoller:轮询器通过"poll runnables"、"unprocessed events"、"poll-commands"等全局资源 ID 加锁,保证同一时刻只有一个节点在轮询持久化存储并派发可执行实例。
  • WorkflowController:启动、挂起、恢复、终止工作流等管理操作均以workflowId加锁,避免管理操作与消费者并发修改同一实例。
  • ActivityController:活动订阅以"sub:{subscription.Id}"加锁,协调分布式活动令牌的领取。
  • SyncWorkflowRunner:同步执行器同样基于锁保证同步执行的互斥性。

这些场景印证了分布式锁的两种粒度:实例级(按 workflowId / eventKey 精确互斥同一资源)与全局级(固定资源 ID 实现"主节点选举"式互斥),二者共同保证集群中的并发安全。

集群部署与配置注意事项

  1. 必须搭配外部队列提供程序:锁只解决"谁先执行"的互斥问题,不解决"工作项如何在节点间分发"的问题。集群化需要同时配置外部队列(如仓库内的 WorkflowCore.QueueProviders.SqlServer、WorkflowCore.QueueProviders.RabbitMQ 等),详见 multi-node-clusters 文档。
  2. SQL Server 连接要求:提供程序需要能够连接到你指定的 SQL Server 实例并具备执行sp_getapplock/sp_releaseapplock的权限(public数据库角色默认即可调用这两类系统存储过程)。它不要求额外的表或初始化脚本,也不依赖 Workflow Core 自身的持久化表。
  3. 锁超时行为:由于@LockTimeout = 0,获取锁采用"立即失败"而非阻塞等待的策略;消费者获取锁失败后会跳过本次轮询、等待下一个轮询周期重试。这与 WorkflowOptions.PollInterval(默认 10 秒)共同决定了集群的调度节奏。
  4. 资源前缀隔离:所有锁资源都以wfc:为前缀,多个 Workflow Core 集群若共享同一个 SQL Server 实例,可通过前缀天然隔离;若需进一步区分,可考虑使用不同数据库。

总结

WorkflowCore.LockProviders.SqlServer是一个零依赖外部中间件(如 Redis、ZooKeeper)的分布式锁方案——只要你的基础设施中已有 SQL Server,就可以用一行UseSqlServerLocking(connectionString)将 Workflow Core 从单节点升级为多节点集群。其底层借助 SQL Server 成熟的应用锁机制(sp_getapplock/sp_releaseapplock,会话级锁所有权)实现了跨节点的互斥与崩溃自动释放,配合外部队列提供程序即可支撑工作流引擎的水平扩展。

  • 后端
  • 工作流自动化
  • 流程编排

【免费下载链接】workflow-core

Lightweight workflow engine for .NET Standard

项目地址:https://gitcode.com/gh_mirrors/wo/workflow-core
点击查看免费下载
上一篇:Airbyte Orb 数据源连接器深度解析:基于 Low-Code CDK 的声明式实现与增量同步实战
下一篇:WinUtil:Windows系统管理的终极免费工具,3分钟快速配置新电脑

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询