Libmemcached.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Cache
  17. * @subpackage Zend_Cache_Backend
  18. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. */
  21. /**
  22. * @see Zend_Cache_Backend_Interface
  23. */
  24. require_once 'Zend/Cache/Backend/ExtendedInterface.php';
  25. /**
  26. * @see Zend_Cache_Backend
  27. */
  28. require_once 'Zend/Cache/Backend.php';
  29. /**
  30. * @package Zend_Cache
  31. * @subpackage Zend_Cache_Backend
  32. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  33. * @license http://framework.zend.com/license/new-bsd New BSD License
  34. */
  35. class Zend_Cache_Backend_Libmemcached extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
  36. {
  37. /**
  38. * Default Server Values
  39. */
  40. const DEFAULT_HOST = '127.0.0.1';
  41. const DEFAULT_PORT = 11211;
  42. const DEFAULT_WEIGHT = 1;
  43. /**
  44. * Log message
  45. */
  46. const TAGS_UNSUPPORTED_BY_CLEAN_OF_LIBMEMCACHED_BACKEND = 'Zend_Cache_Backend_Libmemcached::clean() : tags are unsupported by the Libmemcached backend';
  47. const TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND = 'Zend_Cache_Backend_Libmemcached::save() : tags are unsupported by the Libmemcached backend';
  48. /**
  49. * Available options
  50. *
  51. * =====> (array) servers :
  52. * an array of memcached server ; each memcached server is described by an associative array :
  53. * 'host' => (string) : the name of the memcached server
  54. * 'port' => (int) : the port of the memcached server
  55. * 'weight' => (int) : number of buckets to create for this server which in turn control its
  56. * probability of it being selected. The probability is relative to the total
  57. * weight of all servers.
  58. * =====> (array) client :
  59. * an array of memcached client options ; the memcached client is described by an associative array :
  60. * @see http://php.net/manual/memcached.constants.php
  61. * - The option name can be the name of the constant without the prefix 'OPT_'
  62. * or the integer value of this option constant
  63. *
  64. * @var array available options
  65. */
  66. protected $_options = array(
  67. 'servers' => array(array(
  68. 'host' => self::DEFAULT_HOST,
  69. 'port' => self::DEFAULT_PORT,
  70. 'weight' => self::DEFAULT_WEIGHT,
  71. )),
  72. 'client' => array()
  73. );
  74. /**
  75. * Memcached object
  76. *
  77. * @var mixed memcached object
  78. */
  79. protected $_memcache = null;
  80. /**
  81. * Constructor
  82. *
  83. * @param array $options associative array of options
  84. * @throws Zend_Cache_Exception
  85. * @return void
  86. */
  87. public function __construct(array $options = array())
  88. {
  89. if (!extension_loaded('memcached')) {
  90. Zend_Cache::throwException('The memcached extension must be loaded for using this backend !');
  91. }
  92. // override default client options
  93. $this->_options['client'] = array(
  94. Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
  95. Memcached::OPT_HASH => Memcached::HASH_MD5,
  96. Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
  97. );
  98. parent::__construct($options);
  99. if (isset($this->_options['servers'])) {
  100. $value = $this->_options['servers'];
  101. if (isset($value['host'])) {
  102. // in this case, $value seems to be a simple associative array (one server only)
  103. $value = array(0 => $value); // let's transform it into a classical array of associative arrays
  104. }
  105. $this->setOption('servers', $value);
  106. }
  107. $this->_memcache = new Memcached;
  108. // setup memcached client options
  109. foreach ($this->_options['client'] as $name => $value) {
  110. $optId = null;
  111. if (is_int($name)) {
  112. $optId = $name;
  113. } else {
  114. $optConst = 'Memcached::OPT_' . strtoupper($name);
  115. if (defined($optName)) {
  116. $optId = constant($optConst);
  117. }
  118. }
  119. if ($optId) {
  120. $this->_memcache->setOption($optId, $value);
  121. }
  122. }
  123. // setup memcached servers
  124. $servers = array();
  125. foreach ($this->_options['servers'] as $server) {
  126. if (!array_key_exists('port', $server)) {
  127. $server['port'] = self::DEFAULT_PORT;
  128. }
  129. if (!array_key_exists('weight', $server)) {
  130. $server['weight'] = self::DEFAULT_WEIGHT;
  131. }
  132. $servers[] = array($server['host'], $server['port'], $server['weight']);
  133. }
  134. $this->_memcache->addServers($servers);
  135. }
  136. /**
  137. * Test if a cache is available for the given id and (if yes) return it (false else)
  138. *
  139. * @param string $id Cache id
  140. * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
  141. * @return string|false cached datas
  142. */
  143. public function load($id, $doNotTestCacheValidity = false)
  144. {
  145. $tmp = $this->_memcache->get($id);
  146. if (isset($tmp[0])) {
  147. return $tmp[0];
  148. }
  149. return false;
  150. }
  151. /**
  152. * Test if a cache is available or not (for the given id)
  153. *
  154. * @param string $id Cache id
  155. * @return int|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  156. */
  157. public function test($id)
  158. {
  159. $tmp = $this->_memcache->get($id);
  160. if (isset($tmp[0], $tmp[1])) {
  161. return (int)$tmp[1];
  162. }
  163. return false;
  164. }
  165. /**
  166. * Save some string datas into a cache record
  167. *
  168. * Note : $data is always "string" (serialization is done by the
  169. * core not by the backend)
  170. *
  171. * @param string $data Datas to cache
  172. * @param string $id Cache id
  173. * @param array $tags Array of strings, the cache record will be tagged by each string entry
  174. * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
  175. * @return boolean True if no problem
  176. */
  177. public function save($data, $id, $tags = array(), $specificLifetime = false)
  178. {
  179. $lifetime = $this->getLifetime($specificLifetime);
  180. // ZF-8856: using set because add needs a second request if item already exists
  181. $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $lifetime);
  182. if ($result === false) {
  183. $rsCode = $this->_memcache->getResultCode();
  184. $rsMsg = $this->_memcache->getResultMessage();
  185. $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}");
  186. }
  187. if (count($tags) > 0) {
  188. $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
  189. }
  190. return $result;
  191. }
  192. /**
  193. * Remove a cache record
  194. *
  195. * @param string $id Cache id
  196. * @return boolean True if no problem
  197. */
  198. public function remove($id)
  199. {
  200. return $this->_memcache->delete($id);
  201. }
  202. /**
  203. * Clean some cache records
  204. *
  205. * Available modes are :
  206. * 'all' (default) => remove all cache entries ($tags is not used)
  207. * 'old' => unsupported
  208. * 'matchingTag' => unsupported
  209. * 'notMatchingTag' => unsupported
  210. * 'matchingAnyTag' => unsupported
  211. *
  212. * @param string $mode Clean mode
  213. * @param array $tags Array of tags
  214. * @throws Zend_Cache_Exception
  215. * @return boolean True if no problem
  216. */
  217. public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  218. {
  219. switch ($mode) {
  220. case Zend_Cache::CLEANING_MODE_ALL:
  221. return $this->_memcache->flush();
  222. break;
  223. case Zend_Cache::CLEANING_MODE_OLD:
  224. $this->_log("Zend_Cache_Backend_Libmemcached::clean() : CLEANING_MODE_OLD is unsupported by the Libmemcached backend");
  225. break;
  226. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  227. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  228. case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
  229. $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_LIBMEMCACHED_BACKEND);
  230. break;
  231. default:
  232. Zend_Cache::throwException('Invalid mode for clean() method');
  233. break;
  234. }
  235. }
  236. /**
  237. * Return true if the automatic cleaning is available for the backend
  238. *
  239. * @return boolean
  240. */
  241. public function isAutomaticCleaningAvailable()
  242. {
  243. return false;
  244. }
  245. /**
  246. * Set the frontend directives
  247. *
  248. * @param array $directives Assoc of directives
  249. * @throws Zend_Cache_Exception
  250. * @return void
  251. */
  252. public function setDirectives($directives)
  253. {
  254. parent::setDirectives($directives);
  255. $lifetime = $this->getLifetime(false);
  256. if ($lifetime > 2592000) {
  257. // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds)
  258. $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime');
  259. }
  260. if ($lifetime === null) {
  261. // #ZF-4614 : we tranform null to zero to get the maximal lifetime
  262. parent::setDirectives(array('lifetime' => 0));
  263. }
  264. }
  265. /**
  266. * Return an array of stored cache ids
  267. *
  268. * @return array array of stored cache ids (string)
  269. */
  270. public function getIds()
  271. {
  272. $this->_log("Zend_Cache_Backend_Libmemcached::save() : getting the list of cache ids is unsupported by the Libmemcached backend");
  273. return array();
  274. }
  275. /**
  276. * Return an array of stored tags
  277. *
  278. * @return array array of stored tags (string)
  279. */
  280. public function getTags()
  281. {
  282. $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
  283. return array();
  284. }
  285. /**
  286. * Return an array of stored cache ids which match given tags
  287. *
  288. * In case of multiple tags, a logical AND is made between tags
  289. *
  290. * @param array $tags array of tags
  291. * @return array array of matching cache ids (string)
  292. */
  293. public function getIdsMatchingTags($tags = array())
  294. {
  295. $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
  296. return array();
  297. }
  298. /**
  299. * Return an array of stored cache ids which don't match given tags
  300. *
  301. * In case of multiple tags, a logical OR is made between tags
  302. *
  303. * @param array $tags array of tags
  304. * @return array array of not matching cache ids (string)
  305. */
  306. public function getIdsNotMatchingTags($tags = array())
  307. {
  308. $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
  309. return array();
  310. }
  311. /**
  312. * Return an array of stored cache ids which match any given tags
  313. *
  314. * In case of multiple tags, a logical AND is made between tags
  315. *
  316. * @param array $tags array of tags
  317. * @return array array of any matching cache ids (string)
  318. */
  319. public function getIdsMatchingAnyTags($tags = array())
  320. {
  321. $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
  322. return array();
  323. }
  324. /**
  325. * Return the filling percentage of the backend storage
  326. *
  327. * @throws Zend_Cache_Exception
  328. * @return int integer between 0 and 100
  329. */
  330. public function getFillingPercentage()
  331. {
  332. $mems = $this->_memcache->getStats();
  333. if ($mems === false) {
  334. return 0;
  335. }
  336. $memSize = null;
  337. $memUsed = null;
  338. foreach ($mems as $key => $mem) {
  339. if ($mem === false) {
  340. $this->_log('can\'t get stat from ' . $key);
  341. continue;
  342. }
  343. $eachSize = $mem['limit_maxbytes'];
  344. $eachUsed = $mem['bytes'];
  345. if ($eachUsed > $eachSize) {
  346. $eachUsed = $eachSize;
  347. }
  348. $memSize += $eachSize;
  349. $memUsed += $eachUsed;
  350. }
  351. if ($memSize === null || $memUsed === null) {
  352. Zend_Cache::throwException('Can\'t get filling percentage');
  353. }
  354. return ((int) (100. * ($memUsed / $memSize)));
  355. }
  356. /**
  357. * Return an array of metadatas for the given cache id
  358. *
  359. * The array must include these keys :
  360. * - expire : the expire timestamp
  361. * - tags : a string array of tags
  362. * - mtime : timestamp of last modification time
  363. *
  364. * @param string $id cache id
  365. * @return array array of metadatas (false if the cache id is not found)
  366. */
  367. public function getMetadatas($id)
  368. {
  369. $tmp = $this->_memcache->get($id);
  370. if (isset($tmp[0], $tmp[1], $tmp[2])) {
  371. $data = $tmp[0];
  372. $mtime = $tmp[1];
  373. $lifetime = $tmp[2];
  374. return array(
  375. 'expire' => $mtime + $lifetime,
  376. 'tags' => array(),
  377. 'mtime' => $mtime
  378. );
  379. }
  380. return false;
  381. }
  382. /**
  383. * Give (if possible) an extra lifetime to the given cache id
  384. *
  385. * @param string $id cache id
  386. * @param int $extraLifetime
  387. * @return boolean true if ok
  388. */
  389. public function touch($id, $extraLifetime)
  390. {
  391. $tmp = $this->_memcache->get($id);
  392. if (isset($tmp[0], $tmp[1], $tmp[2])) {
  393. $data = $tmp[0];
  394. $mtime = $tmp[1];
  395. $lifetime = $tmp[2];
  396. $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime;
  397. if ($newLifetime <=0) {
  398. return false;
  399. }
  400. // #ZF-5702 : we try replace() first becase set() seems to be slower
  401. if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $newLifetime))) {
  402. $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $newLifetime);
  403. if ($result === false) {
  404. $rsCode = $this->_memcache->getResultCode();
  405. $rsMsg = $this->_memcache->getResultMessage();
  406. $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}");
  407. }
  408. }
  409. return $result;
  410. }
  411. return false;
  412. }
  413. /**
  414. * Return an associative array of capabilities (booleans) of the backend
  415. *
  416. * The array must include these keys :
  417. * - automatic_cleaning (is automating cleaning necessary)
  418. * - tags (are tags supported)
  419. * - expired_read (is it possible to read expired cache records
  420. * (for doNotTestCacheValidity option for example))
  421. * - priority does the backend deal with priority when saving
  422. * - infinite_lifetime (is infinite lifetime can work with this backend)
  423. * - get_list (is it possible to get the list of cache ids and the complete list of tags)
  424. *
  425. * @return array associative of with capabilities
  426. */
  427. public function getCapabilities()
  428. {
  429. return array(
  430. 'automatic_cleaning' => false,
  431. 'tags' => false,
  432. 'expired_read' => false,
  433. 'priority' => false,
  434. 'infinite_lifetime' => false,
  435. 'get_list' => false
  436. );
  437. }
  438. }