97 lines
3.5 KiB
C#
97 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);
|
||
}
|
||
}
|