Main Page | Namespace List | Class Hierarchy | Class List | File List | Class Members | File Members | Related Pages

memcached-client.php

Go to the documentation of this file.
00001 <?php 00002 // 00003 // +---------------------------------------------------------------------------+ 00004 // | memcached client, PHP | 00005 // +---------------------------------------------------------------------------+ 00006 // | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net> | 00007 // | All rights reserved. | 00008 // | | 00009 // | Redistribution and use in source and binary forms, with or without | 00010 // | modification, are permitted provided that the following conditions | 00011 // | are met: | 00012 // | | 00013 // | 1. Redistributions of source code must retain the above copyright | 00014 // | notice, this list of conditions and the following disclaimer. | 00015 // | 2. Redistributions in binary form must reproduce the above copyright | 00016 // | notice, this list of conditions and the following disclaimer in the | 00017 // | documentation and/or other materials provided with the distribution. | 00018 // | | 00019 // | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | 00020 // | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | 00021 // | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | 00022 // | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | 00023 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | 00024 // | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 00025 // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 00026 // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 00027 // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 00028 // | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 00029 // +---------------------------------------------------------------------------+ 00030 // | Author: Ryan T. Dean <rtdean@cytherianage.net> | 00031 // | Heavily influenced by the Perl memcached client by Brad Fitzpatrick. | 00032 // | Permission granted by Brad Fitzpatrick for relicense of ported Perl | 00033 // | client logic under 2-clause BSD license. | 00034 // +---------------------------------------------------------------------------+ 00035 // 00036 // $TCAnet$ 00037 // 00038 00064 // {{{ requirements 00065 // }}} 00066 00067 // {{{ constants 00068 // {{{ flags 00069 00073 define("MEMCACHE_SERIALIZED", 1<<0); 00074 00078 define("MEMCACHE_COMPRESSED", 1<<1); 00079 00080 // }}} 00081 00085 define("COMPRESSION_SAVINGS", 0.20); 00086 00087 // }}} 00088 00089 // {{{ class memcached 00096 class memcached 00097 { 00098 // {{{ properties 00099 // {{{ public 00100 00107 var $stats; 00108 00109 // }}} 00110 // {{{ private 00111 00118 var $_cache_sock; 00119 00126 var $_debug; 00127 00134 var $_host_dead; 00135 00142 var $_have_zlib; 00143 00150 var $_compress_enable; 00151 00158 var $_compress_threshold; 00159 00166 var $_persistant; 00167 00174 var $_single_sock; 00175 00182 var $_servers; 00183 00190 var $_buckets; 00191 00198 var $_bucketcount; 00199 00206 var $_active; 00207 00208 // }}} 00209 // }}} 00210 // {{{ methods 00211 // {{{ public functions 00212 // {{{ memcached() 00213 00222 function memcached ($args) 00223 { 00224 $this->set_servers(@$args['servers']); 00225 $this->_debug = @$args['debug']; 00226 $this->stats = array(); 00227 $this->_compress_threshold = @$args['compress_threshold']; 00228 $this->_persistant = array_key_exists('persistant', $args) ? (@$args['persistant']) : false; 00229 $this->_compress_enable = true; 00230 $this->_have_zlib = function_exists("gzcompress"); 00231 00232 $this->_cache_sock = array(); 00233 $this->_host_dead = array(); 00234 } 00235 00236 // }}} 00237 // {{{ add() 00238 00250 function add ($key, $val, $exp = 0) 00251 { 00252 return $this->_set('add', $key, $val, $exp); 00253 } 00254 00255 // }}} 00256 // {{{ decr() 00257 00267 function decr ($key, $amt=1) 00268 { 00269 return $this->_incrdecr('decr', $key, $amt); 00270 } 00271 00272 // }}} 00273 // {{{ delete() 00274 00284 function delete ($key, $time = 0) 00285 { 00286 if (!$this->_active) 00287 return false; 00288 00289 $sock = $this->get_sock($key); 00290 if (!is_resource($sock)) 00291 return false; 00292 00293 $key = is_array($key) ? $key[1] : $key; 00294 00295 @$this->stats['delete']++; 00296 $cmd = "delete $key $time\r\n"; 00297 if(!fwrite($sock, $cmd, strlen($cmd))) 00298 { 00299 $this->_dead_sock($sock); 00300 return false; 00301 } 00302 $res = trim(fgets($sock)); 00303 00304 if ($this->_debug) 00305 $this->_debugprint(sprintf("MemCache: delete %s (%s)\n", $key, $res)); 00306 00307 if ($res == "DELETED") 00308 return true; 00309 return false; 00310 } 00311 00312 // }}} 00313 // {{{ disconnect_all() 00314 00320 function disconnect_all () 00321 { 00322 foreach ($this->_cache_sock as $sock) 00323 fclose($sock); 00324 00325 $this->_cache_sock = array(); 00326 } 00327 00328 // }}} 00329 // {{{ enable_compress() 00330 00338 function enable_compress ($enable) 00339 { 00340 $this->_compress_enable = $enable; 00341 } 00342 00343 // }}} 00344 // {{{ forget_dead_hosts() 00345 00351 function forget_dead_hosts () 00352 { 00353 $this->_host_dead = array(); 00354 } 00355 00356 // }}} 00357 // {{{ get() 00358 00367 function get ($key) 00368 { 00369 if (!$this->_active) 00370 return false; 00371 00372 $sock = $this->get_sock($key); 00373 00374 if (!is_resource($sock)) 00375 return false; 00376 00377 @$this->stats['get']++; 00378 00379 $cmd = "get $key\r\n"; 00380 if (!fwrite($sock, $cmd, strlen($cmd))) 00381 { 00382 $this->_dead_sock($sock); 00383 return false; 00384 } 00385 00386 $val = array(); 00387 $this->_load_items($sock, $val); 00388 00389 if ($this->_debug) 00390 foreach ($val as $k => $v) 00391 $this->_debugprint(@sprintf("MemCache: sock %s got %s => %s\r\n", serialize($sock), $k, $v)); 00392 00393 return @$val[$key]; 00394 } 00395 00396 // }}} 00397 // {{{ get_multi() 00398 00407 function get_multi ($keys) 00408 { 00409 if (!$this->_active) 00410 return false; 00411 00412 $this->stats['get_multi']++; 00413 00414 foreach ($keys as $key) 00415 { 00416 $sock = $this->get_sock($key); 00417 if (!is_resource($sock)) continue; 00418 $key = is_array($key) ? $key[1] : $key; 00419 if (!isset($sock_keys[$sock])) 00420 { 00421 $sock_keys[$sock] = array(); 00422 $socks[] = $sock; 00423 } 00424 $sock_keys[$sock][] = $key; 00425 } 00426 00427 // Send out the requests 00428 foreach ($socks as $sock) 00429 { 00430 $cmd = "get"; 00431 foreach ($sock_keys[$sock] as $key) 00432 { 00433 $cmd .= " ". $key; 00434 } 00435 $cmd .= "\r\n"; 00436 00437 if (fwrite($sock, $cmd, strlen($cmd))) 00438 { 00439 $gather[] = $sock; 00440 } else 00441 { 00442 $this->_dead_sock($sock); 00443 } 00444 } 00445 00446 // Parse responses 00447 $val = array(); 00448 foreach ($gather as $sock) 00449 { 00450 $this->_load_items($sock, $val); 00451 } 00452 00453 if ($this->_debug) 00454 foreach ($val as $k => $v) 00455 $this->_debugprint(sprintf("MemCache: got %s => %s\r\n", $k, $v)); 00456 00457 return $val; 00458 } 00459 00460 // }}} 00461 // {{{ incr() 00462 00472 function incr ($key, $amt=1) 00473 { 00474 return $this->_incrdecr('incr', $key, $amt); 00475 } 00476 00477 // }}} 00478 // {{{ replace() 00479 00490 function replace ($key, $value, $exp=0) 00491 { 00492 return $this->_set('replace', $key, $value, $exp); 00493 } 00494 00495 // }}} 00496 // {{{ run_command() 00497 00514 function run_command ($sock, $cmd) 00515 { 00516 if (!is_resource($sock)) 00517 return array(); 00518 00519 if (!fwrite($sock, $cmd, strlen($cmd))) 00520 return array(); 00521 00522 while (true) 00523 { 00524 $res = fgets($sock); 00525 $ret[] = $res; 00526 if (preg_match('/^END/', $res)) 00527 break; 00528 if (strlen($res) == 0) 00529 break; 00530 } 00531 return $ret; 00532 } 00533 00534 // }}} 00535 // {{{ set() 00536 00548 function set ($key, $value, $exp=0) 00549 { 00550 return $this->_set('set', $key, $value, $exp); 00551 } 00552 00553 // }}} 00554 // {{{ set_compress_threshold() 00555 00563 function set_compress_threshold ($thresh) 00564 { 00565 $this->_compress_threshold = $thresh; 00566 } 00567 00568 // }}} 00569 // {{{ set_debug() 00570 00580 function set_debug ($dbg) 00581 { 00582 $this->_debug = $dbg; 00583 } 00584 00585 // }}} 00586 // {{{ set_servers() 00587 00597 function set_servers ($list) 00598 { 00599 $this->_servers = $list; 00600 $this->_active = count($list); 00601 $this->_buckets = null; 00602 $this->_bucketcount = 0; 00603 00604 $this->_single_sock = null; 00605 if ($this->_active == 1) 00606 $this->_single_sock = $this->_servers[0]; 00607 } 00608 00609 // }}} 00610 // }}} 00611 // {{{ private methods 00612 // {{{ _close_sock() 00613 00621 function _close_sock ($sock) 00622 { 00623 $host = array_search($sock, $this->_cache_sock); 00624 fclose($this->_cache_sock[$host]); 00625 unset($this->_cache_sock[$host]); 00626 } 00627 00628 // }}} 00629 // {{{ _connect_sock() 00630 00641 function _connect_sock (&$sock, $host, $timeout = 0.25) 00642 { 00643 list ($ip, $port) = explode(":", $host); 00644 if ($this->_persistant == 1) 00645 { 00646 $sock = @pfsockopen($ip, $port, $errno, $errstr, $timeout); 00647 } else 00648 { 00649 $sock = @fsockopen($ip, $port, $errno, $errstr, $timeout); 00650 } 00651 00652 if (!$sock) 00653 return false; 00654 return true; 00655 } 00656 00657 // }}} 00658 // {{{ _dead_sock() 00659 00667 function _dead_sock ($sock) 00668 { 00669 $host = array_search($sock, $this->_cache_sock); 00670 @list ($ip, $port) = explode(":", $host); 00671 $this->_host_dead[$ip] = time() + 30 + intval(rand(0, 10)); 00672 $this->_host_dead[$host] = $this->_host_dead[$ip]; 00673 unset($this->_cache_sock[$host]); 00674 } 00675 00676 // }}} 00677 // {{{ get_sock() 00678 00687 function get_sock ($key) 00688 { 00689 if (!$this->_active) 00690 return false; 00691 00692 if ($this->_single_sock !== null) 00693 return $this->sock_to_host($this->_single_sock); 00694 00695 $hv = is_array($key) ? intval($key[0]) : $this->_hashfunc($key); 00696 00697 if ($this->_buckets === null) 00698 { 00699 foreach ($this->_servers as $v) 00700 { 00701 if (is_array($v)) 00702 { 00703 for ($i=0; $i<$v[1]; $i++) 00704 $bu[] = $v[0]; 00705 } else 00706 { 00707 $bu[] = $v; 00708 } 00709 } 00710 $this->_buckets = $bu; 00711 $this->_bucketcount = count($bu); 00712 } 00713 00714 $realkey = is_array($key) ? $key[1] : $key; 00715 for ($tries = 0; $tries<20; $tries++) 00716 { 00717 $host = $this->_buckets[$hv % $this->_bucketcount]; 00718 $sock = $this->sock_to_host($host); 00719 if (is_resource($sock)) 00720 return $sock; 00721 $hv += $this->_hashfunc($tries . $realkey); 00722 } 00723 00724 return false; 00725 } 00726 00727 // }}} 00728 // {{{ _hashfunc() 00729 00738 function _hashfunc ($key) 00739 { 00740 return crc32($key); 00741 } 00742 00743 // }}} 00744 // {{{ _incrdecr() 00745 00756 function _incrdecr ($cmd, $key, $amt=1) 00757 { 00758 if (!$this->_active) 00759 return null; 00760 00761 $sock = $this->get_sock($key); 00762 if (!is_resource($sock)) 00763 return null; 00764 00765 $key = is_array($key) ? $key[1] : $key; 00766 $this->stats[$cmd]++; 00767 if (!fwrite($sock, "$cmd $key $amt\r\n")) 00768 return $this->_dead_sock($sock); 00769 00770 stream_set_timeout($sock, 1, 0); 00771 $line = fgets($sock); 00772 if (!preg_match('/^(\d+)/', $line, $match)) 00773 return null; 00774 return $match[1]; 00775 } 00776 00777 // }}} 00778 // {{{ _load_items() 00779 00788 function _load_items ($sock, &$ret) 00789 { 00790 while (1) 00791 { 00792 $decl = fgets($sock); 00793 if ($decl == "END\r\n") 00794 { 00795 return true; 00796 } elseif (preg_match('/^VALUE (\S+) (\d+) (\d+)\r\n$/', $decl, $match)) 00797 { 00798 list($rkey, $flags, $len) = array($match[1], $match[2], $match[3]); 00799 $bneed = $len+2; 00800 $offset = 0; 00801 00802 while ($bneed > 0) 00803 { 00804 $data = fread($sock, $bneed); 00805 $n = strlen($data); 00806 if ($n == 0) 00807 break; 00808 $offset += $n; 00809 $bneed -= $n; 00810 @$ret[$rkey] .= $data; 00811 } 00812 00813 if ($offset != $len+2) 00814 { 00815 // Something is borked! 00816 if ($this->_debug) 00817 $this->_debugprint(sprintf("Something is borked! key %s expecting %d got %d length\n", $rkey, $len+2, $offset)); 00818 00819 unset($ret[$rkey]); 00820 $this->_close_sock($sock); 00821 return false; 00822 } 00823 00824 $ret[$rkey] = rtrim($ret[$rkey]); 00825 00826 if ($this->_have_zlib && $flags & MEMCACHE_COMPRESSED) 00827 $ret[$rkey] = gzuncompress($ret[$rkey]); 00828 00829 if ($flags & MEMCACHE_SERIALIZED) 00830 $ret[$rkey] = unserialize($ret[$rkey]); 00831 00832 } else 00833 { 00834 $this->_debugprint("Error parsing memcached response\n"); 00835 return 0; 00836 } 00837 } 00838 } 00839 00840 // }}} 00841 // {{{ _set() 00842 00854 function _set ($cmd, $key, $val, $exp) 00855 { 00856 if (!$this->_active) 00857 return false; 00858 00859 $sock = $this->get_sock($key); 00860 if (!is_resource($sock)) 00861 return false; 00862 00863 @$this->stats[$cmd]++; 00864 00865 $flags = 0; 00866 00867 if (!is_scalar($val)) 00868 { 00869 $val = serialize($val); 00870 $flags |= MEMCACHE_SERIALIZED; 00871 if ($this->_debug) 00872 $this->_debugprint(sprintf("client: serializing data as it is not scalar\n")); 00873 } 00874 00875 $len = strlen($val); 00876 00877 if ($this->_have_zlib && $this->_compress_enable && 00878 $this->_compress_threshold && $len >= $this->_compress_threshold) 00879 { 00880 $c_val = gzcompress($val, 9); 00881 $c_len = strlen($c_val); 00882 00883 if ($c_len < $len*(1 - COMPRESS_SAVINGS)) 00884 { 00885 if ($this->_debug) 00886 $this->_debugprint(sprintf("client: compressing data; was %d bytes is now %d bytes\n", $len, $c_len)); 00887 $val = $c_val; 00888 $len = $c_len; 00889 $flags |= MEMCACHE_COMPRESSED; 00890 } 00891 } 00892 if (!fwrite($sock, "$cmd $key $flags $exp $len\r\n$val\r\n")) 00893 return $this->_dead_sock($sock); 00894 00895 $line = trim(fgets($sock)); 00896 00897 if ($this->_debug) 00898 { 00899 if ($flags & MEMCACHE_COMPRESSED) 00900 $val = 'compressed data'; 00901 $this->_debugprint(sprintf("MemCache: %s %s => %s (%s)\n", $cmd, $key, $val, $line)); 00902 } 00903 if ($line == "STORED") 00904 return true; 00905 return false; 00906 } 00907 00908 // }}} 00909 // {{{ sock_to_host() 00910 00919 function sock_to_host ($host) 00920 { 00921 if (isset($this->_cache_sock[$host])) 00922 return $this->_cache_sock[$host]; 00923 00924 $now = time(); 00925 list ($ip, $port) = explode (":", $host); 00926 if (isset($this->_host_dead[$host]) && $this->_host_dead[$host] > $now || 00927 isset($this->_host_dead[$ip]) && $this->_host_dead[$ip] > $now) 00928 return null; 00929 00930 if (!$this->_connect_sock($sock, $host)) 00931 return $this->_dead_sock($host); 00932 00933 // Do not buffer writes 00934 stream_set_write_buffer($sock, 0); 00935 00936 $this->_cache_sock[$host] = $sock; 00937 00938 return $this->_cache_sock[$host]; 00939 } 00940 00941 function _debugprint($str){ 00942 print($str); 00943 } 00944 00945 // }}} 00946 // }}} 00947 // }}} 00948 } 00949 00950 // }}} 00951 ?>

Generated on Tue Jun 29 23:40:05 2004 for Mediawiki by doxygen 1.3.7