Skip to content

Commit

Permalink
Merge branch 'master' of https://www.github.com/2881099/FreeRedis
Browse files Browse the repository at this point in the history
  • Loading branch information
2881099 committed Nov 18, 2024
2 parents 0ba2d2c + 775c443 commit f7519f7
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 190 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ FreeRedis is a redis client based on .NET, supports .NET Core 2.1+, .NET Framewo
- 🎣 Support Redis Master-Slave
- 📡 Support Redis Pub-Sub
- 📃 Support Redis Lua Scripting
- 💻 Support PipelineTransactionDelayQueueRediSearch
- 💻 Support Pipeline, Transaction, DelayQueue, RediSearch
- 🌴 Support Geo type commands (requires redis-server 3.2 and above)
- 🌲 Support Streams type commands (requires redis-server 5.0 and above)
- ⚡ Support Client-side-caching (requires redis-server 6.0 and above)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<AssemblyName>FreeRedis.DistributedCache</AssemblyName>
<PackageId>FreeRedis.DistributedCache</PackageId>
<RootNamespace>FreeRedis.DistributedCache</RootNamespace>
<Version>1.3.2</Version>
<Version>1.3.3</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageProjectUrl>https://github.com/2881099/FreeRedis</PackageProjectUrl>
<Description>分布式缓存 FreeRedis 实现 Microsoft.Extensions.Caching</Description>
Expand Down
2 changes: 1 addition & 1 deletion src/FreeRedis.OpenTelemetry/FreeRedis.OpenTelemetry.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<AssemblyName>FreeRedis.OpenTelemetry</AssemblyName>
<PackageId>FreeRedis.OpenTelemetry</PackageId>
<RootNamespace>FreeRedis.OpenTelemetry</RootNamespace>
<Version>1.3.2</Version>
<Version>1.3.3</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageProjectUrl>https://github.com/2881099/FreeRedis</PackageProjectUrl>
<RepositoryUrl>https://github.com/2881099/FreeRedis</RepositoryUrl>
Expand Down
2 changes: 1 addition & 1 deletion src/FreeRedis/FreeRedis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<AssemblyName>FreeRedis</AssemblyName>
<PackageId>FreeRedis</PackageId>
<RootNamespace>FreeRedis</RootNamespace>
<Version>1.3.2</Version>
<Version>1.3.3</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageProjectUrl>https://github.com/2881099/FreeRedis</PackageProjectUrl>
<Description>FreeRedis is .NET redis client, supports cluster, sentinel, master-slave, pipeline, transaction and connection pool.</Description>
Expand Down
25 changes: 0 additions & 25 deletions src/FreeRedis/FreeRedis.sln

This file was deleted.

5 changes: 5 additions & 0 deletions src/FreeRedis/FreeRedis.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

271 changes: 148 additions & 123 deletions src/FreeRedis/RedisClient/Modules/Lock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,145 +5,170 @@

namespace FreeRedis
{
partial class RedisClient
{
/// <summary>
/// 开启分布式锁,若超时返回null
/// </summary>
/// <param name="name">锁名称</param>
/// <param name="timeoutSeconds">超时(秒)</param>
/// <param name="autoDelay">自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。</param>
/// <returns></returns>
public LockController Lock(string name, int timeoutSeconds, bool autoDelay = true)
{
name = $"RedisClientLock:{name}";
var startTime = DateTime.Now;
while (DateTime.Now.Subtract(startTime).TotalSeconds < timeoutSeconds)
{
var value = Guid.NewGuid().ToString();
if (SetNx(name, value, timeoutSeconds) == true)
{
double refreshSeconds = (double)timeoutSeconds / 2.0;
return new LockController(this, name, value, timeoutSeconds, refreshSeconds, autoDelay, CancellationToken.None);
}
Thread.CurrentThread.Join(3);
}
return null;
}

/// <summary>
/// 开启分布式锁,若超时返回null
/// </summary>
/// <param name="name">锁名称</param>
/// <param name="timeoutSeconds">独占锁过期时间</param>
/// <param name="refrshTimeoutSeconds">每隔多久自动延长一次锁,时间要比 timeoutSeconds 小,否则没有意义</param>
/// <param name="waitTimeoutSeconds">等待锁释放超时时间,如果这个时间内获取不到锁,则 LockController 为 null</param>
/// <param name="autoDelay">自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。</param>
/// <param name="token">CancellationToken 自动取消锁</param>
/// <returns></returns>
public LockController Lock(string name, int timeoutSeconds, int refrshTimeoutSeconds, int waitTimeoutSeconds, bool autoDelay = true, CancellationToken token = default)
{
if (refrshTimeoutSeconds == 0) throw new ArgumentException(nameof(refrshTimeoutSeconds), "刷新间隔时间不能为0");
if (waitTimeoutSeconds == 0) waitTimeoutSeconds = refrshTimeoutSeconds;

name = $"RedisClientLock:{name}";
var startTime = DateTime.Now;

// 规定时间内等待锁释放
while (DateTime.Now.Subtract(startTime).TotalSeconds < waitTimeoutSeconds)
{
var value = Guid.NewGuid().ToString();
if (SetNx(name, value, timeoutSeconds) == true)
{
return new LockController(this, name, value, timeoutSeconds, refrshTimeoutSeconds, autoDelay, token);
}
if (token.IsCancellationRequested) return null;
Thread.CurrentThread.Join(millisecondsTimeout: 3);
}
return null;
}

public class LockController : IDisposable
{

RedisClient _client;
string _name;
string _value;
int _timeoutSeconds;
Timer _autoDelayTimer;
private readonly CancellationToken _token;
internal LockController(RedisClient rds, string name, string value, int timeoutSeconds, double refreshSeconds, bool autoDelay, CancellationToken token)
{
_client = rds;
_name = name;
_value = value;
_timeoutSeconds = timeoutSeconds;
_token = token;
if (autoDelay)
{
var refreshMilli = (int)(refreshSeconds * 1000);
var timeoutMilli = timeoutSeconds * 1000;
_autoDelayTimer = new Timer(state2 => Refresh(timeoutMilli), null, refreshMilli, refreshMilli);
}
}

/// <summary>
/// 延长锁时间,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
/// </summary>
/// <param name="milliseconds">延长的毫秒数</param>
/// <returns>成功/失败</returns>
public bool Delay(int milliseconds)
{
var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
partial class RedisClient
{
/// <summary>
/// 开启分布式锁,若超时返回null
/// </summary>
/// <param name="name">锁名称</param>
/// <param name="timeoutSeconds">超时(秒)</param>
/// <param name="autoDelay">自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。</param>
/// <returns></returns>
public LockController Lock(string name, int timeoutSeconds, bool autoDelay = true)
{
name = $"RedisClientLock:{name}";
var startTime = DateTime.Now;
while (DateTime.Now.Subtract(startTime).TotalSeconds < timeoutSeconds)
{
var value = Guid.NewGuid().ToString();
if (SetNx(name, value, timeoutSeconds) == true)
{
double refreshSeconds = (double)timeoutSeconds / 2.0;
return new LockController(this, name, value, timeoutSeconds, refreshSeconds, autoDelay, CancellationToken.None);
}
Thread.CurrentThread.Join(3);
}
return null;
}

/// <summary>
/// 开启分布式锁,若超时返回null
/// </summary>
/// <param name="name">锁名称</param>
/// <param name="timeoutSeconds">独占锁过期时间</param>
/// <param name="refrshTimeoutSeconds">每隔多久自动延长一次锁,时间要比 timeoutSeconds 小,否则没有意义</param>
/// <param name="waitTimeoutSeconds">等待锁释放超时时间,如果这个时间内获取不到锁,则 LockController 为 null</param>
/// <param name="autoDelay">自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。</param>
/// <param name="token">CancellationToken 自动取消锁</param>
/// <returns></returns>
public LockController Lock(string name, int timeoutSeconds, int refrshTimeoutSeconds, int waitTimeoutSeconds, bool autoDelay = true, CancellationToken token = default)
{
if (refrshTimeoutSeconds == 0) throw new ArgumentException(nameof(refrshTimeoutSeconds), "刷新间隔时间不能为0");
if (waitTimeoutSeconds == 0) waitTimeoutSeconds = refrshTimeoutSeconds;

name = $"RedisClientLock:{name}";
var startTime = DateTime.Now;

// 规定时间内等待锁释放
while (DateTime.Now.Subtract(startTime).TotalSeconds < waitTimeoutSeconds)
{
var value = Guid.NewGuid().ToString();
if (SetNx(name, value, timeoutSeconds) == true)
{
return new LockController(this, name, value, timeoutSeconds, refrshTimeoutSeconds, autoDelay, token);
}
if (token.IsCancellationRequested) return null;
Thread.CurrentThread.Join(millisecondsTimeout: 3);
}
return null;
}

public class LockController : IDisposable
{

RedisClient _client;
string _name;
string _value;
int _timeoutSeconds;
Timer _autoDelayTimer;
private CancellationTokenSource _handleLostTokenSource;
private readonly CancellationToken _token;

/// <summary>
/// 当刷新锁时间的看门狗线程失去与Redis连接时,导致无法刷新延长锁时间时,触发此HandelLostToken Cancel
/// </summary>
public CancellationToken? HandleLostToken { get; }

internal LockController(RedisClient rds, string name, string value, int timeoutSeconds, double refreshSeconds, bool autoDelay, CancellationToken token)
{
_client = rds;
_name = name;
_value = value;
_timeoutSeconds = timeoutSeconds;
_token = token;
if (autoDelay)
{
_handleLostTokenSource = new CancellationTokenSource();
HandleLostToken = _handleLostTokenSource.Token;

var refreshMilli = (int)(refreshSeconds * 1000);
var timeoutMilli = timeoutSeconds * 1000;
_autoDelayTimer = new Timer(state2 => Refresh(timeoutMilli), null, refreshMilli, refreshMilli);
}
}

/// <summary>
/// 延长锁时间,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
/// </summary>
/// <param name="milliseconds">延长的毫秒数</param>
/// <returns>成功/失败</returns>
public bool Delay(int milliseconds)
{
var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
local ttlva = redis.call('PTTL', KEYS[1])
redis.call('PEXPIRE', KEYS[1], ARGV[2] + ttlva)
return 1
end
return 0", new[] { _name }, _value, milliseconds)?.ToString() == "1";
if (ret == false) _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
return ret;
}

/// <summary>
/// 刷新锁时间,把key的ttl重新设置为milliseconds,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
/// </summary>
/// <param name="milliseconds">刷新的毫秒数</param>
/// <returns>成功/失败</returns>
public bool Refresh(int milliseconds)
{
var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if (ret == false) _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
return ret;
}

/// <summary>
/// 刷新锁时间,把key的ttl重新设置为milliseconds,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
/// </summary>
/// <param name="milliseconds">刷新的毫秒数</param>
/// <returns>成功/失败</returns>
public bool Refresh(int milliseconds)
{
if (_token.IsCancellationRequested)
{
_autoDelayTimer?.Dispose();
}

try
{
var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
redis.call('PEXPIRE', KEYS[1], ARGV[2])
return 1
end
return 0", new[] { _name }, _value, milliseconds)?.ToString() == "1";
if (ret == false) _autoDelayTimer?.Dispose(); //未知情况,关闭定时器

if (_token.IsCancellationRequested)
{
_autoDelayTimer?.Dispose();
}
return ret;
}

/// <summary>
/// 释放分布式锁
/// </summary>
/// <returns>成功/失败</returns>
public bool Unlock()
{
_autoDelayTimer?.Dispose();
return _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if (ret == false)
{
_handleLostTokenSource?.Cancel();
_autoDelayTimer?.Dispose(); //未知情况,关闭定时器
}

return ret;
}
catch
{
_handleLostTokenSource?.Cancel();
_autoDelayTimer?.Dispose(); //未知情况,关闭定时器
return false;//这里必须要吞掉异常,否则会导致整个程序崩溃,因为Timer的异常没有地方去处理
}
}

/// <summary>
/// 释放分布式锁
/// </summary>
/// <returns>成功/失败</returns>
public bool Unlock()
{
_handleLostTokenSource?.Dispose();
_autoDelayTimer?.Dispose();
return _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
redis.call('DEL', KEYS[1])
return 1
end
return 0", new[] { _name }, _value)?.ToString() == "1";
}
}

public void Dispose() => this.Unlock();
}
}
public void Dispose() => this.Unlock();
}
}

}
Loading

0 comments on commit f7519f7

Please sign in to comment.