Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nuova versione per il cluster #6

Merged
merged 13 commits into from
Jan 13, 2025
91 changes: 81 additions & 10 deletions src/Console/CleanOrphanedKeysCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,92 @@
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Padosoft\SuperCache\RedisConnector;
use Padosoft\SuperCache\Service\GetClusterNodesService;
use Padosoft\SuperCache\SuperCacheManager;

class CleanOrphanedKeysCommand extends Command
{
protected $signature = 'supercache:clean-orphans {--connection_name= : (opzionale) nome della connessione redis}' ;
protected $signature = 'supercache:clean-orphans {--connection_name= : (opzionale) nome della connessione redis}';
protected $description = 'Clean orphaned cache keys';
protected RedisConnector $redis;
protected int $numShards;
protected SuperCacheManager $superCache;
protected GetClusterNodesService $getClusterNodesService;

public function __construct(RedisConnector $redis)
public function __construct(RedisConnector $redis, SuperCacheManager $superCache, GetClusterNodesService $getClusterNodesService)
{
parent::__construct();
$this->redis = $redis;
$this->numShards = (int) config('supercache.num_shards'); // Numero di shard configurato
$this->superCache = $superCache;
$this->getClusterNodesService = $getClusterNodesService;
}

public function handle(): void
{
if (config('database.redis.clusters.' . ($this->option('connection_name') ?? 'default')) !== null) {
$this->handleOnCluster();
} else {
$this->handleOnStandalone();
}
}

public function handleOnCluster(): void
{
// Tenta di acquisire un lock globale
$lockAcquired = $this->superCache->lock('clean_orphans:lock', $this->option('connection_name'), 300);
if (!$lockAcquired) {
return;
}
// Purtroppo lo scan non funziona sul cluster per cui va eseguito su ogni nodo (master)
$array_nodi = $this->getClusterNodesService->getClusterNodes($this->option('connection_name'));

foreach ($array_nodi as $node) {
[$host, $port] = explode(':', $node);

// Per ogni shard vado a cercare i bussolotti (SET) dei tags che contengono le chiavi
for ($i = 0; $i < $this->numShards; $i++) {
// Inserisco nel pattern supercache: così sonop sicura di trovare solo i SET che riguardano il contesto della supercache
$shard_key = '*' . config('supercache.prefix') . 'tag:*:shard:' . $i;
// Creo una connessione persistente perchè considerando la durata dell'elaborazione si evita che dopo 30 secondi muoia tutto!
$connection = $this->redis->getNativeRedisConnection($this->option('connection_name'), $host, $port);

$cursor = null;
do {
$response = $connection['connection']->scan($cursor, $shard_key);

if ($response === false) {
//Nessuna chiave trovata ...
break;
}

foreach ($response as $key) {
// Ho trovato un bussolotto che matcha, vado a recuperare i membri del SET
$members = $connection['connection']->sMembers($key);
foreach ($members as $member) {
// Controllo che la chiave sia morta, ma ATTENZIONE non posso usare la connessione che ho già perchè sono su un singolo nodo, mentre nel bussolotto potrebbero esserci chiavi in sharding diversi
if ($this->redis->getRedisConnection($this->option('connection_name'))->exists($member)) {
// La chiave è viva! vado avanti
continue;
}
// Altrimenti rimuovo i cadaveri dal bussolotto e dai tags
// Qui posso usare la connessione che già ho in quanto sto modificando il bussolotto che sicuramente è nello shard corretto del nodo
$connection['connection']->srem($key, $member);
// Rimuovo anche i tag, che però potrebbero essere in un altro nodo quindi uso una nuova connessione
$this->redis->getRedisConnection($this->option('connection_name'))->del($member . ':tags');
}
}
} while ($cursor !== 0); // Continua fino a completamento

// Chiudo la connessione
$connection['connection']->close();
}
}
// Rilascio il lock globale
$this->superCache->unLock('clean_orphans:lock', $this->option('connection_name'));
}

public function handleOnStandalone(): void
{
// Carica il prefisso di default per le chiavi
$prefix = config('supercache.prefix');
Expand All @@ -33,16 +103,17 @@ public function handle(): void
local success, result = pcall(function()
local database_prefix = string.gsub(KEYS[5], "temp", "")
local shard_prefix = KEYS[1]
local tags_prefix = KEYS[6]
local num_shards = string.gsub(KEYS[2], database_prefix, "")
local lock_key = KEYS[3]
local lock_ttl = string.gsub(KEYS[4], database_prefix, "")

-- Tenta di acquisire un lock globale
if redis.call("SET", lock_key, "1", "NX", "EX", lock_ttl) then
-- Scansiona tutti gli shard
-- redis.log(redis.LOG_NOTICE, "Scansiona tutti gli shard: " .. num_shards)
for i = 0, num_shards - 1 do
local shard_key = shard_prefix .. ":" .. i
-- redis.log(redis.LOG_NOTICE, "shard_key: " .. shard_key)
-- Ottieni tutte le chiavi dallo shard
local cursor = "0"
local keys = {}
Expand All @@ -55,15 +126,17 @@ public function handle(): void
until cursor == "0"
-- Cerco tutte le chiavi associate a questa chiave
for _, key in ipairs(keys) do
-- redis.log(redis.LOG_NOTICE, "CHIAVE: " .. key)
local members = redis.call("SMEMBERS", key)
for _, member in ipairs(members) do
redis.log(redis.LOG_NOTICE, "member: " .. database_prefix .. member)
if redis.call("EXISTS", database_prefix .. member) == 0 then
-- La chiave è orfana, rimuovila dallo shard
redis.call("SREM", key, member)
-- redis.log(redis.LOG_NOTICE, "Rimossa chiave orfana key: " .. key .. " member: " .. member)
-- Devo rimuovere anche la chiave con i tags
-- gescat_laravel_database_supercache:tags:supercache:trilly
local tagsKey = tags_prefix .. member
redis.log(redis.LOG_WARNING, "tagsKey: " .. tagsKey)
local tagsKey = database_prefix .. member .. ":tags"
-- redis.log(redis.LOG_NOTICE, "Rimuovo la chiave con tagsKey: " .. tagsKey)
redis.call("DEL", tagsKey)
end
end
Expand All @@ -83,26 +156,24 @@ public function handle(): void
try {
// Parametri dello script
$shardPrefix = $prefix . 'tag:*:shard';
$tagPrefix = $prefix . 'tags:';
$tagPrefix = $prefix;
$lockKey = $prefix . 'clean_orphans:lock';
$lockTTL = 300; // Timeout lock di 300 secondi

// Esegui lo script Lua
$return = $this->redis->getRedisConnection($this->option('connection_name'))->eval(
$script,
6, // Numero di parametri passati a Lua come KEYS
5, // Numero di parametri passati a Lua come KEYS
$shardPrefix,
$this->numShards,
$lockKey,
$lockTTL,
'temp',
$tagPrefix,
);

if ($return !== 'OK') {
Log::error('Errore durante l\'esecuzione dello script Lua: ' . $return);
}

} catch (\Exception $e) {
Log::error('Errore durante l\'esecuzione dello script Lua: ' . $e->getMessage());
}
Expand Down
39 changes: 39 additions & 0 deletions src/Console/GetClusterNodesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Padosoft\SuperCache\Console;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Padosoft\SuperCache\RedisConnector;
use Padosoft\SuperCache\Service\GetClusterNodesService;

class GetClusterNodesCommand extends Command
{
protected $signature = 'supercache:get-cluster-nodes
{--connection_name= : (opzionale) nome della connessione redis }
';
protected $description = 'Comando per ottenere i nodi del cluster sulla connessione specificata';
protected RedisConnector $redis;
protected GetClusterNodesService $service;

public function __construct(RedisConnector $redis, GetClusterNodesService $service)
{
parent::__construct();
$this->redis = $redis;
$this->service = $service;
}

/**
* @throws \JsonException
*/
public function handle(): void
{
try {
$array_nodi = $this->service->getClusterNodes($this->option('connection_name'));
$this->output->writeln(json_encode($array_nodi, JSON_THROW_ON_ERROR));
} catch (\Throwable $e) {
Log::error('Errore durante il recupero dei nodi del cluster ' . $e->getMessage());
$this->output->writeln(json_encode([], JSON_THROW_ON_ERROR));
}
}
}
Loading