Ouais, ça fait un peu slogan d’une pub américaine (mal doublée en français) pour un merveilleux objet du télé-achat qu’il faut absolument acheter… mais quand même, memcache c’est drôlement bien.
Pourquoi utiliser un cache?
La question est un peu débile, tout le monde comprend bien l’intérêt d’utiliser un cache… mais il faut cependant bien choisir les données a y mettre et (surtout) utiliser le(s) bon(s) système(s).
Quand on construit une page web dynamiquement, c’est un peu crétin de la générer 20 fois si les données n’ont pas changé ou si une mise a jour hebdomadaire suffit. On peut alors, en ajoutant les headers HTTP qui vont bien, forcer l’utilisation du cache du navigateur, pour éviter qu’une même personne ne régénère 10 fois la même page a coups de F5 (cf. RFC 2616). On peut aussi générer la page une première fois (lors de la première requête) puis enregistrer le résultat pour le resservir ensuite aux autres. On peut même faire les deux en même temps et utiliser des
outils-tout-fait t-as-rien-a-faire et ça marche en 5 minutes (cf. JP cache par exemple pour une page en php). Il est également possible d’utiliser Apache et Squid pour faire des choses sympas, ou même encore utiliser le cache de MySQL si on est fou et/ou si on sait ce que l’on fait.
Dans la majorité des cas, les données (souvent sérialisées et compressées) sont stockées dans un fichier. C’est bien mais c’est pas super rapide, surtout quand on doit partager le cache entre plusieurs serveurs et qu’on utilise donc NFS par exemple, ou que l’on doit partager des données entre plusieurs
applications. Une solution est donc d’utiliser memcache.
Memcached, c’est quoi ?
Comme ils le disent eux-même, “memcached is a high-performance, distributed memory object caching system, generic in nature, but intended for use in speeding up dynamic web applications by alleviating database load.”. Donc en gros, memcache un système de cache qui stocke les données en ram pour être rapide. On fait donc tourner un daemon sur notre serveur, on se connecte dessus (tcp) et lui envoie et/ou demande des données, c’est extrêmement simple. Il faut cependant bien garder en tête que les données stockées en mémoire partagée sont perdu lors d’un reboot, on sera donc intelligent et on y mettra pas n’importe quoi. L’idéal est bien de l’utiliser en complément d’un autre système de cache (file system) et d’y stocker uniquement des données pouvant être volatiles (des resultsets par exemple).
Installer et configurer memcached
Télécharge et compile les dernières sources disponibles : http://www.danga.com/memcached/download.bml
ou utilise apt-get si tu as un vrai os.
panjhy@sushi:~$ sudo apt-get install memcached
Edite le fichier /etc/memcached.conf, modifie le port et l’ip a écouter, et la mémoire a allouer si besoin est (il est également recommandé de créer un user spécifique pour lancer le daemon).
Lance memcached en utilisant la ligne de commande suivante :
panjhy@sushi:~$ sudo /etc/init.d/memcached start
Vérifie que tout s’est bien passé, qu’aucune erreur n’a été loggée (fichier de logs par default : /var/log/memcached).
Configurer php
Télécharge l’extension PECL a l’adresse suivante : http://pecl.php.net/package/memcache
Décompresse l’archive et compile :
panjhy@sushi:/tmp$ wget http://pecl.php.net/get/memcache-2.1.0.tgz panjhy@sushi:/tmp$ tar zxvf memcache-2.1.0.tgz panjhy@sushi:/tmp$ cd memcache-2.1.0 panjhy@sushi:/tmp/memcache-2.1.0$ phpize panjhy@sushi:/tmp/memcache-2.1.0$ ./configure panjhy@sushi:/tmp/memcache-2.1.0$ sudo make panjhy@sushi:/tmp/memcache-2.1.0$ sudo make install
(note: on peut aussi installer une extension PECL via PEAR, c’est plu simple quand ca marche)
Edite ton php.ini et ajoute l’extension memcache dans la section extensions :
extension=memcache.so
Redémarre apache (si php est en module, sinon kill tes process cgi) et c’est fini
panjhy@sushi:/tmp/memcache-2.1.0$ sudo /etc/init.d/apache2 restart
Jete un oeil voir si tout c’est bien passe :
panjhy@sushi:/usr/local/lib/php$ php -r "phpinfo();" | grep memcache memcache memcache support => enabled memcache.allow_failover => 1 => 1 memcache.chunk_size => 8192 => 8192 memcache.default_port => 11211 => 11211 memcache.max_failover_attempts => 20 => 20 sushi:/usr/local/lib/php#
Utiliser mencache avec php
Toute la doc se trouve par la :
http://php.net/manual/en/ref.memcache.php
Les 3 fonctions vitales sont :
- Memcache::conect qui permet de se connecter a memcached. On préférera quand même utiliser Memcache::addServer car, comme on peut le lire dans la doc, sur une architecture 32 bits on ne peut pas assigner plus de 4Go par process, on pourra par contre lancer plusieurs daemon.
- Memcache::set permet de placer un item en mémoire. Le troisième paramètre est le TTL (durée de vie de l’item en cache). Je trouve bien plus logique de fixer le TTL au moment où on met quelque chose en cache que de checker si l’item a expiré quand on le récupère.
- Memcache::get permet de récupérer un item a partir du cache, retourne false si l’item n’est plus valide.
Voici une petite classe pour l’utiliser simplement :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | <?php class mcd { private $_memcache = NULL; // (object) memcache instance private $_status = NULL; // (bool) cache enable private $_params = array( 'use_pconnect' => false, // (bool) use persistent connexion 'use_zlib' => false, // (bool) use zlib to store conpressed data 'timeout' => 1, // (int) timeout (secondes) 'default_ttl' => 86400 // (int) default item ttl (secondes) ); private $_servers = array( array( 'host' => 'localhost', // (srting) hostname or ip 'port' => '11211', // (int) tcp port 'weight' => 50 // (int) server's weight ), // array( // 'host' => 'mcd.plumbr.com', // 'port' => '11211', // 'weight' => 10 // ), ); /** * New instance of memcache * * @param array $params parameters * @param array $servers servers pool */ public function __construct($params = array(), $servers = NULL) { $this->_status = (true === defined('CACHE_ENABLE') and true === CACHE_ENABLE ? true : false); $this->_params = array_merge($this->_params, $params); $this->_params['use_zlib'] = (true === $this->_params['use_zlib'] ? MEMCACHE_COMPRESSED : 0); if(NULL !== $servers) $this->_servers = $servers; if(true === $this->_status) { try { $this->_memcache = new Memcache; assert('true === is_object($this->_memcache)'); $this->_connect(); } catch(Exception $e) { trigger_error(var_export($e, true), E_USER_WARNING); } } } /** * (void) Connect to the memcache pool servers */ private function _connect() { foreach($this->_servers as $server) { if(false === array_key_exists('host', $server) or false === array_key_exists('port', $server)) throw new Exception('Bad server config :/'); if(false === $this->_memcache->addServer( $server['host'], $server['port'], $this->_params['use_pconnect'], (true === array_key_exists('weight', $server) ? $server['weight'] : 50), $this->_params['timeout'] )) throw new Exception('Cannot connect to memcached '.$server['host'].':'.$server['port']); } } /** * Return the value cached item * * @param mixed $data item value * @param string $name item key * @param int $ttl item cache ttl * @return mixed return false if an erro rhas occured */ public function set(&$data, $name, $ttl = NULL) { assert('true === is_string($name)'); if(false === $this->_status) return false; if(NULL === $ttl) $ttl = $this->_params['default_ttl']; if(false === $this->_memcache->set($name, $data, $this->_params['use_zlib'], $ttl)) { trigger_error('Cannot set '.$name.' into memcache server', E_USER_WARNING); return false; } return true; } /** * Retrieve item from the server * * @param string $name item's name * @return mixed return false if an error has occured */ public function get($name) { assert('true === is_string($name)'); return (false === $this->_status ? false : $this->_memcache->get($name)); } /** * (void) Flush all existing items */ public function flush() { $this->_memcache->flush(); } /** * Destructor: close the connexions to memcached */ public function __destruct() { if($this->_memcache) $this->_memcache->close(); } } error_reporting(E_ALL); ini_set('display_errors', 1); define('CACHE_ENABLE' , true); $data = array('foo' => time()); // data to store $cache = new mcd(array('use_zlib' => true)); $cache->set($data, 'foo', 2); // store $data var_dump($cache->get('foo')); // retreive $data sleep(3); var_dump($cache->get('foo')); // test cache expiration: have to return false $cache->set($data, 'foo'); var_dump($cache->get('foo')); $cache->flush(); // flush entire cached items var_dump($cache->get('foo')); // test cache flush: have to return false ?> |
