在 .NET 中使用 RedLock.NET 实现分布式锁
分布式锁为多服务实例系统中的关键代码段提供独占访问。这在 Kubernetes 中的微服务尤为重要,因为 Pod 或不同服务会竞争资源。主要目标是原子执行操作,例如更新数据库中的关键数据或调用外部 API。
典型场景:
- 更改数据库中的业务关键值。
- 向监管系统发送请求(例如,类似于 Chestny Znak 的系统)。
- 同一微服务 Pod 之间或编排/编舞中的服务之间的同步。
标准的 .NET 原语如 SemaphoreSlim、AsyncLock 或 lock 不适用,因为实例物理分离。Mutex 在 Windows 上有效,但在异步代码和 Docker 容器中存在问题。
RedLock.NET 概述
RedLock.NET 是一个在 .NET 中实现 RedLock 算法的库。最新稳定版本为 2.3.2(2022)。该包通过了安全检查,无已知漏洞。作者积极在 GitHub 上维护问题。
RedLock 算法使用 Redis 集群实现带有 TTL 的分布式锁,即使在网络故障时也能确保安全。该库封装了创建、延长和释放锁的逻辑。
替代方案:通过数据库实现锁
PostgreSQL 中的锁表
经典方法是为锁创建一个专用表:
CREATE TABLE locks (
resource VARCHAR(100) PRIMARY KEY,
locked_by VARCHAR(100),
locked_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ
);
带冲突处理的原子 INSERT:
INSERT INTO locks (resource, locked_by, expires_at)
VALUES (@resource, @locked_by, NOW() + (@timespan || ' seconds')::interval)
ON CONFLICT (resource) DO UPDATE
SET locked_by = EXCLUDED.locked_by,
expires_at = EXCLUDED.expires_at
WHERE locks.expires_at < NOW()
RETURNING locked_by;
逻辑:
- 如果锁不存在,则创建一个带有 TTL 的新锁。
- 如果锁已过期,则更新它。
- 如果锁处于活动状态,则返回空结果。
要求:
- 每个操作唯一的
resource。 - 唯一的
locked_by(GUID 或实例 ID + 线程 ID)。
隔离级别
SERIALIZABLE 可确保无额外表的情况下实现原子性,但会降低吞吐量。在企业环境中,由于负载原因,数据库管理员很少批准使用它。带有 READ COMMITTED 的 UPSERT 方法更受欢迎。
方法比较
| 方法 | 优点 | 缺点 |
|---------------|-----------------------|-------------------------------|
| .NET 原语 | 简单,低延迟 | 仅限单实例 |
| Mutex | 操作系统级同步 | 不适用于 Docker/Linux,异步问题 |
| 数据库锁 | 数据库集成 | 数据库开销,可扩展性问题 |
| RedLock.NET | 高可用性,TTL | Redis 依赖 |
数据库锁适用于低频简单场景。RedLock.NET 适用于高负载场景,提供强保证。
RedLock.NET 的实际实现
安装:dotnet add package RedLock.net。使用 Redis 端点创建一个 RedLockFactory:
var multiplexer = new ConnectionMultiplexer(...);
var factory = RedLockFactory.Create(...);
using var @lock = await factory.CreateLockAsync(resource, expiry);
if (@lock.IsAcquired) {
// critical section
}
关键参数:
resource:字符串标识符(Redis 键)。expiry:用于 TTL 的TimeSpan。- 法定人数:用于共识的 Redis 节点多数。
故障处理:库通过 IExtensibleLock 自动扩展长时间运行操作的锁。
扩展和最佳实践
- 使用 Redis Sentinel 或 Cluster 实现高可用性。
- 节点间时钟漂移 < 10ms。
- 有效时间 > 2 倍时钟漂移 + 操作时间。
- 监控锁获取失败率。
在生产环境中,与熔断器结合,用于关键代码段中的外部调用。
关键要点
- RedLock.NET 提供基于法定人数的安全性,无单点故障。
- 数据库锁更简单,但易受数据库过载影响,且不可扩展。
- 始终设置 TTL 以防僵尸锁。
- 在负载下测试,模拟网络分区。
- 对于 .NET Core 及更高版本,在容器中避免使用 Mutex。
— Editorial Team
暂无评论。