Memcached.php 18 KB

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