Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/In-Memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public class Startup
services.AddEasyCaching(options =>
{
//use memory cache
options.UseInMemory(Configuration, "default", "easycahing:inmemory");
options.UseInMemory(Configuration, "default", "easycaching:inmemory");
});
}
}
Expand Down Expand Up @@ -152,4 +152,4 @@ public class ValuesController : Controller

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.

By the way, deep clone will hurt the performance, so if you don't need it, you should disable.
By the way, deep clone will hurt the performance, so if you don't need it, you should disable.
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
namespace Microsoft.Extensions.DependencyInjection
using EasyCaching.CSRedis.DistributedLock;

namespace Microsoft.Extensions.DependencyInjection
{
using System;
using EasyCaching.Core;
using EasyCaching.Core.Configurations;
using EasyCaching.Core.DistributedLock;
using EasyCaching.CSRedis;
using Microsoft.Extensions.Configuration;
using System;

/// <summary>
/// EasyCaching options extensions.
Expand Down Expand Up @@ -61,5 +64,16 @@ void configure(RedisOptions x)
options.RegisterExtension(new RedisOptionsExtension(name, configure));
return options;
}

/// <summary>
/// Uses the CSRedis lock.
/// </summary>
/// <param name="options">Options.</param>
public static EasyCachingOptions UseCSRedisLock(this EasyCachingOptions options)
{
options.UseDistributedLock<CSRedisLockFactory>();

return options;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
{
using EasyCaching.Core;
using EasyCaching.Core.Configurations;
using EasyCaching.Core.DistributedLock;
using EasyCaching.Core.Serialization;
using EasyCaching.CSRedis.DistributedLock;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -86,8 +88,9 @@ public void AddServices(IServiceCollection services)
var serializers = x.GetServices<IEasyCachingSerializer>();
var optionsMon = x.GetRequiredService<IOptionsMonitor<RedisOptions>>();
var options = optionsMon.Get(_name);
var dlf = x.GetService<CSRedisLockFactory>();
var factory = x.GetService<ILoggerFactory>();
return new DefaultCSRedisCachingProvider(_name, clients, serializers, options, factory);
return new DefaultCSRedisCachingProvider(_name, clients, serializers, options, dlf, factory);
};

services.AddSingleton<IEasyCachingProvider, DefaultCSRedisCachingProvider>(createFactory);
Expand Down
31 changes: 27 additions & 4 deletions src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
namespace EasyCaching.CSRedis
{
using System;
using System.Collections.Generic;
using System.Linq;
using EasyCaching.Core;
using EasyCaching.Core.DistributedLock;
using EasyCaching.Core.Serialization;
using EasyCaching.CSRedis.DistributedLock;
using global::CSRedis;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

public partial class DefaultCSRedisCachingProvider : EasyCachingAbstractProvider
{
Expand Down Expand Up @@ -53,12 +55,33 @@ public partial class DefaultCSRedisCachingProvider : EasyCachingAbstractProvider
/// <param name="serializers">Serializers.</param>
/// <param name="options">Options.</param>
/// <param name="loggerFactory">Logger factory.</param>
public DefaultCSRedisCachingProvider(
string name,
IEnumerable<EasyCachingCSRedisClient> clients,
IEnumerable<IEasyCachingSerializer> serializers,
RedisOptions options,
ILoggerFactory loggerFactory = null)
: this(name, clients, serializers, options, null, loggerFactory)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="T:EasyCaching.CSRedis.DefaultCSRedisCachingProvider"/> class.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="clients">Clients.</param>
/// <param name="serializers">Serializers.</param>
/// <param name="options">Options.</param>
/// <param name="factory">Distributed lock factory</param>
/// <param name="loggerFactory">Logger factory.</param>
public DefaultCSRedisCachingProvider(
string name,
IEnumerable<EasyCachingCSRedisClient> clients,
IEnumerable<IEasyCachingSerializer> serializers,
RedisOptions options,
CSRedisLockFactory factory = null,
ILoggerFactory loggerFactory = null)
: base(factory, options)
{
this._name = name;
this._options = options;
Expand Down Expand Up @@ -401,7 +424,7 @@ public override void BaseSet<T>(string cacheKey, T cacheValue, TimeSpan expirati
/// <typeparam name="T">The 1st type parameter.</typeparam>
public override void BaseSetAll<T>(IDictionary<string, T> values, TimeSpan expiration)
{
//whether to use pipe based on redis mode
//whether to use pipe based on redis mode
if (MaxRdSecond > 0)
{
var addSec = new Random().Next(1, MaxRdSecond);
Expand Down
22 changes: 22 additions & 0 deletions src/EasyCaching.CSRedis/DistributedLock/CSRedisLockFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using EasyCaching.Core.DistributedLock;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Linq;

namespace EasyCaching.CSRedis.DistributedLock
{
public class CSRedisLockFactory : DistributedLockFactory
{
private readonly IEnumerable<EasyCachingCSRedisClient> _clients;

public CSRedisLockFactory(IEnumerable<EasyCachingCSRedisClient> clients,
IOptionsMonitor<RedisOptions> optionsMonitor,
ILoggerFactory loggerFactory = null)
: base(name => DistributedLockOptions.FromProviderOptions(optionsMonitor.Get(name)), loggerFactory) =>
_clients = clients;

protected override IDistributedLockProvider GetLockProvider(string name) =>
new CSRedisLockProvider(name, _clients.Single(x => x.Name.Equals(name)));
}
}
42 changes: 42 additions & 0 deletions src/EasyCaching.CSRedis/DistributedLock/CSRedisLockProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using CSRedis;
using EasyCaching.Core.DistributedLock;
using System;
using System.Threading.Tasks;

namespace EasyCaching.CSRedis.DistributedLock
{
public class CSRedisLockProvider : IDistributedLockProvider
{
private readonly string _name;
private readonly EasyCachingCSRedisClient _database;

public CSRedisLockProvider(string name, EasyCachingCSRedisClient database)
{
_name = name;
_database = database;
}

public Task<bool> SetAsync(string key, byte[] value, int ttlMs) =>
_database.SetAsync($"{_name}/{key}", value, TimeSpan.FromMilliseconds(ttlMs));

public bool Add(string key, byte[] value, int ttlMs) =>
_database.Set($"{_name}/{key}", value, TimeSpan.FromMilliseconds(ttlMs), RedisExistence.Nx);

public Task<bool> AddAsync(string key, byte[] value, int ttlMs) =>
_database.SetAsync($"{_name}/{key}", value, TimeSpan.FromMilliseconds(ttlMs), RedisExistence.Nx);

public bool Delete(string key, byte[] value) =>
(long)_database.Eval(@"if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1]);
end
return -1;", $"{_name}/{key}", value) >= 0;

public async Task<bool> DeleteAsync(string key, byte[] value) =>
(long)await _database.EvalAsync(@"if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1]);
end
return -1;", $"{_name}/{key}", value) >= 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about move the lua script to a constant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


public bool CanRetry(Exception ex) => ex is RedisClientException;
}
}
187 changes: 187 additions & 0 deletions src/EasyCaching.Core/DistributedLock/DistributedLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace EasyCaching.Core.DistributedLock
{
public class DistributedLock : MemoryLock
{
private readonly IDistributedLockProvider _provider;
private readonly object _syncObj = new object();
private readonly DistributedLockOptions _options;
private readonly ILogger _logger;

private byte[] _value;
private Timer _timer;

public DistributedLock(string name, string key, IDistributedLockProvider provider, DistributedLockOptions options, ILoggerFactory loggerFactory = null) : base($"{name}/{key}")
{
_provider = provider;
_options = options;
_logger = loggerFactory?.CreateLogger(GetType().FullName);
}

public override bool Lock(int millisecondsTimeout, CancellationToken cancellationToken)
{
var sw = Stopwatch.StartNew();
if (base.Lock(millisecondsTimeout, cancellationToken))
{
GetNewGuid();

do
{
try
{
if (_provider.Add(Key, _value, _options.MaxTtl))
{
StartPing();

return true;
}
}
catch (Exception ex)
{
_logger?.LogWarning(default, ex, ex.Message);

if (!_provider.CanRetry(ex)) break;
}

if (cancellationToken.IsCancellationRequested)
{
_value = null;

cancellationToken.ThrowIfCancellationRequested();
}

Thread.Sleep(Math.Max(0, Math.Min(100, millisecondsTimeout - (int)sw.ElapsedMilliseconds)));
} while (sw.ElapsedMilliseconds < millisecondsTimeout);

_logger?.LogWarning($"{Key}/Wait fail");

base.Release();
}

_value = null;
return false;
}

public override async ValueTask<bool> LockAsync(int millisecondsTimeout, CancellationToken cancellationToken)
{
var sw = Stopwatch.StartNew();
if (await base.LockAsync(millisecondsTimeout, cancellationToken))
{
GetNewGuid();

do
{
try
{
if (await _provider.AddAsync(Key, _value, _options.MaxTtl))
{
StartPing();

return true;
}
}
catch (Exception ex)
{
_logger?.LogWarning(default, ex, ex.Message);

if (!_provider.CanRetry(ex)) break;
}

if (cancellationToken.IsCancellationRequested)
{
_value = null;

cancellationToken.ThrowIfCancellationRequested();
}

await Task.Delay(Math.Max(0, Math.Min(100, millisecondsTimeout - (int)sw.ElapsedMilliseconds)), cancellationToken);
} while (sw.ElapsedMilliseconds < millisecondsTimeout);

_logger?.LogWarning($"{Key}/Wait fail");

await base.ReleaseAsync();
}

_value = null;
return false;
}

public override void Release()
{
Interlocked.Exchange(ref _timer, null)?.Dispose();

var value = Interlocked.Exchange(ref _value, null);
if (value == null) return;

try
{
if (_provider.Delete(Key, value)) _logger?.LogInformation($"{Key}/Release lock");
else _logger?.LogWarning($"{Key}/Release lock fail");
}
finally
{
base.Release();
}
}

public override async ValueTask ReleaseAsync()
{
Interlocked.Exchange(ref _timer, null)?.Dispose();

var value = Interlocked.Exchange(ref _value, null);
if (value == null) return;

try
{
if (await _provider.DeleteAsync(Key, value)) _logger?.LogInformation($"{Key}/Release lock");
else _logger?.LogWarning($"{Key}/Release lock fail");
}
finally
{
await base.ReleaseAsync();
}
}

private void GetNewGuid()
{
lock (_syncObj)
{
if (_value != null) throw new DistributedLockException();

var id = Guid.NewGuid();

_value = id.ToByteArray();

_logger?.LogDebug($"{Key}/NewGuid: {id:D}");
}
}

private void StartPing()
{
_logger?.LogInformation($"{Key}/Wait success, start ping");

_timer = new Timer(Ping, this, _options.DueTime, _options.Period);
}

private static async void Ping(object state)
{
var self = (DistributedLock)state;

try
{
await self._provider.SetAsync(self.Key, self._value, self._options.MaxTtl);

self._logger?.LogDebug($"{self.Key}/Ping success");
}
catch (Exception ex)
{
self._logger?.LogWarning(default, ex, $"{self.Key}/Ping fail");
}
}
}
}
12 changes: 12 additions & 0 deletions src/EasyCaching.Core/DistributedLock/DistributedLockException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace EasyCaching.Core.DistributedLock
{
[Serializable]
public class DistributedLockException : Exception
{
public DistributedLockException() : base("锁释放前请不要重复锁") { }

public DistributedLockException(string message) : base(message) { }
}
}
Loading