PHP Redis : Comment indexer les clés ?

  • Auteur de la discussion Auteur de la discussion ortolojf
  • Date de début Date de début
WRInaute accro
Bonjour

Suis en train d'implémenter mes sessions avec PHP Redis.

Les méthode suivantes n'agissent que sur des string du genre : $key : $value.

ttl(), expire(), del(), etc...

Sur mon petit projet, les valeurs $key : $value sont affectées/lues avec : $connId->hSet($id, [key => $value]);

Comment gérer les delete et expire() ?

Suis-je obligé de n'enregistrer que des : ($id . $key) : $value ?

Mes id de session sont des UUID V4.

Pour lire les enreg de $id je ferais ( à peu près ) :

PHP:
  $array_keys = array();

  $iterator = NULL;
  while($iterator != 0)
  {
                $array_keys = $this->connId->scan($iterator, "[a-z0-9-]+*");

               //   ... etc...
  }

Merci beaucoup de votre aide.
 
WRInaute accro
Pardon

J'avais trouvé.

J'ai mis les enregs dans des "hash", un hash par id de session.

Chaque id de session est strictement aléatoire, et ( dans mon cas ), du type UUID v4.

Les expire() ne peuvent être affectés que pour la clé de chaque hash.

Voici mon session_handler.php

Merci de vos avis et critiques.

PHP:
<?php
require(__DIR__ . '/clientID.php');
define('MAXLIFETIME', ini_get('session.gc_maxlifetime'));
define('MAX_IDLE_TIME', 300);
define('MIN_LEFT_TIME', 30);
class CustomSessionHandler implements SessionHandlerInterface,
        SessionIdInterface,
        SessionUpdateTimestampHandlerInterface
{
        private $redisConn;
        private $lastCreatedId;
        private $fp = null;
        public function    __construct()
        {
                $this->fp = fopen("/var/www/html/php/config/redis.log", "w");
                session_set_save_handler(
                        array($this, "open"),
                        array($this, "close"),
                        array($this, "read"), 
                        array($this, "write"), 
                        array($this, "destroy"), 
                        array($this, "gc"), 
                        array($this, "create_sid"), 
                        array($this, "validateId"), 
                        array($this, "updateTimestamp"));
                fwrite($this->fp, "Running session_set_save_handler();\n");
                // Set the shutdown function
                register_shutdown_function('session_write_close');
                fwrite($this->fp, "Preparing session_write_close();\n");
                // Define and initialise the Session Handler
                session_start();
                return true;
        }
        /**
        * 1ère fonction.
        **/
        public function open($savePath, $sessionName)
        {
                /**
                Parameters
                    host: string. can be a host, or the path to a unix domain socket. Starting from version 5.0.0 it is possible to specify schema port: int, optional
                    timeout: float, value in seconds (optional, default is 0 meaning unlimited)
                    reserved: should be NULL if retry_interval is specified
                    retry_interval: int, value in milliseconds (optional)
                    read_timeout: float, value in seconds (optional, default is 0 meaning unlimited)
                **/
                try
                {
                        $this->redisConn = new Redis();
                        fwrite($this->fp, "Instanciation class Redis done.\n");
                } catch(Exception $e) {
                        fwrite($this->fp, "Erreur instanciation classe Redis : " . $e->getMessage() . "\n");
                        return false;
                }   
                try
                {
                        $this->redisConn->connect('/var/run/redis/redis.sock'); // unix domain socket.
                        fwrite($this->fp, "Connect to Redis server done.\n");
                } catch(Exception $e) {
                        fwrite($this->fp, "Erreur connexion serveur Redis : " . $e->getMessage() . "\n");
                        return false;
                }
                try
                {
                        $this->redisConn->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
                        fwrite($this->fp, "Setting Redis client option done.\n");
                        return true;
                } catch(Exception $e) {
                        fwrite($this->fp, "Erreur setting option client Redis : " . $e->getMessage() . "\n");
                        return false;
                }
                return false;
                /* With PhpRedis >= 5.3.0 you can specify authentication information on connect */
                /**
                if($this->redisConn->connect('/var/run/redis/redis.sock', 1, NULL, 100, 0, ['auth' => ['phpredis', 'phpredis']]) === true)
                {
                    return true;
                }
                **/
                // return value should be true for success or false for failure
        }
        /**
        * 2ème fonction.
        * ( si nouvelle session ).
        **/
        public function create_sid()
        {
                try
                {
                        $this->lastCreatedId = clientID();
                } catch(Exception $e) {
                        fwrite($this->fp, "Erreur création Id : " . $e->getMessage() . "\n");
                        return false;
                }
                // invoked internally when a new session id is needed
                // no parameter is needed and return value should be the new session id created
                $this->affic("Session Id created", $this->lastCreatedId);
                return $this->lastCreatedId;
        }
        public function validateId($sessionId)
        {
                $this->affic("Session Id checked", $sessionId);
                if ($sessionId !== $this->lastCreatedId) {
                        return false;
                }
                fwrite($this->fp, "session_id() checked : " . session_id() . "\n");
                if(preg_match("{^[a-z0-9-]+$}", session_id()))
                {
                        return true;
                }
                return false;
                // return value should be true if the session id is valid otherwise false
                // if false is returned a new session id will be generated by php internally
                // checks session existance
        }
        /**
        * fonction
        * lecture
        * de l'array
        * lue par hGetAll().
        **/
        protected function affic($str, $tmp_array)
        {
                if(is_array($tmp_array))
                {
                        foreach($tmp_array as $key => $value)
                        {
                                fwrite($this->fp, "\t\t" . $str . " : " . $key . " => " . $value . "\n");
                        }
                }
                else
                {
                        fwrite($this->fp, "\t\t" . $str . " : " . $tmp_array . "\n");
                }
        }
        /**
        * Dernière fonction.
        **/
        public function close()
        {
                if($this->gc(MAXLIFETIME) === false)
                {
                        fwrite($this->fp, "\t\tFailed updating session data while closing the session.\n");
                        fclose($this->fp);
                        fwrite($this->fp, "\t\tClosing the session.\n");
                        $this->redisConn->close();
                        return false;
                }
                fwrite($this->fp, "\t\tClosing the session.\n");
                fclose($this->fp);
                return $this->redisConn->close();
                // return value should be true for success or false for failure
        }
        /**
        * 3ème fonction
        * si nouvelle fonction,
        * ou 2ème fonction.
        **/
        public function read($id)
        {
                $array_read        = array();
                try
                {
                        $array_read = $this->redisConn->hGetAll($id);
                } catch(Exception $err) {
                        fwrite($this->fp, "Erreur read Data : " . $e->getMessage() . "\n");
                        return false;
                }
                $this->affic("Data read", $array_read);
                if ((is_array($array_read))&&
                        (count($array_read) > 0))
                {
                        $_SESSION = $array_read;
                        return serialize($array_read);
                }
                return "";
        }
        /**
        * Avant-dernière fonction,
        * ( si session_write_close(); )
        **/
        public function write($id, $data)
        {
                $array_write = array();
                if(strlen($data) > 0)
                {
                        $array_write = unserialize($data);
                        if(count($array_write) > 0)
                        {
                                foreach($array_write as $key => $value)
                                {
                                        if($this->redisConn->hSet($id, $key, $value) === false)
                                        {
                                                $this->affic("Failed writing key : " . $key, $value);
                                                return false;
                                        }
                                }
                                if($this->redisConn->expire($id, MAXLIFETIME) === false)
                                {
                                    $this->affic("Succeded setting key TTL : " . $id . " => " . $key, $value);
                                    return false;
                                }
                        }
                }
                $this->affic("Data written", $array_write);
                return true;
                //                return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
                // return value should be true for success or false for failure
        }
        /**
        * Avant-dernière fonction,
        *         après 
        *    session_regenerate_id(),
        *    session_destroy(),
        *    ou session_decode() === false
        **/
        public function destroy($id)
        {
                /**
                * Seulement
                * pour la session
                *     $id.
                */
                $array_del = array();
                $array_del = $this->redisConn->hGetAll($id);
                $this->affic("Data destroyed", $array_del);
                foreach($array_del as $key => $value)
                {
                        if($this->redisConn->hDel($id, $key) === false)
                        {
                                $this->affic("Failed to destroy the session key : " . $id . " => " . $key, $value);
                                return false;
                        }
                }
                if($this->redisConn->del($id) === false)
                {
                        fwrite($this->fp, "\t\tFailed to destroy the session.\n");
                        return false;
                }
                return true;
                // return value should be true for success or false for failure
        }
        /**
        * Après session_start(),
        * en fonction de 
        * session.gc_divisor, 
        * session.gc_probability, 
        * session.gc_maxlifetime
        **/
        /**
        * Fonction globale
        */
        public function gc($maxlifetime)
        {
                $array_keys = array();
                /**
                * Lecture $str_key
                * des id_sessions.
                **/
                $array_keys = $this->redisConn->keys("*");
                $this->affic("\$array_keys", $array_keys);
                foreach($array_keys as $str_key)
                {
                    if($str_key == $this->lastCreatedId)
                    {
                        continue;
                    }
//                    if(preg_match("#^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$#", $str_key))
                    if(preg_match("#^[a-z0-9-]+$#", $str_key))
                    {
                        unset($ttl);
                        $ttl = $this->redisConn->ttl($str_key);
//                        $this->affic("Key TTL : " . $str_key, $ttl);
                        if(($ttl < 0)&&($ttl != -2))
                        {
//                            $this->affic("Key TTL : " . $str_key, $ttl);
                            $array_del = array();
                            $array_del = $this->redisConn->hGetAll($str_key);
                            $this->affic("Array_keys to delete", $array_del);
                            foreach($array_del as $key => $value)
                            {
                                $this->affic("Key to be deleted = " . $str_key, $key);
                                if($this->redisConn->hDel($str_key, $key) === false)
                                {
                                    $this->affic("Failed deleting key : " . $str_key . " => " . $key, $value);
                                    return false;
                                }
                                else
                                {
                                    $this->affic("Succeded deleting key : " . $str_key . " => " . $key, $value);
                                }
                            }
                            if($this->redisConn->del($str_key) === false)
                            {
                                    $this->affic("Failed deleting key", $str_key);
                                    return false;
                            }
                            else
                            {
                                $this->affic("Succeded deleting key", $str_key);
                            }
                        }
                    }
                }
                return true;
                // return value should be true for success or false for failure
                /**
                foreach (glob("$this->savePath/sess_*") as $file) {
                        if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
                                unlink($file);
                        }
                }
                **/
        }
        public function updateTimestamp($id, $data)
        {
                $array_updated    = array();
                $array_read        = array();
                $array_read        = unserialize($data);
                //    $array_read = $this->redisConn->hGetAll($id);
                foreach($array_read as $key => $value)
                {
                    $this->affic("Attempting to update the session key " . $key . " TTL = " . $this->redisConn->ttl($key), $value);
                    if($this->redisConn->hExists($this->lastCreatedId, $key) === true)
                    {
                        if($this->redisConn->expire($key, MAX_IDLE_TIME) === false)
                        {
                            $this->affic("Failed while updating the session key TTL" . $key, $value);
                            return false;
                        }
                        $this->affic("Succeded while updating the session key TTL" . $key, $value);
                    }
                }
                       
                return true;
                // return value should be true for success or false for failure
        }
}
$handler = new CustomSessionHandler();
// if (session_status() == PHP_SESSION_NONE) {session_start();}
//    session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid [, callable $validate_sid [, callable $update_timestamp ]]] ) : bool
?>
 
WRInaute accro
Rebond.

La fonction gc() ne se déclenche que si (time() % 6000) < 6).

J'obtiens environ 180 clés simultanées dans redis.log.

J'ai mis celà sur mon site.

Prenez si vous voulez.

PHP:
<?php

/**
  * session_handler.php
 **/
require(__DIR__ . '/clientID.php');
define('MAXLIFETIME', ini_get('session.gc_maxlifetime'));
define('MAX_IDLE_TIME', 300);
define('MIN_LEFT_TIME', 30);
define('ID_KEY', 'x9NNGejdBGrwlZCkBBAqTYZlD82tzj4w');
define('COUNT_KEY', 'COUNT_KEY');
define('GC_PERIOD', 6000);
class CustomSessionHandler implements SessionHandlerInterface,
    SessionIdInterface,
    SessionUpdateTimestampHandlerInterface
{
    private $redisConn;
    private $lastCreatedId;
    private $fp;
    public function    __construct()
    {
        session_set_save_handler(
            array($this, "open"),
            array($this, "close"),
            array($this, "read"), 
            array($this, "write"), 
            array($this, "destroy"), 
            array($this, "gc"), 
            array($this, "create_sid"), 
            array($this, "validateId"), 
            array($this, "updateTimestamp"));
        // Set the shutdown function
        register_shutdown_function('session_write_close');
        // Define and initialise the Session Handler
        session_start();
        return true;
    }
    /**
    * 1ère fonction.
    **/
    public function open($savePath, $sessionName)
    {
                /**
                Parameters
                    host: string. can be a host, or the path to a unix domain socket. Starting from version 5.0.0 it is possible to specify schema port: int, optional
                    timeout: float, value in seconds (optional, default is 0 meaning unlimited)
                    reserved: should be NULL if retry_interval is specified
                    retry_interval: int, value in milliseconds (optional)
                    read_timeout: float, value in seconds (optional, default is 0 meaning unlimited)
                **/
        try
        {
            $this->redisConn = new Redis();
        } catch(Exception $e) {
            return false;
        }   
        try
        {
            $this->redisConn->connect('/var/run/redis/redis.sock'); // unix domain socket.
        } catch(Exception $e) {
            $this->redisConn->close();
            return false;
        }
        try
        {
            $this->redisConn->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
            return true;
        } catch(Exception $e) {
            $this->redisConn->close();
            return false;
        }
        return false;
        /* With PhpRedis >= 5.3.0 you can specify authentication information on connect */
                /**
                if($this->redisConn->connect('/var/run/redis/redis.sock', 1, NULL, 100, 0, ['auth' => ['phpredis', 'phpredis']]) === true)
                {
                    return true;
                }
                **/
        // return value should be true for success or false for failure
    }
    /**
    * 2ème fonction.
    * ( si nouvelle session ).
    **/
    public function create_sid()
    {
        try
        {
            $this->lastCreatedId = clientID();
        } catch(Exception $e) {
            return false;
        }
        // invoked internally when a new session id is needed
        // no parameter is needed and return value should be the new session id created
        return $this->lastCreatedId;
    }
    public function validateId($sessionId)
    {
        if ($sessionId !== $this->lastCreatedId) {
            return false;
        }
        if((strlen(session_id()) > 2)&&(preg_match("#^[a-z0-9-]+$#", session_id())))
        {
            return true;
        }
        return false;
        // return value should be true if the session id is valid otherwise false
        // if false is returned a new session id will be generated by php internally
        // checks session existance
    }
    /**
    * Dernière fonction.
    **/
    public function close()
    {
        /**
        if($this->gc(MAXLIFETIME) === false)
        {
            $this->redisConn->close();
            return false;
        }
        **/
        return $this->redisConn->close();
        // return value should be true for success or false for failure
    }
    /**
    * 3ème fonction
    * si nouvelle fonction,
    * ou 2ème fonction.
    **/
    public function affic($key, $tmp_array)
    {
        if(is_array($tmp_array))
        {
                foreach($tmp_array as $key2 => $value)
                {
                    fwrite($this->fp, $key . " => " . $key2 . " => " . $value . "\n");
                }
        }
        else
        {
            fwrite($this->fp, $key . " => " . $tmp_array . "\n");
        }
        return true;
    }
    /**
    * 3ème fonction
    * si nouvelle fonction,
    * ou 2ème fonction.
    **/
    public function read($id)
    {
        $array_read        = array();
        try
        {
            $array_read = $this->redisConn->hGetAll($id);
        } catch(Exception $err) {
            return false;
        }
        if ((is_array($array_read))&&
            (count($array_read) > 0))
        {
            $_SESSION = $array_read;
            return serialize($array_read);
        }
        return "";
    }
    /**
    * Avant-dernière fonction,
    * ( si session_write_close(); )
    **/
    public function run_gc()
    {
        if((time() % GC_PERIOD) <= 6)
        {
            if($this->gc(MAXLIFETIME) === false)
            {
                $this->redisConn->close();
                return false;
            }
        }
        return true;
    }
    /**
    * Avant-dernière fonction,
    * ( si session_write_close(); )
    **/
    public function write($id, $data)
    {
        $array_write = array();
        if(strlen($data) > 0)
        {
            $array_write = unserialize($data);
            if(count($array_write) > 0)
            {
                foreach($array_write as $key => $value)
                {
                    if($this->redisConn->hSet($id, $key, $value) === false)
                    {
                        $this->redisConn->close();
                        return false;
                    }
                    if($this->redisConn->expire($id, MAXLIFETIME) === false)
                    {
                        $this->redisConn->close();
                        return false;
                    }
                }
            }
        }
        return $this->run_gc();
        //                return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
        // return value should be true for success or false for failure
    }
    /**
    * Avant-dernière fonction,
    *         après 
    *    session_regenerate_id(),
    *    session_destroy(),
    *    ou session_decode() === false
    **/
    public function destroy($id)
    {
        /**
        * Seulement
        * pour la session
        *     $id.
        */
        $array_del = array();
        $array_del = $this->redisConn->hGetAll($id);
        foreach($array_del as $key => $value)
        {
            if($this->redisConn->hDel($id, $key) === false)
            {
                return false;
            }
        }
        if($this->redisConn->del($id) === false)
        {
            return false;
        }
        return true;
        // return value should be true for success or false for failure
    }
    /**
    * Après session_start(),
    * en fonction de 
    * session.gc_divisor, 
    * session.gc_probability, 
    * session.gc_maxlifetime
    **/
    /**
    * Fonction globale
    */
    public function gc($maxlifetime)
    {
        $array_keys = array();
        $i = 0;
        $iterator = null;
        while($iterator !== 0)
        {
            /**
            * Lecture $str_key
            * des id_sessions.
            **/
            $array_keys = $this->redisConn->scan($iterator, '*');
               
            $array_only_keys = preg_filter("{([a-z]+|-)}", $array_keys);
            $i += count($array_keys);
            foreach($array_keys as $str_key)
            {
                if(($str_key == $this->lastCreatedId)||
                        ($str_key == ID_KEY))
                {
                        continue;
                }
                if((strlen($str_key) > 2)&&(preg_match("#^[a-z0-9-]+$#", $str_key)))
                {
                    unset($ttl);
                    $ttl = $this->redisConn->ttl($str_key);
                    if(($ttl < 0)&&($ttl != -2))
                    {
                        $array_del = array();
                        $array_del = $this->redisConn->hGetAll($str_key);
                        foreach($array_del as $key => $value)
                        {
                            if($this->redisConn->hDel($str_key, $key) === false)
                            {
                                return false;
                            }
                        }
                        if($this->redisConn->del($str_key) === false)
                        {
                            return false;
                        }
                    }
                }
            }
        }
        $this->fp = fopen(__DIR__ . "/redis.log", "w");
       
        if($this->redisConn->hSet(ID_KEY, COUNT_KEY, $i) === false)
        {
            $this->affic("\$this->redisConn->hSet(ID_KEY, COUNT_KEY", $this->redisConn->hGet(ID_KEY, COUNT_KEY) . " Keys failed.");
            return false;
        }
        $this->affic("\$this->redisConn->hSet(ID_KEY, COUNT_KEY", $this->redisConn->hGet(ID_KEY, COUNT_KEY) . " Keys succeded.");
        fclose($this->fp);
        return true;
        // return value should be true for success or false for failure
                /**
                foreach (glob("$this->savePath/sess_*") as $file) {
                        if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
                                unlink($file);
                        }
                }
                **/
    }
    public function updateTimestamp($id, $data)
    {
        $array_updated    = array();
        $array_read        = array();
        $array_read        = unserialize($data);
        //    $array_read = $this->redisConn->hGetAll($id);
        foreach($array_read as $key => $value)
        {
            if($this->redisConn->hExists($id, $key) === true)
            {
                if($this->redisConn->expire($id, MAX_IDLE_TIME) === false)
                {
                    return false;
                }
                return true;
            }
        }
        return true;
        // return value should be true for success or false for failure
    }
}
$handler = new CustomSessionHandler();
// if (session_status() == PHP_SESSION_NONE) {session_start();}
//    session_set_save_handler ( callable $open , callable $close , callable $read , callable $write , callable $destroy , callable $gc [, callable $create_sid [, callable $validate_sid [, callable $update_timestamp ]]] ) : bool
?>
 
WRInaute accro
Excusez-moi

J'ai mis le gc.filemtime à 1440 et le gc.divisor à 1000 ( après 100 ).

Je me suis aperçu que la fonction gc() était lancée sans que j'ais besoin de le faire manuellement.

Voici le log.

read Keys c'est le nbre total de clés-enregs

delete Keys c'est le nbre de clés-enregs à deleter.

Le blème, c'est que aucune clé semble exister.

Donc il n'y a pas de delete faits.

Cependant, le nombre de clés total n'augmente pas.

Par quel miracle, les clés sont effacées avant que gc() ne soit lancée ?

Le DELAY c'était quand je faisais moi-même les lancements de gc().

Merci beaucoup de votre aide.

Amicalement.

Code:
*************************************************************************
    DELAY = 600 secondes, Date mesure = 03/08/2020 11:03:35
*************************************************************************
    $this->redisConn->hSet(ID_KEY, GC_KEY => 128 read Keys succeded.
    $this->redisConn->hSet(ID_KEY, GC_DELETED => 0 delete Keys succeded.
*************************************************************************
    DELAY = 600 secondes, Date mesure = 03/08/2020 11:05:07
*************************************************************************
    $this->redisConn->hSet(ID_KEY, GC_KEY => 107 read Keys succeded.
    $this->redisConn->hSet(ID_KEY, GC_DELETED => 0 delete Keys succeded.
*************************************************************************
    DELAY = 600 secondes, Date mesure = 03/08/2020 11:12:40
*************************************************************************
    $this->redisConn->hSet(ID_KEY, GC_KEY => 99 read Keys succeded.
    $this->redisConn->hSet(ID_KEY, GC_DELETED => 0 delete Keys succeded.
*************************************************************************
    DELAY = 600 secondes, Date mesure = 03/08/2020 11:19:28
*************************************************************************
    $this->redisConn->hSet(ID_KEY, GC_KEY => 89 read Keys succeded.
    $this->redisConn->hSet(ID_KEY, GC_DELETED => 0 delete Keys succeded.
*************************************************************************
    DELAY = 600 secondes, Date mesure = 03/08/2020 11:19:38
*************************************************************************
    $this->redisConn->hSet(ID_KEY, GC_KEY => 90 read Keys succeded.
    $this->redisConn->hSet(ID_KEY, GC_DELETED => 0 delete Keys succeded.
*************************************************************************
    DELAY = 600 secondes, Date mesure = 03/08/2020 11:20:05
*************************************************************************
    $this->redisConn->hSet(ID_KEY, GC_KEY => 100 read Keys succeded.
    $this->redisConn->hSet(ID_KEY, GC_DELETED => 0 delete Keys succeded.
*************************************************************************
    DELAY = 600 secondes, Date mesure = 03/08/2020 11:20:18
*************************************************************************
    $this->redisConn->hSet(ID_KEY, GC_KEY => 98 read Keys succeded.
    $this->redisConn->hSet(ID_KEY, GC_DELETED => 0 delete Keys succeded.
 
Discussions similaires
Haut