DistributedLock
DistributedLock是一个.NET库,基于各种底层技术提供了健壮且易用的分布式互斥锁、读写锁和信号量。
使用DistributedLock,在多个应用程序/机器之间同步访问代码区域变得非常简单:
await using (await myDistributedLock.AcquireAsync())
{
// 我在这里持有锁
}
实现
DistributedLock包含基于各种技术的实现;你可以单独安装实现包,也可以直接安装DistributedLock NuGet包 ,这是一个"元"包,包含所有实现作为依赖项。注意每个包都根据SemVer独立进行版本控制。
- DistributedLock.SqlServer : 使用Microsoft SQL Server
- DistributedLock.Postgres : 使用PostgreSQL
- DistributedLock.MySql : 使用MySQL或MariaDB
- DistributedLock.Oracle : 使用Oracle
- DistributedLock.Redis : 使用Redis
- DistributedLock.Azure : 使用Azure blobs
- DistributedLock.ZooKeeper : 使用Apache ZooKeeper
- DistributedLock.FileSystem : 使用锁文件
- DistributedLock.WaitHandles : 使用操作系统全局
WaitHandle
(仅限Windows)
点击上面任何包的名称查看该实现的具体文档,或继续阅读适用于所有实现的通用文档。
DistributedLock.Core 包含公共代码和抽象,被所有实现引用。
同步原语
虽然所有实现都支持锁,但其他原语只被某些实现支持。有关详细信息,请参阅特定实现的文档页面。
基本用法
名称
由于分布式锁(和其他分布式同步原语)不局限于单个进程,它们的身份基于通过构造函数提供的名称。不同的底层技术对名称格式有不同的限制;然而,DistributedLock在很大程度上允许你忽略这些限制,通过转义/散列否则无效的名称。
Acquire
所有同步原语都支持相同的基本访问模式。Acquire
方法返回一个"句柄"对象,表示持有锁。当句柄被处置时,锁被释放:
var myDistributedLock = new SqlDistributedLock(name, connectionString); // 例如,如果我们使用SQL Server
using (myDistributedLock.Acquire())
{
// 我们在这里持有锁
} // using块的隐式Dispose()调用在这里释放它
TryAcquire
虽然Acquire
会阻塞直到锁可用,但还有一个TryAcquire
变体,如果无法获取锁(因为被其他地方持有),则返回null
:
using (var handle = myDistributedLock.TryAcquire())
{
if (handle != null)
{
// 我们获得了锁 :-)
}
else
{
// 其他人持有它 :-(
}
}
async支持
这两种方法的async
版本也受支持。当你编写异步代码时,这些方法是首选,因为它们在等待锁时不会消耗线程。如果你使用C#8或更高版本,你还可以异步处置句柄:
超时
await using (await myDistributedLock.AcquireAsync()) { ... }
此外,所有这些方法都支持可选的timeout
参数。timeout
决定了Acquire
在失败并抛出TimeoutException
之前等待的时间,以及TryAcquire
在返回null之前等待的时间。Acquire
的默认timeout
是Timeout.InfiniteTimeSpan
,而TryAcquire
的默认timeout
是TimeSpan.Zero
。
取消
最后,这些方法接受一个可选的CancellationToken
参数,允许通过取消来中断获取操作。请注意,一旦获取成功,这不会取消对锁的持有。
提供者
对于使用依赖注入的应用程序,DistributedLock的提供者使得将锁(或其他原语)的名称规范与其他设置(如数据库连接字符串)分离变得容易。例如,在ASP.NET Core应用中,你可能这样做:
// 在你的Startup.cs中:
services.AddSingleton<IDistributedLockProvider>(_ => new PostgresDistributedSynchronizationProvider(myConnectionString));
services.AddTransient<SomeService>();
// 在SomeService.cs中
public class SomeService
{
private readonly IDistributedLockProvider _synchronizationProvider;
public SomeService(IDistributedLockProvider synchronizationProvider)
{
this._synchronizationProvider = synchronizationProvider;
}
public void InitializeUserAccount(int id)
{
// 使用提供者构造一个锁
var @lock = this._synchronizationProvider.CreateLock($"UserAccount{id}");
using (@lock.Acquire())
{
// 做一些事情
}
// 或者,对于常见用例,扩展方法允许通过单个调用完成此操作
using (this._synchronizationProvider.AcquireLock($"UserAccount{id}"))
{
// 做一些事情
}
}
}
其他主题
贡献
欢迎贡献!如果你有兴趣为新的或现有的问题做出贡献,请通过问题评论让我知道,这样我可以帮助你入门并避免你浪费精力。
在本地使用存储库的设置步骤记录在这里。
发布说明
- 2.5
- 新增支持通过
DbDataSource
创建 Postgres 锁,这对使用NpgsqlMultiHostDataSource
的应用很有帮助。感谢 davidngjy 的实现! (#153, DistributedLock.Postgres 1.2.0) - 升级 Npgsql 到 8.0.3 以避免漏洞。感谢 @Meir017/@davidngjy 的实现! (#218, DistributedLock.Postgres 1.2.0)
- 修复启用连接保活时 Postgres 的竞态条件 (#216, DistributedLock.Core 1.0.7)
- 升级 Microsoft.Data.SqlClient 到 5.2.1 以避免漏洞 (#210, DistributedLock.SqlServer 1.0.5)
- 新增支持通过
- 2.4
- 新增支持在 Postgres 中使用
pg_advisory_xact_lock
进行事务范围锁定,这对使用 PgBouncer 很有帮助 (#168, DistributedLock.Postgres 1.1.0) - 改进对较新版本 StackExchange.Redis 的支持,尤其是在使用默认积压策略时 (#162, DistributedLock.Redis 1.0.3)。感谢 @Bartleby2718 的帮助!
- 放弃对
net461
的支持 (仍然支持net462
)。感谢 @Bartleby2718 的实现! - 减少库抛出的
UnobservedTaskException
的发生 (#192, DistributedLock.Core 1.0.6) - 将依赖项更新到没有已知问题/漏洞的现代版本 (#111/#177/#184/#185, 所有包)。感谢 @Bartleby2718 的帮助!
- 改进在 Linux/.NET 8 上
FileDistributedLock
的目录创建并发处理 (#195, DistributedLock.FileSystem 1.0.2) - 允许在 SQL Server 中使用事务范围锁而无需显式禁用多路复用 (#189, DistributedLock.SqlServer 1.0.4)
- 在 dndocs 上新的 API 文档。感谢 @NeuroXiq!
- 新的贡献者文档,指导如何在本地运行项目(参见 Contributing)
- 新增支持在 Postgres 中使用
- 2.3.4
- 支持 Npgsql 8.0 的 ExecuteScalar 重大变更 (#174, DistributedLock.Postgres 1.0.5)。感谢 @Kaffeetasse 的诊断和修复!
- 2.3.3
- 由于漏洞更新 Microsoft.Data.SqlClient (#149, DistributedLock.SqlServer 1.0.3)
- 由于漏洞更新 Oracle.ManagedDataAccess 和 Oracle.ManagedDataAccess.Core 的版本 (DistributedLock.Oracle 1.0.2)
- 2.3.2
- 解决使用短暂非零超时等待 Postgres 建议锁时底层的竞态条件 (#147, DistributedLock.Postgres 1.0.4)。感谢 @Tzachi009 报告和隔离这个问题!
- 2.3.1
- 修复关系数据库锁的
HandleLostToken
并发问题 (#133, DistributedLock.Core 1.0.5, DistributedLock.MySql 1.0.1, DistributedLock.Oracle 1.0.1, DistributedLock.Postgres 1.0.3, DistributedLock.SqlServer 1.0.2)。感谢 @OskarKlintrot 的测试! - 修复在 Redis 中尝试禁用自动延期时的误导性错误消息 (#130, DistributedLock.Redis 1.0.2)
- 修复取消
WaitHandle
的异步等待的并发问题 (#120, DistributedLock.WaitHandles 1.0.1)
- 修复关系数据库锁的
- 2.3.0
- 2.2.0
- 新增基于 MySQL/MariaDB 的实现 (#95, DistributedLock.MySql 1.0.0)。感谢 @theplacefordev 的测试!
- 2.1.0
- 新增基于 ZooKeeper 的实现 (#41, DistributedLock.ZooKeeper 1.0.0)
- 2.0.2
- 2.0.1
- 修复使用
WithKeyPrefix
的数据库时 Redis 锁的行为 (#66, DistributedLock.Redis 1.0.1)。感谢 @skomis-mm 的贡献!
- 修复使用
- 2.0.0 (另请参阅 从 1.x 迁移到 2.x)
- 重新设计了包结构,现在 DistributedLock 是一个伞形包,每种实现技术都有自己的包 (重大变更)
- 新增基于 Postgresql 的锁定 (#56, DistributedLock.Postgres 1.0.0)
- 新增基于 Redis 的锁定 (#24, DistributedLock.Redis 1.0.0)
- 新增基于 Azure blob 的锁定 (#42, DistributedLock.Azure 1.0.0)
- 新增基于文件的锁定 (#28, DistributedLock.FileSystem 1.0.0)
- 新增提供者类以改进 IOC 集成 (#13)
- 为程序集添加强名称。感谢 @pedropaulovc 的贡献! (#47, 重大变更)
- 使锁句柄实现
IAsyncDisposable
和IDisposable
#20, 重大变更) - 为所有同步原语公开与实现无关的接口 (例如
IDistributedLock
) (#10) - 新增
HandleLostToken
API 用于跟踪锁的底层连接是否断开 (#6, 重大变更) - 新增 SourceLink 支持 (#57)
- 移除
GetSafeName
API,改为默认安全命名 (重大变更) - 将 "SystemDistributedLock" 重命名为 "EventWaitHandleDistributedLock" (DistributedLock.WaitHandles 1.0.0)
- 停止支持 net45 (重大变更)
- 从
SqlDistributedLock
中移除DbConnection
和DbTransaction
构造函数,保留接受IDbConnection
/IDbTransaction
的构造函数 (#35, 重大变更) - 将返回
Task<IDisposable>
的方法改为返回ValueTask
,使得using (@lock.AcquireAsync()) { ... }
在没有await
的情况下不再编译 (#34, 重大变更) - 将
UpgradeableLockHandle.UpgradeToWriteLock
改为返回void
(#33, 重大变更) - 对所有目标框架默认切换到 Microsoft.Data.SqlClient (重大变更)
- 将所有锁定实现更改为非可重入 (重大变更)
- 1.5.0
- 通过 Microsoft.Data.SqlClient 添加跨平台支持 (#25)。此功能适用于 .NET Standard >= 2.0。感谢 @alesebi91 帮助实现和测试!
- 添加 C#8 可空注释 (#31)
- 修复连接多路复用中的小错误,该错误可能导致更多锁争用 (#32)
- 1.4.0
- 1.3.1 小修复,避免在基于事务的锁中"泄漏"隔离级别更改 ([#