98 lines
3.5 KiB
C#
98 lines
3.5 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using Microsoft.Extensions.Configuration;
|
|||
|
|
using Microsoft.Extensions.Options;
|
|||
|
|
using StackExchange.Redis;
|
|||
|
|
using Sozsoft.Languages.Localization;
|
|||
|
|
using Volo.Abp.Caching;
|
|||
|
|
using Volo.Abp.DependencyInjection;
|
|||
|
|
|
|||
|
|
namespace Sozsoft.Languages.Caching;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Uygulamanın dağıtık önbelleğini boşaltır. <c>FLUSHALL</c> yerine yalnızca bu uygulamanın anahtarlarını
|
|||
|
|
/// siler: Redis başka bir uygulama ya da ortamla paylaşılıyorsa onların verisine dokunulmaz.
|
|||
|
|
/// Bağlantı süreç başına tektir; her çağrıda yeni bağlantı açmak bağlantı sızdırıyordu.
|
|||
|
|
/// </summary>
|
|||
|
|
public class DistributedCacheCleaner : ISingletonDependency, IDisposable
|
|||
|
|
{
|
|||
|
|
private const int BatchSize = 500;
|
|||
|
|
|
|||
|
|
private readonly Lazy<ConnectionMultiplexer> _connection;
|
|||
|
|
private readonly string[] _keyPatterns;
|
|||
|
|
private readonly IDatabaseResourceLocalizer _localizer;
|
|||
|
|
|
|||
|
|
public DistributedCacheCleaner(
|
|||
|
|
IConfiguration configuration,
|
|||
|
|
IOptions<AbpDistributedCacheOptions> cacheOptions,
|
|||
|
|
IDatabaseResourceLocalizer localizer)
|
|||
|
|
{
|
|||
|
|
_localizer = localizer;
|
|||
|
|
_connection = new Lazy<ConnectionMultiplexer>(
|
|||
|
|
() => ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!));
|
|||
|
|
|
|||
|
|
// ABP iki biçimde yazar: IDistributedCache<T> anahtarı [t:{kiracı},]c:{önbellek},k:{KeyPrefix}{anahtar},
|
|||
|
|
// bellek içi tanım önbelleklerinin damgası ise doğrudan {KeyPrefix}_Abp...Stamp. Host "Platform",
|
|||
|
|
// DbMigrator "Platform:" öneki kullanır; ':' atılınca desenler iki sürecin anahtarlarını da kapsar.
|
|||
|
|
var prefix = (cacheOptions.Value.KeyPrefix ?? string.Empty).TrimEnd(':');
|
|||
|
|
_keyPatterns = [$"{prefix}*", $"*,k:{prefix}*"];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <returns>Silinen anahtar sayısı.</returns>
|
|||
|
|
public async Task<long> ClearAsync()
|
|||
|
|
{
|
|||
|
|
var connection = _connection.Value;
|
|||
|
|
var database = connection.GetDatabase();
|
|||
|
|
long removed = 0;
|
|||
|
|
|
|||
|
|
foreach (var endpoint in connection.GetEndPoints())
|
|||
|
|
{
|
|||
|
|
var server = connection.GetServer(endpoint);
|
|||
|
|
if (server.IsReplica || !server.IsConnected)
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var pattern in _keyPatterns)
|
|||
|
|
{
|
|||
|
|
var batch = new List<RedisKey>(BatchSize);
|
|||
|
|
await foreach (var key in server.KeysAsync(database.Database, pattern, BatchSize))
|
|||
|
|
{
|
|||
|
|
batch.Add(key);
|
|||
|
|
if (batch.Count == BatchSize)
|
|||
|
|
{
|
|||
|
|
removed += await DeleteAsync(database, batch);
|
|||
|
|
batch.Clear();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
removed += await DeleteAsync(database, batch);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Dil sözlüğünün süreç içi kopyası Redis'te değildir; temizlik onu da kapsar.
|
|||
|
|
_localizer.ClearLocalCache();
|
|||
|
|
|
|||
|
|
return removed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Anahtarlar tek tek silinir: çok anahtarlı DEL, cluster'da farklı slot'lara düşen anahtarları reddeder.
|
|||
|
|
private static async Task<long> DeleteAsync(IDatabase database, List<RedisKey> keys)
|
|||
|
|
{
|
|||
|
|
var results = await Task.WhenAll(keys.Select(key => database.KeyDeleteAsync(key)));
|
|||
|
|
return results.Count(deleted => deleted);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Dispose()
|
|||
|
|
{
|
|||
|
|
if (_connection.IsValueCreated)
|
|||
|
|
{
|
|||
|
|
_connection.Value.Dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
GC.SuppressFinalize(this);
|
|||
|
|
}
|
|||
|
|
}
|