AggregateCache.class.php

Go to the documentation of this file.
00001 <?php
00002 /****************************************************************************
00003  *   Copyright (C) 2005-2008 by Anton E. Lebedevich, Konstantin V. Arkhipov *
00004  *                                                                          *
00005  *   This program is free software; you can redistribute it and/or modify   *
00006  *   it under the terms of the GNU Lesser General Public License as         *
00007  *   published by the Free Software Foundation; either version 3 of the     *
00008  *   License, or (at your option) any later version.                        *
00009  *                                                                          *
00010  ****************************************************************************/
00011 
00018     class AggregateCache extends SelectivePeer
00019     {
00020         const LEVEL_ULTRAHIGH   = 0xFFFF;
00021         const LEVEL_HIGH        = 0xC000;
00022         const LEVEL_NORMAL      = 0x8000;
00023         const LEVEL_LOW         = 0x4000;
00024         const LEVEL_VERYLOW     = 0x0001;
00025 
00026         protected $peers    = array();
00027         private $levels     = array();
00028 
00032         public static function create()
00033         {
00034             return new self;
00035         }
00036 
00040         public function addPeer(
00041             $label, CachePeer $peer, $level = self::LEVEL_NORMAL
00042         )
00043         {
00044             if (isset($this->peers[$label]))
00045                 throw new WrongArgumentException(
00046                     'use unique names for your peers'
00047                 );
00048             
00049             if ($peer->isAlive()) {
00050                 $this->peers[$label]['object'] = $peer;
00051                 $this->peers[$label]['level'] = $level;
00052                 $this->peers[$label]['stat'] = array();
00053                 $this->alive = true;
00054             }
00055             
00056             return $this;
00057         }
00058         
00062         public function dropPeer($label)
00063         {
00064             if (!isset($this->peers[$label]))
00065                 throw new MissingElementException(
00066                     "there is no peer with '{$label}' label"
00067                 );
00068 
00069             unset($this->peer[$label]);
00070             
00071             return $this;
00072         }
00073         
00077         public function setClassLevel($class, $level)
00078         {
00079             $this->levels[$class] = $level;
00080             
00081             return $this;
00082         }
00083 
00084         public function checkAlive()
00085         {
00086             $this->alive = false;
00087             
00088             foreach ($this->peers as $label => $peer)
00089                 if ($peer['object']->isAlive())
00090                     $this->alive = true;
00091                 else
00092                     unset($this->peers[$label]);
00093 
00094             return $this->alive;
00095         }
00096 
00101         public function increment($key, $value)
00102         {
00103             $label = $this->guessLabel($key);
00104             
00105             if ($this->peers[$label]['object']->isAlive())
00106                 return $this->peers[$label]['object']->increment($key, $value);
00107             else
00108                 $this->checkAlive();
00109             
00110             return null;
00111         }
00112         
00113         public function decrement($key, $value)
00114         {
00115             $label = $this->guessLabel($key);
00116             
00117             if ($this->peers[$label]['object']->isAlive())
00118                 return $this->peers[$label]['object']->decrement($key, $value);
00119             else
00120                 $this->checkAlive();
00121             
00122             return null;
00123         }
00124         
00125         public function get($key)
00126         {
00127             $label = $this->guessLabel($key);
00128             
00129             if ($this->peers[$label]['object']->isAlive())
00130                 return $this->peers[$label]['object']->get($key);
00131             else
00132                 $this->checkAlive();
00133             
00134             return null;
00135         }
00136         
00137         public function getList($indexes)
00138         {
00139             $labels = array();
00140             $out = array();
00141             
00142             foreach ($indexes as $index)
00143                 $labels[$this->guessLabel($index)][] = $index;
00144             
00145             foreach ($labels as $label => $indexList)
00146                 if ($this->peers[$label]['object']->isAlive()) {
00147                     if ($list = $this->peers[$label]['object']->getList($indexList))
00148                         $out = array_merge($out, $list);
00149                 } else
00150                     $this->checkAlive();
00151             
00152             return $out;
00153         }
00154         
00155         public function delete($key)
00156         {
00157             $label = $this->guessLabel($key);
00158             
00159             if (!$this->peers[$label]['object']->isAlive()) {
00160                 $this->checkAlive();
00161                 return false;
00162             }
00163 
00164             return $this->peers[$label]['object']->delete($key);
00165         }
00166 
00170         public function clean()
00171         {
00172             foreach ($this->peers as $peer)
00173                 $peer['object']->clean();
00174 
00175             $this->checkAlive();
00176 
00177             return parent::clean();
00178         }
00179 
00180         public function getStats()
00181         {
00182             $stats = array();
00183 
00184             foreach ($this->peers as $level => $peer)
00185                 $stats[$level] = $peer['stat'];
00186 
00187             return $stats;
00188         }
00189         
00190         public function append($key, $data)
00191         {
00192             $label = $this->guessLabel($key);
00193             
00194             if ($this->peers[$label]['object']->isAlive())
00195                 return $this->peers[$label]['object']->append($key, $data);
00196             else
00197                 $this->checkAlive();
00198             
00199             return false;
00200         }
00201         
00202         protected function store(
00203             $action, $key, $value, $expires = Cache::EXPIRES_MINIMUM
00204         )
00205         {
00206             $label = $this->guessLabel($key);
00207             
00208             if ($this->peers[$label]['object']->isAlive())
00209                 return
00210                     $this->peers[$label]['object']->$action(
00211                         $key,
00212                         $value,
00213                         $expires
00214                     );
00215             else
00216                 $this->checkAlive();
00217             
00218             return false;
00219         }
00220 
00224         protected function guessLabel($key)
00225         {
00226             $class = $this->getClassName();
00227 
00228             if (isset($this->levels[$class]))
00229                 $classLevel = $this->levels[$class];
00230             else
00231                 $classLevel = self::LEVEL_NORMAL;
00232             
00233             // init by $key, randomness will be restored later
00234             mt_srand(hexdec(substr(md5($key), 3, 7)));
00235 
00236             $zeroDistances = array();
00237             $weights = array();
00238 
00239             foreach ($this->peers as $label => $peer) {
00240                 $distance = abs($classLevel - $peer['level']);
00241 
00242                 if (!$distance)
00243                     $zeroDistances[] = $label;
00244                 else
00245                     $weights[$peer['level']] = 1 / pow($distance, 2); // BOVM
00246             }
00247             
00248             if (count($zeroDistances)) {
00249 
00250                 $selectedLabel =
00251                     $zeroDistances[mt_rand(0, count($zeroDistances) - 1)];
00252 
00253             } else {
00254 
00255                 // weighted random level selection
00256                 $sum = mt_rand() * array_sum($weights) / mt_getrandmax();
00257                 $peerLevel = null;
00258                 foreach ($weights as $level => $weight) {
00259                     if ($sum <= $weight) {
00260                         $peerLevel = $level;
00261                         break;
00262                     } else
00263                         $sum -= $weight;
00264                 }
00265 
00266                 $selectedPeers = array();
00267                 foreach ($this->peers as $label => $peer) {
00268                     if ($peer['level'] == $peerLevel)
00269                         $selectedPeers[] = $label;
00270                 }
00271 
00272                 $selectedLabel = $selectedPeers[mt_rand(0, count($selectedPeers) - 1)];
00273             }
00274 
00275             if (isset($this->peers[$selectedLabel]['stat'][$class]))
00276                 ++$this->peers[$selectedLabel]['stat'][$class];
00277             else
00278                 $this->peers[$selectedLabel]['stat'][$class] = 1;
00279             
00280             // restore randomness
00281             mt_srand(
00282                 (int) (
00283                     (int) (microtime(true) << 2)
00284                     * (rand(time() / 2, time()) >> 2)
00285                 )
00286             );
00287             
00288             return $selectedLabel;
00289         }
00290     }
00291 ?>