C# 基于 CSRedis 实现安全分布式锁的 Lua 脚本原理与封装
在分布式系统中,基于 Redis 实现分布式锁是常见的并发控制手段。本文深入解析 C# 环境下使用 CSRedis 库构建安全分布式锁的实现细节,重点阐述如何利用 Lua 脚本保证“获取-删除”操作的原子性,从而杜绝锁被意外释放的风险。文中提供了经过简化的封装类代码,支持自定义 Token 管理及多实例连接。
分布式锁的基础原理
实现一个基础的分布式锁主要依赖 Redis 的原子命令。核心流程如下:
- 加锁:使用
SET key token NX EX seconds或SETNX命令。只有当 Key 不存在时才写入,同时设置过期时间以防止死锁。 - 判断锁:通过
GET key获取当前锁的值(Token)。 - 解锁:使用
DEL key删除锁。
虽然简单的命令组合看似可行,但在实际高并发场景中,直接执行 DEL 存在严重的隐患。如果锁设置了自动过期时间,当锁因超时自动释放后,另一个线程 A 成功获取了该锁。此时,第一个线程 B 由于网络延迟或处理缓慢,其之前发送的 DEL 命令才到达 Redis。这将导致线程 B 错误地释放了线程 A 持有的锁,造成并发冲突。
Lua 脚本确保原子性
为解决上述误删问题,必须保证“检查 Token”和“删除 Key”两个操作具有原子性。Redis 提供 EVAL 命令执行 Lua 脚本,脚本在 Redis 服务端串行执行,中间不会被其他命令插入。
标准的解锁 Lua 脚本逻辑如下:
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
该脚本首先获取 Key 对应的值,并与传入的 Token(ARGV[1])进行比对。仅当两者完全一致时,才执行删除操作并返回 1;否则返回 0,表示无法释放非本机持有的锁。这种方式允许开发者在任何地方以观察者身份,只要知道正确的 Token,即可安全判断锁的状态甚至辅助清理(需严格授权)。
C# 封装实现
以下代码展示了如何基于 CSRedis 库封装一个简易的分布式锁管理器。相比官方库,此实现简化了续租功能,并将 Token 的控制权交给调用者,同时保持 Key 的原生结构不加前缀,便于调试和监控。
第1步:定义管理器和基础工具方法
创建一个 CsRedisManager 类,使用 ConcurrentDictionary 管理多个 Redis 客户端实例,并提供基础的读写、过期及脚本执行方法。
using CSRedis;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace CsRedis.Helper
{
public class CsRedisManager
{
private ConcurrentDictionary<string, CSRedisClient> _serviceNameWithClient;
public void Init()
{
_serviceNameWithClient = new ConcurrentDictionary<string, CSRedisClient>();
}
public CSRedisClient GetRedisClient(string serviceName)
{
CSRedisClient result = null;
_serviceNameWithClient.TryGetValue(serviceName, out result);
return result;
}
public bool AddRedisClient(string serviceName, string connectStr)
{
CSRedisClient cSRedisClient = new CSRedisClient(connectStr);
return _serviceNameWithClient.TryAdd(serviceName, cSRedisClient);
}
// ... 省略 Set, Get, SetNx, Expire, Del 等基础包装方法 ...
// 这些方法内部均包含对 client 为空的校验及异常抛出
/// <summary>
/// 执行 Lua 脚本
/// </summary>
public object Eval(string serviceName, string script, string key, params object[] args)
{
var res = GetRedisClient(serviceName).Eval(script, key, args);
return res;
}
}
}
第2步:实现加锁与解锁核心逻辑
加锁直接使用 SetNx(带过期时间的 SET NX)。解锁则调用预先定义的 Lua 脚本。
public class CsRedisManager
{
// ... 前述字段和方法 ...
/// <summary>
/// 添加共享锁
/// </summary>
/// <param name="serviceName">Redis服务名称锁的Key唯一标识符(如GUID)毫秒数有效期
第3步:测试验证
在实际使用中,初始化管理器,添加 Redis 连接配置,然后模拟加锁和释放锁的过程。注意连接字符串中的密码和其他参数需根据实际环境调整。
using CsRedis.Helper;
using NUnit.Framework;
namespace TestProject
{
public class Tests
{
[Test]
public void TestLockUnlock()
{
var csRedisManager = new CsRedisManager();
csRedisManager.Init();
// 添加 Redis 客户端,请替换实际连接串
csRedisManager.AddRedisClient("TEST", "127.0.0.1:6379,password=YourPassword,
connectTimeout=1000,syncTimeout=10000,defaultDatabase=0");
var token = Guid.NewGuid().ToString();
var lockKey = "LOCKKEY1";
// 加锁,有效期20秒(传入毫秒数)
var isLocked = csRedisManager.AddLock("TEST", lockKey, token, 20 * 1000);
Assert.IsTrue(isLocked);
// 释放锁
var isReleased = csRedisManager.ReleaseLock("TEST", lockKey, token);
Assert.IsTrue(isReleased);
}
}
}
总结
通过在 C# 中结合 CSRedis 库与 Lua 脚本,可以有效构建具备高可靠性的分布式锁。关键在于利用 Token 机制区分不同线程/客户端持有的锁,并利用 Lua 脚本保证释放锁时的条件检查与执行原子性,从而避免由网络延迟导致的锁误删问题。以上就是基于 CSRedis 实现分布式锁的详细内容,更多关于 C# Redis 高级应用的技术资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













