返回首页

RedLock.NET:.NET 中的分布式锁

本文描述了使用 RedLock.NET 和 PostgreSQL 替代方案在 .NET 中实现分布式锁。比较了标准原语、Mutex、数据库锁。提供了 Kubernetes 中微服务的 SQL 示例和最佳实践

.NET 中的分布式锁:RedLock 与数据库方法
Advertisement 728x90

在 .NET 中使用 RedLock.NET 实现分布式锁

分布式锁为多服务实例系统中的关键代码段提供独占访问。这在 Kubernetes 中的微服务尤为重要,因为 Pod 或不同服务会竞争资源。主要目标是原子执行操作,例如更新数据库中的关键数据或调用外部 API。

典型场景:

  • 更改数据库中的业务关键值。
  • 向监管系统发送请求(例如,类似于 Chestny Znak 的系统)。
  • 同一微服务 Pod 之间或编排/编舞中的服务之间的同步。

标准的 .NET 原语如 SemaphoreSlimAsyncLocklock 不适用,因为实例物理分离。Mutex 在 Windows 上有效,但在异步代码和 Docker 容器中存在问题。

Google AdInline article slot

RedLock.NET 概述

RedLock.NET 是一个在 .NET 中实现 RedLock 算法的库。最新稳定版本为 2.3.2(2022)。该包通过了安全检查,无已知漏洞。作者积极在 GitHub 上维护问题。

RedLock 算法使用 Redis 集群实现带有 TTL 的分布式锁,即使在网络故障时也能确保安全。该库封装了创建、延长和释放锁的逻辑。

替代方案:通过数据库实现锁

PostgreSQL 中的锁表

经典方法是为锁创建一个专用表:

Google AdInline article slot
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 的新锁。
  • 如果锁已过期,则更新它。
  • 如果锁处于活动状态,则返回空结果。

要求:

Google AdInline article slot
  • 每个操作唯一的 resource
  • 唯一的 locked_by(GUID 或实例 ID + 线程 ID)。

隔离级别

SERIALIZABLE 可确保无额外表的情况下实现原子性,但会降低吞吐量。在企业环境中,由于负载原因,数据库管理员很少批准使用它。带有 READ COMMITTEDUPSERT 方法更受欢迎。

方法比较

| 方法 | 优点 | 缺点 |

|---------------|-----------------------|-------------------------------|

| .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

Advertisement 728x90

继续阅读