using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using StackExchange.Redis; using WIDESEAWCS_RedisService.Connection; using WIDESEAWCS_RedisService.Options; namespace WIDESEAWCS_RedisService.Lock { public class RedisDistributedLockService : IDistributedLockService { private readonly IRedisConnectionManager _connectionManager; private readonly RedisOptions _options; private readonly ILogger _logger; // Lua脚本:安全释放锁(只有持有者才能释放) private const string ReleaseLockScript = @" if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; // Lua脚本:安全续期锁 private const string RenewLockScript = @" if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('pexpire', KEYS[1], ARGV[2]) else return 0 end"; public RedisDistributedLockService( IRedisConnectionManager connectionManager, IOptions options, ILogger logger) { _connectionManager = connectionManager; _options = options.Value; _logger = logger; } private string BuildKey(string key) => $"{_options.KeyPrefix}lock:{key}"; public string? AcquireLock(string lockKey, TimeSpan expiry, TimeSpan? waitTimeout = null) { var fullKey = BuildKey(lockKey); var token = Guid.NewGuid().ToString("N"); var db = _connectionManager.GetDatabase(); var deadline = DateTime.UtcNow + (waitTimeout ?? TimeSpan.Zero); do { if (db.StringSet(fullKey, token, expiry, When.NotExists)) return token; if (waitTimeout == null) return null; Thread.Sleep(50); } while (DateTime.UtcNow < deadline); return null; } public bool ReleaseLock(string lockKey, string lockToken) { var db = _connectionManager.GetDatabase(); var result = db.ScriptEvaluate( ReleaseLockScript, new RedisKey[] { BuildKey(lockKey) }, new RedisValue[] { lockToken }); return (long)result == 1; } public bool RenewLock(string lockKey, string lockToken, TimeSpan expiry) { var db = _connectionManager.GetDatabase(); var result = db.ScriptEvaluate( RenewLockScript, new RedisKey[] { BuildKey(lockKey) }, new RedisValue[] { lockToken, (long)expiry.TotalMilliseconds }); return (long)result == 1; } public T? ExecuteWithLock(string lockKey, TimeSpan expiry, Func action, TimeSpan? waitTimeout = null) { var token = AcquireLock(lockKey, expiry, waitTimeout); if (token == null) return default; try { return action(); } finally { ReleaseLock(lockKey, token); } } public bool ExecuteWithLock(string lockKey, TimeSpan expiry, Action action, TimeSpan? waitTimeout = null) { var token = AcquireLock(lockKey, expiry, waitTimeout); if (token == null) return false; try { action(); return true; } finally { ReleaseLock(lockKey, token); } } } }