Archive pour janvier, 2009

PHP caching Opcode

admin janvier 16th, 2009

Parmis les outils sympathiques dont nous disposons pour alléger les ressources processeur d’un serveur Apache / Php il existe des solutions dite de caching Opcode.

En précompilant les scripts PHP et en les mettant en cache elles soulagent le compilateur Php. Elles permettent de manière significative d’alléger le travail du(es) processeurs d’un serveur et donc par extension d’accepter aussi plus de trafic sur un seul serveur ;-).

La solution que j’ai tésté sous débian et approuvé depuis maintenant 6 mois est Eaccelerator (Sans aucun problème)

Voici la suite de commandes à passer dans le shell de votre serveur linux :

aptitude install php5-dev
wget http://bart.eaccelerator.net/source/0.9.5.3/eaccelerator-0.9.5.3.tar.bz2
 
bunzip2 eaccelerator-0.9.5.3.tar.bz2
tar xvf eaccelerator-0.9.5.3.tar
 
cd ./eaccelerator-0.9.5.3
 
phpize
 
./configure
 
make
 
make install

Editer le fichier /etc/php5/apache2/php.ini
Insérer les infos de configuration au niveau des extensions :

extension="eaccelerator.so"
eaccelerator.shm_size="256"
eaccelerator.cache_dir="/var/cache/eaccelerator"
eaccelerator.enable="1"
eaccelerator.optimizer="1"
eaccelerator.check_mtime="1"
eaccelerator.debug="0"
eaccelerator.filter=""
eaccelerator.shm_max="0"
eaccelerator.shm_ttl="0"
eaccelerator.shm_prune_period="0"
eaccelerator.shm_only="0"
eaccelerator.compress="1"
eaccelerator.compress_level="9"

Créer le répertoire de stockage des fichiers précompilés

mkdir /var/cache/eaccelerator
chmod 777 /var/cache/eaccelerator

Réloader Apache

/etc/init.d/apache2 reload

Vérifier dans le phpinfo si Eaccelerator est actif et lancé.

Et ensuite les essais de montée en charge sous apache.. Croustillant ;-)

Un simple petit ab va vous donner des ailes quand à l’optimisation que le caching Opcode amène.

Avec eaccelerator :
ab -n 1000 -c 20 http://url
>>>> Requests per second: 156.29 [#/sec]

Sans eaccelerator :
>>>> Requests per second: 44.46 [#/sec]

Résultat on a divisé la charge serveur par 3 (en gros) ou si vous préférez le serveur maintenant accèpte 150 reqùetes à la seconde au lieu de 45 à la seconde.

J’ai lu que parfois certains système de caching Opcode avaient du mal à rafraichir leurs sources en cas de mise à jour massive de scripts php.  Eaccelerator n’est à priori pas de ceux la.

Si vous faites évoluer votre version de php, n’oubliez pas de recompiler eaccelerator.
Avant la recompilation faites un

make clean

Références :
http://www.eaccelerator.net/
http://www.ipersec.com/index.php/2006/05/30/benchmarking-php-accelerators/
http://2bits.com/articles/benchmarking-apc-vs-eaccelerator-using-drupal.html

array_key_filter - Filtrer simplement un tableau sur ses colonnes (array)

admin janvier 8th, 2009

Voici une fonction qui permet avec une logique identique au Sql de filtrer les données d’un tableau sur des colonnes, et aussi donc de filtrer un résultat Sql.

Pour par exemple filtrer une liste de produit sur la taille, la couleur, le prix, tout critère disponible dans le tableau passer par ce genre de fonction soulage le serveur sql d’une requête par utilisateur.

Comment cela fonctionne :

1 - Un tableau de donnée avec de colonnes

$db_produit=array();
 
$db_produit[0]["codebar"]="111111";
$db_produit[0]["listestailles"]=array("M","XL");
$db_produit[0]["listescouleurs"]=array("Rouge","vert");
$db_produit[0]["prix"]=30;
$db_produit[0]["prix_remise"]=5;
 
$db_produit[1]["codebar"]="1122211";
$db_produit[1]["listestailles"]=array("M","L","S");
$db_produit[1]["listescouleurs"]=array("bleu","vert");
$db_produit[1]["prix"]=150;
$db_produit[1]["prix_remise"]=120;
 
//etc...

Les critères de filtrage des données :

$critere_restriction=array();
$critere_restriction["affichetaille"]=45;
$critere_restriction["affichecouleur"]="bleu";
$critere_restriction["prixmin"]=10;
$critere_restriction["prixmax"]=30;
 
if (count($critere_restriction)>0)
{
	// Restreint l'affichage des produits aux produits selectionnes
	$callback_func = create_function('$db_produit , $critere_restriction','
	if (
 
	( ($critere_restriction["affichetaille"]!="" &&
in_array($critere_restriction["affichetaille"],(array)$db_produit["listestailles"]))
|| $critere_restriction["affichetaille"]=="" )
 
	&&
 
	( ($critere_restriction["affichecouleur"]!="" &&
in_array($critere_restriction["affichecouleur"],(array)$db_produit["listescouleurs"]))
|| $critere_restriction["affichecouleur"]=="" )
 
	&&
 
	( (
	is_numeric($critere_restriction["prixmax"]) &&
is_numeric($critere_restriction["prixmin"])
	&&
	(
	(
	$db_produit["prix"]<=$critere_restriction["prixmax"] &&
$db_produit["prix"]>=$critere_restriction["prixmin"]
	)
	||
	(
	$db_produit["prix_remise"]!=-1 && is_numeric($db_produit["prix_remise"]) &&
$db_produit["prix_remise"]<=$critere_restriction["prixmax"] &&
$db_produit["prix_remise"]>=$critere_restriction["prixmin"]
	)
	)
 
	|| $critere_restriction["prixmin"]==""
	)
	)
 
	)
	{return true;}
	');
 
	// Filtre la base
	$db_produit=array_key_filter($db_produit,$critere_restriction,$callback_func);
	unset($critere_restriction);
	$nombre_total_de_resultat=count($db_produit);
}

La partie qui permet de faire les conditions de filtrage est celle ci. Vous faites simplement une condition qui répond à votre besoin.
Dans cette condition on retourne le résultat de la ligne du tableau si elle correspond à un des critères demandé :

				if (
 
				( ($critere_restriction["affichetaille"]!="" &&
in_array($critere_restriction["affichetaille"],(array)$db_produit["listestailles"]))
|| $critere_restriction["affichetaille"]=="" )
 
				&&
 
				( ($critere_restriction["affichecouleur"]!="" &&
in_array($critere_restriction["affichecouleur"],(array)$db_produit["listescouleurs"]))
|| $critere_restriction["affichecouleur"]=="" )
 
				&&
 
				( (
				is_numeric($critere_restriction["prixmax"]) &&
is_numeric($critere_restriction["prixmin"])
				&&
				(
				(
				$db_produit["prix"]<=$critere_restriction["prixmax"] &&
$db_produit["prix"]>=$critere_restriction["prixmin"]
				)
				||
				(
				$db_produit["prix_remise"]!=-1 && is_numeric($db_produit["prix_remise"]) &&
$db_produit["prix_remise"]<=$critere_restriction["prixmax"] &&
$db_produit["prix_remise"]>=$critere_restriction["prixmin"]
				)
				)
 
				|| $critere_restriction["prixmin"]==""
				)
				)
 
				)
				{return true;}

Voici la fonction de filtrage :

// # array_key_filter ###################################################
// # Permet de filtrer un tableau sur ses colonnes
// # Retour : tableau filtre
function array_key_filter($array,$criteres_restriction=array(),$callback)
{
	$ret = array();
	foreach($array as $key_recherche=>$array_resultat_page)
	{
		if ($callback($array_resultat_page,$criteres_restriction))
		{
			$ret[]=$array_resultat_page;
		}
	}
	return $ret;
}

Php 5 - Caching de données

admin janvier 8th, 2009

Voici un système de cache d’une grande simplicité qui permet :

  • De mettre en cache uniquement certaines parties de pages délivrées à l’internaute
  • De mettre en cache n’importe quelle donnée sous forme de tableau (Ce qui est très intéressant pour soulager un serveur SQL par exemple)

Pour mettre en cache la sortie d’un script par exemple ou d’une partie de la sortie de ce script :

<?php
    // Group , Id, TTL en secondes
    if (!OutputCache::Start("myGroup", "myID", 600)) {
 
        // Generate some output (as you do)...
 
        OutputCache::End();
    }
 
?>

Si votre sortie affiche des informations “aléatoires” ou différentes à chaque affichage de la page, il suffit de passer un nombre aléatoire dans le nom du groupe et de l’id du fichier de cache. Idem en cas de site multilingue, ou pour gérer la cache selon les rubriques d’un site.

Ex :

// Mise en cache de 5 possibilites d'affichage
$alea_cache=rand (1,5);
$cache_ttl=600;
if
(!OutputCache::Start("Groupe_Prod_".$code_langue."_".$id_rubrique."_".$alea_cache,
"Produit_BF_".$code_langue."_".$id_rubrique."_".$alea_cache,$cache_ttl)) {
 
// Ici on passe le code php qui va afficher les données
 
echo "Fichier de cache
".Produit_BF_".$code_langue."_".$id_rubrique."_".$alea_cache;
 
OutputCache::End();
}

Pour mettre en cache un tableau de donnée (array), ou le résultat d’une requête Sql.

<?php
    if (!$data = DataCache::Get("myGroup", "myOtherID")) {
 
        $mesdonnees=array("A","B","C");
        // Group - Id - TTl en secondes
        DataCache::Put("myGroup", "myOtherID", 600, $mesdonnees);
    }
 
    print_r($mesdonnees);
?>

Voici le code de la classe :

<?php
    /**
    *
o------------------------------------------------------------------------------o
    * | This package is licensed under the Phpguru license 2008. A quick
summary is  |
    * | that the code is free to use for non-commercial purposes. For
commercial     |
    * | purposes of any kind there is a small license fee to pay. You can read
more  |
    * | at:                                                                    
     |
    * |                  http://www.phpguru.org/static/license.html            
     |
    *
o------------------------------------------------------------------------------o
    *
    * © Copyright 2008 Richard Heyes
    */
 
 
/**
* Caching Libraries for PHP5
* 
* Handles data and output caching. Defaults to /dev/shm
* (shared memory). All methods are static.
* 
* Eg: (output caching)
* 
* if (!OutputCache::Start('group', 'unique id', 600)) {
* 
*   // ... Output
* 
*   OutputCache::End();
* }
* 
* Eg: (data caching)
* 
* if (!$data = DataCache::Get('group', 'unique id')) {
* 
*   $data = time();
* 
*   DataCache::Put('group', 'unique id', 10, $data);
* }
* 
* echo $data;
*/
    class Cache
    {
        /**
        * Whether caching is enabled
        * @var bool
        */
        public static $enabled = true;
 
        /**
        * Place to store the cache files
        * @var string
        */
        protected static $store = '/dev/shm/';
 
        /**
        * Prefix to use on cache files
        * @var string
        */
        protected static $prefix = 'cache_';
 
        /**
        * Stores data
        * 
        * @param string $group Group to store data under
        * @param string $id    Unique ID of this data
        * @param int    $ttl   How long to cache for (in seconds)
        */
        protected static function write($group, $id, $ttl, $data)
        {
            $filename = self::getFilename($group, $id);
 
            if ($fp = fopen($filename, 'xb')) {
 
                if (flock($fp, LOCK_EX)) {
                    fwrite($fp, $data);
                }
                fclose($fp);
 
                // Set filemtime
                touch($filename, time() + $ttl);
            }
        }
 
        /**
        * Reads data
        * 
        * @param string $group Group to store data under
        * @param string $id    Unique ID of this data
        */
        protected static function read($group, $id)
        {
            $filename = self::getFilename($group, $id);
 
            return file_get_contents($filename);
        }
 
        /**
        * Determines if an entry is cached
        * 
        * @param string $group Group to store data under
        * @param string $id    Unique ID of this data
        */
        protected static function isCached($group, $id)
        {
            $filename = self::getFilename($group, $id);
 
            if (self::$enabled && file_exists($filename) &&
filemtime($filename) > time()) {
                return true;
            }
 
            @unlink($filename);
 
            return false;
        }
 
        /**
        * Builds a filename/path from group, id and
        * store.
        * 
        * @param string $group Group to store data under
        * @param string $id    Unique ID of this data
        */
        protected static function getFilename($group, $id)
        {
            $id = md5($id);
 
            return self::$store . self::$prefix . "{$group}_{$id}";
        }
 
        /**
        * Sets the filename prefix to use
        * 
        * @param string $prefix Filename Prefix to use
        */
        public static function setPrefix($prefix)
        {
            self::$prefix = $prefix;
        }
 
        /**
        * Sets the store for cache files. Defaults to
        * /dev/shm. Must have trailing slash.
        * 
        * @param string $store The dir to store the cache data in
        */
        public static function setStore($store)
        {
            self::$store = $store;
        }
    }
 
    /**
    * Output Cache extension of base caching class
    */
    class OutputCache extends Cache
    {
        /**
        * Group of currently being recorded data
        * @var string
        */
        private static $group;
 
        /**
        * ID of currently being recorded data
        * @var string
        */
        private static $id;
 
        /**
        * Ttl of currently being recorded data
        * @var int
        */
        private static $ttl;
 
        /**
        * Starts caching off. Returns true if cached, and dumps
        * the output. False if not cached and start output buffering.
        * 
        * @param  string $group Group to store data under
        * @param  string $id    Unique ID of this data
        * @param  int    $ttl   How long to cache for (in seconds)
        * @return bool          True if cached, false if not
        */
        public static function Start($group, $id, $ttl)
        {
            if (self::isCached($group, $id)) {
                echo self::read($group, $id);
                return true;
 
            } else {
 
                ob_start();
 
                self::$group = $group;
                self::$id    = $id;
                self::$ttl   = $ttl;
 
                return false;
            }
        }
 
        /**
        * Ends caching. Writes data to disk.
        */
        public static function End()
        {
            $data = ob_get_contents();
            ob_end_flush();
 
            self::write(self::$group, self::$id, self::$ttl, $data);
        }
    }
 
    /**
    * Data cache extension of base caching class
    */
    class DataCache extends Cache
    {
 
        /**
        * Retrieves data from the cache
        * 
        * @param  string $group Group this data belongs to
        * @param  string $id    Unique ID of the data
        * @return mixed         Either the resulting data, or null
        */
        public static function Get($group, $id)
        {
            if (self::isCached($group, $id)) {
                return unserialize(self::read($group, $id));
            }
 
            return null;
        }
 
        /**
        * Stores data in the cache
        * 
        * @param string $group Group this data belongs to
        * @param string $id    Unique ID of the data
        * @param int    $ttl   How long to cache for (in seconds)
        * @param mixed  $data  The data to store
        */
        public static function Put($group, $id, $ttl, $data)
        {
            self::write($group, $id, $ttl, serialize($data));
        }
    }
?>

Il est évident que dès qu’un site commence à monter en charge, ce genre d’outil est très efficace et simple à implémenter. Je l’utilise sur de nombreux site à forte fréquentation et ce dernier ne m’a jamais posé de problème. Il n’a pour le moment et ce depuis 2005 que des avantages. A vos éditeurs de code pour les intéressés.

Référence :

http://www.phpguru.org/static/Caching.html

Remote_Filesize - Connaitre le poids d’un fichier distant - Utile en Php 4.x

admin janvier 8th, 2009

Voici une fonction qui permet de connaitre le poids “d’un fichier distant” pour ceux qui travaillent sur des scripts en php4 et qui ne disposent pas de la fonction get_headers disponible en php5.x.

	function Remote_Filesize($url) {
        $sch = parse_url($url, PHP_URL_SCHEME);
        if (($sch != "http") && ($sch != "https") && ($sch != "ftp") && ($sch
!= "ftps")) {
            return false;
        }
        if (($sch == "http") || ($sch == "https")) {
            $headers = @get_headers($url, 1);
            if (!is_array($headers)){$headers=array();}
            if ((!array_key_exists("Content-Length", $headers))) { return
false; }
            return $headers["Content-Length"];
        }
        if (($sch == "ftp") || ($sch == "ftps")) {
            $server = parse_url($url, PHP_URL_HOST);
            $port = parse_url($url, PHP_URL_PORT);
            $path = parse_url($url, PHP_URL_PATH);
            $user = parse_url($url, PHP_URL_USER);
            $pass = parse_url($url, PHP_URL_PASS);
            if ((!$server) || (!$path)) { return false; }
            if (!$port) { $port = 21; }
            if (!$user) { $user = "anonymous"; }
            if (!$pass) { $pass = "phpos@"; }
            switch ($sch) {
                case "ftp":
                    $ftpid = ftp_connect($server, $port);
                    break;
                case "ftps":
                    $ftpid = ftp_ssl_connect($server, $port);
                    break;
            }
            if (!$ftpid) { return false; }
            $login = ftp_login($ftpid, $user, $pass);
            if (!$login) { return false; }
            $ftpsize = ftp_size($ftpid, $path);
            ftp_close($ftpid);
            if ($ftpsize == -1) { return false; }
            return $ftpsize;
        }
    }