Skip to content

Commit ed4d79b

Browse files
authored
Merge pull request #314 from pengweiqhca/lock
Add true retriever lock
2 parents 95c7465 + c3bd563 commit ed4d79b

File tree

51 files changed

+1489
-148
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1489
-148
lines changed

docs/In-Memory.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public class Startup
7878
services.AddEasyCaching(options =>
7979
{
8080
//use memory cache
81-
options.UseInMemory(Configuration, "default", "easycahing:inmemory");
81+
options.UseInMemory(Configuration, "default", "easycaching:inmemory");
8282
});
8383
}
8484
}
@@ -152,4 +152,4 @@ public class ValuesController : Controller
152152

153153
If you need to modify the data after you read from cache, don't forget the enable deep clone, otherwise, the cached data will be modified.
154154

155-
By the way, deep clone will hurt the performance, so if you don't need it, you should disable.
155+
By the way, deep clone will hurt the performance, so if you don't need it, you should disable.

src/EasyCaching.CSRedis/Configurations/EasyCachingOptionsExtensions.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
namespace Microsoft.Extensions.DependencyInjection
1+
using EasyCaching.CSRedis.DistributedLock;
2+
3+
namespace Microsoft.Extensions.DependencyInjection
24
{
3-
using System;
45
using EasyCaching.Core;
56
using EasyCaching.Core.Configurations;
7+
using EasyCaching.Core.DistributedLock;
68
using EasyCaching.CSRedis;
79
using Microsoft.Extensions.Configuration;
10+
using System;
811

912
/// <summary>
1013
/// EasyCaching options extensions.
@@ -61,5 +64,16 @@ void configure(RedisOptions x)
6164
options.RegisterExtension(new RedisOptionsExtension(name, configure));
6265
return options;
6366
}
67+
68+
/// <summary>
69+
/// Uses the CSRedis lock.
70+
/// </summary>
71+
/// <param name="options">Options.</param>
72+
public static EasyCachingOptions UseCSRedisLock(this EasyCachingOptions options)
73+
{
74+
options.UseDistributedLock<CSRedisLockFactory>();
75+
76+
return options;
77+
}
6478
}
6579
}

src/EasyCaching.CSRedis/Configurations/RedisOptionsExtension.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
{
33
using EasyCaching.Core;
44
using EasyCaching.Core.Configurations;
5+
using EasyCaching.Core.DistributedLock;
56
using EasyCaching.Core.Serialization;
7+
using EasyCaching.CSRedis.DistributedLock;
68
using Microsoft.Extensions.DependencyInjection;
79
using Microsoft.Extensions.DependencyInjection.Extensions;
810
using Microsoft.Extensions.Logging;
@@ -86,8 +88,9 @@ public void AddServices(IServiceCollection services)
8688
var serializers = x.GetServices<IEasyCachingSerializer>();
8789
var optionsMon = x.GetRequiredService<IOptionsMonitor<RedisOptions>>();
8890
var options = optionsMon.Get(_name);
91+
var dlf = x.GetService<CSRedisLockFactory>();
8992
var factory = x.GetService<ILoggerFactory>();
90-
return new DefaultCSRedisCachingProvider(_name, clients, serializers, options, factory);
93+
return new DefaultCSRedisCachingProvider(_name, clients, serializers, options, dlf, factory);
9194
};
9295

9396
services.AddSingleton<IEasyCachingProvider, DefaultCSRedisCachingProvider>(createFactory);

src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
namespace EasyCaching.CSRedis
22
{
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
63
using EasyCaching.Core;
4+
using EasyCaching.Core.DistributedLock;
75
using EasyCaching.Core.Serialization;
6+
using EasyCaching.CSRedis.DistributedLock;
87
using global::CSRedis;
98
using Microsoft.Extensions.Logging;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.Linq;
1012

1113
public partial class DefaultCSRedisCachingProvider : EasyCachingAbstractProvider
1214
{
@@ -53,12 +55,33 @@ public partial class DefaultCSRedisCachingProvider : EasyCachingAbstractProvider
5355
/// <param name="serializers">Serializers.</param>
5456
/// <param name="options">Options.</param>
5557
/// <param name="loggerFactory">Logger factory.</param>
58+
public DefaultCSRedisCachingProvider(
59+
string name,
60+
IEnumerable<EasyCachingCSRedisClient> clients,
61+
IEnumerable<IEasyCachingSerializer> serializers,
62+
RedisOptions options,
63+
ILoggerFactory loggerFactory = null)
64+
: this(name, clients, serializers, options, null, loggerFactory)
65+
{
66+
}
67+
68+
/// <summary>
69+
/// Initializes a new instance of the <see cref="T:EasyCaching.CSRedis.DefaultCSRedisCachingProvider"/> class.
70+
/// </summary>
71+
/// <param name="name">Name.</param>
72+
/// <param name="clients">Clients.</param>
73+
/// <param name="serializers">Serializers.</param>
74+
/// <param name="options">Options.</param>
75+
/// <param name="factory">Distributed lock factory</param>
76+
/// <param name="loggerFactory">Logger factory.</param>
5677
public DefaultCSRedisCachingProvider(
5778
string name,
5879
IEnumerable<EasyCachingCSRedisClient> clients,
5980
IEnumerable<IEasyCachingSerializer> serializers,
6081
RedisOptions options,
82+
CSRedisLockFactory factory = null,
6183
ILoggerFactory loggerFactory = null)
84+
: base(factory, options)
6285
{
6386
this._name = name;
6487
this._options = options;
@@ -401,7 +424,7 @@ public override void BaseSet<T>(string cacheKey, T cacheValue, TimeSpan expirati
401424
/// <typeparam name="T">The 1st type parameter.</typeparam>
402425
public override void BaseSetAll<T>(IDictionary<string, T> values, TimeSpan expiration)
403426
{
404-
//whether to use pipe based on redis mode
427+
//whether to use pipe based on redis mode
405428
if (MaxRdSecond > 0)
406429
{
407430
var addSec = new Random().Next(1, MaxRdSecond);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using EasyCaching.Core.DistributedLock;
2+
using Microsoft.Extensions.Logging;
3+
using Microsoft.Extensions.Options;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace EasyCaching.CSRedis.DistributedLock
8+
{
9+
public class CSRedisLockFactory : DistributedLockFactory
10+
{
11+
private readonly IEnumerable<EasyCachingCSRedisClient> _clients;
12+
13+
public CSRedisLockFactory(IEnumerable<EasyCachingCSRedisClient> clients,
14+
IOptionsMonitor<RedisOptions> optionsMonitor,
15+
ILoggerFactory loggerFactory = null)
16+
: base(name => DistributedLockOptions.FromProviderOptions(optionsMonitor.Get(name)), loggerFactory) =>
17+
_clients = clients;
18+
19+
protected override IDistributedLockProvider GetLockProvider(string name) =>
20+
new CSRedisLockProvider(name, _clients.Single(x => x.Name.Equals(name)));
21+
}
22+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using CSRedis;
2+
using EasyCaching.Core.DistributedLock;
3+
using System;
4+
using System.Threading.Tasks;
5+
6+
namespace EasyCaching.CSRedis.DistributedLock
7+
{
8+
public class CSRedisLockProvider : IDistributedLockProvider
9+
{
10+
private readonly string _name;
11+
private readonly EasyCachingCSRedisClient _database;
12+
13+
public CSRedisLockProvider(string name, EasyCachingCSRedisClient database)
14+
{
15+
_name = name;
16+
_database = database;
17+
}
18+
19+
public Task<bool> SetAsync(string key, byte[] value, int ttlMs) =>
20+
_database.SetAsync($"{_name}/{key}", value, TimeSpan.FromMilliseconds(ttlMs));
21+
22+
public bool Add(string key, byte[] value, int ttlMs) =>
23+
_database.Set($"{_name}/{key}", value, TimeSpan.FromMilliseconds(ttlMs), RedisExistence.Nx);
24+
25+
public Task<bool> AddAsync(string key, byte[] value, int ttlMs) =>
26+
_database.SetAsync($"{_name}/{key}", value, TimeSpan.FromMilliseconds(ttlMs), RedisExistence.Nx);
27+
28+
public bool Delete(string key, byte[] value) =>
29+
(long)_database.Eval(@"if redis.call('GET', KEYS[1]) == ARGV[1] then
30+
return redis.call('DEL', KEYS[1]);
31+
end
32+
return -1;", $"{_name}/{key}", value) >= 0;
33+
34+
public async Task<bool> DeleteAsync(string key, byte[] value) =>
35+
(long)await _database.EvalAsync(@"if redis.call('GET', KEYS[1]) == ARGV[1] then
36+
return redis.call('DEL', KEYS[1]);
37+
end
38+
return -1;", $"{_name}/{key}", value) >= 0;
39+
40+
public bool CanRetry(Exception ex) => ex is RedisClientException;
41+
}
42+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
using Microsoft.Extensions.Logging;
2+
using System;
3+
using System.Diagnostics;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
namespace EasyCaching.Core.DistributedLock
8+
{
9+
public class DistributedLock : MemoryLock
10+
{
11+
private readonly IDistributedLockProvider _provider;
12+
private readonly object _syncObj = new object();
13+
private readonly DistributedLockOptions _options;
14+
private readonly ILogger _logger;
15+
16+
private byte[] _value;
17+
private Timer _timer;
18+
19+
public DistributedLock(string name, string key, IDistributedLockProvider provider, DistributedLockOptions options, ILoggerFactory loggerFactory = null) : base($"{name}/{key}")
20+
{
21+
_provider = provider;
22+
_options = options;
23+
_logger = loggerFactory?.CreateLogger(GetType().FullName);
24+
}
25+
26+
public override bool Lock(int millisecondsTimeout, CancellationToken cancellationToken)
27+
{
28+
var sw = Stopwatch.StartNew();
29+
if (base.Lock(millisecondsTimeout, cancellationToken))
30+
{
31+
GetNewGuid();
32+
33+
do
34+
{
35+
try
36+
{
37+
if (_provider.Add(Key, _value, _options.MaxTtl))
38+
{
39+
StartPing();
40+
41+
return true;
42+
}
43+
}
44+
catch (Exception ex)
45+
{
46+
_logger?.LogWarning(default, ex, ex.Message);
47+
48+
if (!_provider.CanRetry(ex)) break;
49+
}
50+
51+
if (cancellationToken.IsCancellationRequested)
52+
{
53+
_value = null;
54+
55+
cancellationToken.ThrowIfCancellationRequested();
56+
}
57+
58+
Thread.Sleep(Math.Max(0, Math.Min(100, millisecondsTimeout - (int)sw.ElapsedMilliseconds)));
59+
} while (sw.ElapsedMilliseconds < millisecondsTimeout);
60+
61+
_logger?.LogWarning($"{Key}/Wait fail");
62+
63+
base.Release();
64+
}
65+
66+
_value = null;
67+
return false;
68+
}
69+
70+
public override async ValueTask<bool> LockAsync(int millisecondsTimeout, CancellationToken cancellationToken)
71+
{
72+
var sw = Stopwatch.StartNew();
73+
if (await base.LockAsync(millisecondsTimeout, cancellationToken))
74+
{
75+
GetNewGuid();
76+
77+
do
78+
{
79+
try
80+
{
81+
if (await _provider.AddAsync(Key, _value, _options.MaxTtl))
82+
{
83+
StartPing();
84+
85+
return true;
86+
}
87+
}
88+
catch (Exception ex)
89+
{
90+
_logger?.LogWarning(default, ex, ex.Message);
91+
92+
if (!_provider.CanRetry(ex)) break;
93+
}
94+
95+
if (cancellationToken.IsCancellationRequested)
96+
{
97+
_value = null;
98+
99+
cancellationToken.ThrowIfCancellationRequested();
100+
}
101+
102+
await Task.Delay(Math.Max(0, Math.Min(100, millisecondsTimeout - (int)sw.ElapsedMilliseconds)), cancellationToken);
103+
} while (sw.ElapsedMilliseconds < millisecondsTimeout);
104+
105+
_logger?.LogWarning($"{Key}/Wait fail");
106+
107+
await base.ReleaseAsync();
108+
}
109+
110+
_value = null;
111+
return false;
112+
}
113+
114+
public override void Release()
115+
{
116+
Interlocked.Exchange(ref _timer, null)?.Dispose();
117+
118+
var value = Interlocked.Exchange(ref _value, null);
119+
if (value == null) return;
120+
121+
try
122+
{
123+
if (_provider.Delete(Key, value)) _logger?.LogInformation($"{Key}/Release lock");
124+
else _logger?.LogWarning($"{Key}/Release lock fail");
125+
}
126+
finally
127+
{
128+
base.Release();
129+
}
130+
}
131+
132+
public override async ValueTask ReleaseAsync()
133+
{
134+
Interlocked.Exchange(ref _timer, null)?.Dispose();
135+
136+
var value = Interlocked.Exchange(ref _value, null);
137+
if (value == null) return;
138+
139+
try
140+
{
141+
if (await _provider.DeleteAsync(Key, value)) _logger?.LogInformation($"{Key}/Release lock");
142+
else _logger?.LogWarning($"{Key}/Release lock fail");
143+
}
144+
finally
145+
{
146+
await base.ReleaseAsync();
147+
}
148+
}
149+
150+
private void GetNewGuid()
151+
{
152+
lock (_syncObj)
153+
{
154+
if (_value != null) throw new DistributedLockException();
155+
156+
var id = Guid.NewGuid();
157+
158+
_value = id.ToByteArray();
159+
160+
_logger?.LogDebug($"{Key}/NewGuid: {id:D}");
161+
}
162+
}
163+
164+
private void StartPing()
165+
{
166+
_logger?.LogInformation($"{Key}/Wait success, start ping");
167+
168+
_timer = new Timer(Ping, this, _options.DueTime, _options.Period);
169+
}
170+
171+
private static async void Ping(object state)
172+
{
173+
var self = (DistributedLock)state;
174+
175+
try
176+
{
177+
await self._provider.SetAsync(self.Key, self._value, self._options.MaxTtl);
178+
179+
self._logger?.LogDebug($"{self.Key}/Ping success");
180+
}
181+
catch (Exception ex)
182+
{
183+
self._logger?.LogWarning(default, ex, $"{self.Key}/Ping fail");
184+
}
185+
}
186+
}
187+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
3+
namespace EasyCaching.Core.DistributedLock
4+
{
5+
[Serializable]
6+
public class DistributedLockException : Exception
7+
{
8+
public DistributedLockException() : base("锁释放前请不要重复锁") { }
9+
10+
public DistributedLockException(string message) : base(message) { }
11+
}
12+
}

0 commit comments

Comments
 (0)