Memcached.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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-2009 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-2009 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_Memcached extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
  36. {
  37. /**
  38. * Default Values
  39. */
  40. const DEFAULT_HOST = '127.0.0.1';
  41. const DEFAULT_PORT = 11211;
  42. const DEFAULT_PERSISTENT = true;
  43. const DEFAULT_WEIGHT = 1;
  44. const DEFAULT_TIMEOUT = 1;
  45. const DEFAULT_RETRY_INTERVAL = 15;
  46. const DEFAULT_STATUS = true;
  47. const DEFAULT_FAILURE_CALLBACK = null;
  48. /**
  49. * Log message
  50. */
  51. const TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND = 'Zend_Cache_Backend_Memcached::clean() : tags are unsupported by the Memcached backend';
  52. const TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND = 'Zend_Cache_Backend_Memcached::save() : tags are unsupported by the Memcached backend';
  53. /**
  54. * Available options
  55. *
  56. * =====> (array) servers :
  57. * an array of memcached server ; each memcached server is described by an associative array :
  58. * 'host' => (string) : the name of the memcached server
  59. * 'port' => (int) : the port of the memcached server
  60. * 'persistent' => (bool) : use or not persistent connections to this memcached server
  61. * 'weight' => (int) : number of buckets to create for this server which in turn control its
  62. * probability of it being selected. The probability is relative to the total
  63. * weight of all servers.
  64. * 'timeout' => (int) : value in seconds which will be used for connecting to the daemon. Think twice
  65. * before changing the default value of 1 second - you can lose all the
  66. * advantages of caching if your connection is too slow.
  67. * 'retry_interval' => (int) : controls how often a failed server will be retried, the default value
  68. * is 15 seconds. Setting this parameter to -1 disables automatic retry.
  69. * 'status' => (bool) : controls if the server should be flagged as online.
  70. * 'failure_callback' => (callback) : Allows the user to specify a callback function to run upon
  71. * encountering an error. The callback is run before failover
  72. * is attempted. The function takes two parameters, the hostname
  73. * and port of the failed server.
  74. *
  75. * =====> (boolean) compression :
  76. * true if you want to use on-the-fly compression
  77. *
  78. * =====> (boolean) compatibility :
  79. * true if you use old memcache server or extension
  80. *
  81. * @var array available options
  82. */
  83. protected $_options = array(
  84. 'servers' => array(array(
  85. 'host' => self::DEFAULT_HOST,
  86. 'port' => self::DEFAULT_PORT,
  87. 'persistent' => self::DEFAULT_PERSISTENT,
  88. 'weight' => self::DEFAULT_WEIGHT,
  89. 'timeout' => self::DEFAULT_TIMEOUT,
  90. 'retry_interval' => self::DEFAULT_RETRY_INTERVAL,
  91. 'status' => self::DEFAULT_STATUS,
  92. 'failure_callback' => self::DEFAULT_FAILURE_CALLBACK
  93. )),
  94. 'compression' => false,
  95. 'compatibility' => false,
  96. );
  97. /**
  98. * Memcache object
  99. *
  100. * @var mixed memcache object
  101. */
  102. protected $_memcache = null;
  103. /**
  104. * Constructor
  105. *
  106. * @param array $options associative array of options
  107. * @throws Zend_Cache_Exception
  108. * @return void
  109. */
  110. public function __construct(array $options = array())
  111. {
  112. if (!extension_loaded('memcache')) {
  113. Zend_Cache::throwException('The memcache extension must be loaded for using this backend !');
  114. }
  115. parent::__construct($options);
  116. if (isset($this->_options['servers'])) {
  117. $value= $this->_options['servers'];
  118. if (isset($value['host'])) {
  119. // in this case, $value seems to be a simple associative array (one server only)
  120. $value = array(0 => $value); // let's transform it into a classical array of associative arrays
  121. }
  122. $this->setOption('servers', $value);
  123. }
  124. $this->_memcache = new Memcache;
  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('persistent', $server)) {
  130. $server['persistent'] = self::DEFAULT_PERSISTENT;
  131. }
  132. if (!array_key_exists('weight', $server)) {
  133. $server['weight'] = self::DEFAULT_WEIGHT;
  134. }
  135. if (!array_key_exists('timeout', $server)) {
  136. $server['timeout'] = self::DEFAULT_TIMEOUT;
  137. }
  138. if (!array_key_exists('retry_interval', $server)) {
  139. $server['retry_interval'] = self::DEFAULT_RETRY_INTERVAL;
  140. }
  141. if (!array_key_exists('status', $server)) {
  142. $server['status'] = self::DEFAULT_STATUS;
  143. }
  144. if (!array_key_exists('failure_callback', $server)) {
  145. $server['failure_callback'] = self::DEFAULT_FAILURE_CALLBACK;
  146. }
  147. if ($this->_options['compatibility']) {
  148. // No status for compatibility mode (#ZF-5887)
  149. $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'],
  150. $server['weight'], $server['timeout'],
  151. $server['retry_interval']);
  152. } else {
  153. $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'],
  154. $server['weight'], $server['timeout'],
  155. $server['retry_interval'],
  156. $server['status'], $server['failure_callback']);
  157. }
  158. }
  159. }
  160. /**
  161. * Test if a cache is available for the given id and (if yes) return it (false else)
  162. *
  163. * @param string $id Cache id
  164. * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
  165. * @return string|false cached datas
  166. */
  167. public function load($id, $doNotTestCacheValidity = false)
  168. {
  169. $tmp = $this->_memcache->get($id);
  170. if (is_array($tmp)) {
  171. return $tmp[0];
  172. }
  173. return false;
  174. }
  175. /**
  176. * Test if a cache is available or not (for the given id)
  177. *
  178. * @param string $id Cache id
  179. * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  180. */
  181. public function test($id)
  182. {
  183. $tmp = $this->_memcache->get($id);
  184. if (is_array($tmp)) {
  185. return $tmp[1];
  186. }
  187. return false;
  188. }
  189. /**
  190. * Save some string datas into a cache record
  191. *
  192. * Note : $data is always "string" (serialization is done by the
  193. * core not by the backend)
  194. *
  195. * @param string $data Datas to cache
  196. * @param string $id Cache id
  197. * @param array $tags Array of strings, the cache record will be tagged by each string entry
  198. * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
  199. * @return boolean True if no problem
  200. */
  201. public function save($data, $id, $tags = array(), $specificLifetime = false)
  202. {
  203. $lifetime = $this->getLifetime($specificLifetime);
  204. if ($this->_options['compression']) {
  205. $flag = MEMCACHE_COMPRESSED;
  206. } else {
  207. $flag = 0;
  208. }
  209. // #ZF-5702 : we try add() first becase set() seems to be slower
  210. if (!($result = $this->_memcache->add($id, array($data, time(), $lifetime), $flag, $lifetime))) {
  211. $result = $this->_memcache->set($id, array($data, time(), $lifetime), $flag, $lifetime);
  212. }
  213. if (count($tags) > 0) {
  214. $this->_log("Zend_Cache_Backend_Memcached::save() : tags are unsupported by the Memcached backend");
  215. }
  216. return $result;
  217. }
  218. /**
  219. * Remove a cache record
  220. *
  221. * @param string $id Cache id
  222. * @return boolean True if no problem
  223. */
  224. public function remove($id)
  225. {
  226. return $this->_memcache->delete($id);
  227. }
  228. /**
  229. * Clean some cache records
  230. *
  231. * Available modes are :
  232. * 'all' (default) => remove all cache entries ($tags is not used)
  233. * 'old' => unsupported
  234. * 'matchingTag' => unsupported
  235. * 'notMatchingTag' => unsupported
  236. * 'matchingAnyTag' => unsupported
  237. *
  238. * @param string $mode Clean mode
  239. * @param array $tags Array of tags
  240. * @throws Zend_Cache_Exception
  241. * @return boolean True if no problem
  242. */
  243. public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  244. {
  245. switch ($mode) {
  246. case Zend_Cache::CLEANING_MODE_ALL:
  247. return $this->_memcache->flush();
  248. break;
  249. case Zend_Cache::CLEANING_MODE_OLD:
  250. $this->_log("Zend_Cache_Backend_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached backend");
  251. break;
  252. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  253. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  254. case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
  255. $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND);
  256. break;
  257. default:
  258. Zend_Cache::throwException('Invalid mode for clean() method');
  259. break;
  260. }
  261. }
  262. /**
  263. * Return true if the automatic cleaning is available for the backend
  264. *
  265. * @return boolean
  266. */
  267. public function isAutomaticCleaningAvailable()
  268. {
  269. return false;
  270. }
  271. /**
  272. * Set the frontend directives
  273. *
  274. * @param array $directives Assoc of directives
  275. * @throws Zend_Cache_Exception
  276. * @return void
  277. */
  278. public function setDirectives($directives)
  279. {
  280. parent::setDirectives($directives);
  281. $lifetime = $this->getLifetime(false);
  282. if ($lifetime > 2592000) {
  283. // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds)
  284. $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime');
  285. }
  286. if ($lifetime === null) {
  287. // #ZF-4614 : we tranform null to zero to get the maximal lifetime
  288. parent::setDirectives(array('lifetime' => 0));
  289. }
  290. }
  291. /**
  292. * Return an array of stored cache ids
  293. *
  294. * @return array array of stored cache ids (string)
  295. */
  296. public function getIds()
  297. {
  298. $this->_log("Zend_Cache_Backend_Memcached::save() : getting the list of cache ids is unsupported by the Memcache backend");
  299. return array();
  300. }
  301. /**
  302. * Return an array of stored tags
  303. *
  304. * @return array array of stored tags (string)
  305. */
  306. public function getTags()
  307. {
  308. $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
  309. return array();
  310. }
  311. /**
  312. * Return an array of stored cache ids which match 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 matching cache ids (string)
  318. */
  319. public function getIdsMatchingTags($tags = array())
  320. {
  321. $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
  322. return array();
  323. }
  324. /**
  325. * Return an array of stored cache ids which don't match given tags
  326. *
  327. * In case of multiple tags, a logical OR is made between tags
  328. *
  329. * @param array $tags array of tags
  330. * @return array array of not matching cache ids (string)
  331. */
  332. public function getIdsNotMatchingTags($tags = array())
  333. {
  334. $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
  335. return array();
  336. }
  337. /**
  338. * Return an array of stored cache ids which match any given tags
  339. *
  340. * In case of multiple tags, a logical AND is made between tags
  341. *
  342. * @param array $tags array of tags
  343. * @return array array of any matching cache ids (string)
  344. */
  345. public function getIdsMatchingAnyTags($tags = array())
  346. {
  347. $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
  348. return array();
  349. }
  350. /**
  351. * Return the filling percentage of the backend storage
  352. *
  353. * @throws Zend_Cache_Exception
  354. * @return int integer between 0 and 100
  355. */
  356. public function getFillingPercentage()
  357. {
  358. $mems = $this->_memcache->getExtendedStats();
  359. $memSize = 0;
  360. $memUsed = 0;
  361. foreach ($mems as $key => $mem) {
  362. if ($mem === false) {
  363. Zend_Cache::throwException('can\'t get stat from ' . $key);
  364. } else {
  365. $eachSize = $mem['limit_maxbytes'];
  366. if ($eachSize == 0) {
  367. Zend_Cache::throwException('can\'t get memory size from ' . $key);
  368. }
  369. $eachUsed = $mem['bytes'];
  370. if ($eachUsed > $eachSize) {
  371. $eachUsed = $eachSize;
  372. }
  373. $memSize += $eachSize;
  374. $memUsed += $eachUsed;
  375. }
  376. }
  377. return ((int) (100. * ($memUsed / $memSize)));
  378. }
  379. /**
  380. * Return an array of metadatas for the given cache id
  381. *
  382. * The array must include these keys :
  383. * - expire : the expire timestamp
  384. * - tags : a string array of tags
  385. * - mtime : timestamp of last modification time
  386. *
  387. * @param string $id cache id
  388. * @return array array of metadatas (false if the cache id is not found)
  389. */
  390. public function getMetadatas($id)
  391. {
  392. $tmp = $this->_memcache->get($id);
  393. if (is_array($tmp)) {
  394. $data = $tmp[0];
  395. $mtime = $tmp[1];
  396. if (!isset($tmp[2])) {
  397. // because this record is only with 1.7 release
  398. // if old cache records are still there...
  399. return false;
  400. }
  401. $lifetime = $tmp[2];
  402. return array(
  403. 'expire' => $mtime + $lifetime,
  404. 'tags' => array(),
  405. 'mtime' => $mtime
  406. );
  407. }
  408. return false;
  409. }
  410. /**
  411. * Give (if possible) an extra lifetime to the given cache id
  412. *
  413. * @param string $id cache id
  414. * @param int $extraLifetime
  415. * @return boolean true if ok
  416. */
  417. public function touch($id, $extraLifetime)
  418. {
  419. if ($this->_options['compression']) {
  420. $flag = MEMCACHE_COMPRESSED;
  421. } else {
  422. $flag = 0;
  423. }
  424. $tmp = $this->_memcache->get($id);
  425. if (is_array($tmp)) {
  426. $data = $tmp[0];
  427. $mtime = $tmp[1];
  428. if (!isset($tmp[2])) {
  429. // because this record is only with 1.7 release
  430. // if old cache records are still there...
  431. return false;
  432. }
  433. $lifetime = $tmp[2];
  434. $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime;
  435. if ($newLifetime <=0) {
  436. return false;
  437. }
  438. // #ZF-5702 : we try replace() first becase set() seems to be slower
  439. if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $flag, $newLifetime))) {
  440. $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $flag, $newLifetime);
  441. }
  442. return $result;
  443. }
  444. return false;
  445. }
  446. /**
  447. * Return an associative array of capabilities (booleans) of the backend
  448. *
  449. * The array must include these keys :
  450. * - automatic_cleaning (is automating cleaning necessary)
  451. * - tags (are tags supported)
  452. * - expired_read (is it possible to read expired cache records
  453. * (for doNotTestCacheValidity option for example))
  454. * - priority does the backend deal with priority when saving
  455. * - infinite_lifetime (is infinite lifetime can work with this backend)
  456. * - get_list (is it possible to get the list of cache ids and the complete list of tags)
  457. *
  458. * @return array associative of with capabilities
  459. */
  460. public function getCapabilities()
  461. {
  462. return array(
  463. 'automatic_cleaning' => false,
  464. 'tags' => false,
  465. 'expired_read' => false,
  466. 'priority' => false,
  467. 'infinite_lifetime' => false,
  468. 'get_list' => false
  469. );
  470. }
  471. }