/core/Cache/RingedCache.class.php

https://github.com/suquant/soloweb-onphp · PHP · 308 lines · 207 code · 54 blank · 47 comment · 28 complexity · ff06fb5f0c304f8afb4da5278723e209 MD5 · raw file

  1. <?php
  2. /***************************************************************************
  3. * Copyright (C) 2011 by Andrew N Fediushin *
  4. * email: g.kutcurua@gmail.com, icq: 723737, jabber: soloweb@jabber.ru *
  5. * *
  6. * This program is free software; you can redistribute it and/or modify *
  7. * it under the terms of the GNU Lesser General Public License as *
  8. * published by the Free Software Foundation; either version 3 of the *
  9. * License, or (at your option) any later version. *
  10. * *
  11. ***************************************************************************/
  12. /* $Id$ */
  13. /**
  14. * A wrapper to multiple cache for workload
  15. * distribution using CachePeer children.
  16. *
  17. * @ingroup Cache
  18. **/
  19. class RingedCache extends SelectivePeer
  20. {
  21. const LEVEL_ULTRAHIGH = 0xFFFF;
  22. const LEVEL_HIGH = 0xC000;
  23. const LEVEL_NORMAL = 0x8000;
  24. const LEVEL_LOW = 0x4000;
  25. const LEVEL_VERYLOW = 0x0001;
  26. protected $peers = array();
  27. private static $ground = 'unknown peer';
  28. private static $ring = array();
  29. /**
  30. * @return RingedCache
  31. **/
  32. public static function create()
  33. {
  34. return new self;
  35. }
  36. /**
  37. * @return RingedCache
  38. **/
  39. public function addPeer(
  40. $label, CachePeer $peer, array $points
  41. )
  42. {
  43. if (isset($this->peers[$label]))
  44. throw new WrongArgumentException(
  45. 'use unique names for your peers'
  46. );
  47. if ($peer->isAlive()) {
  48. foreach ($points as $point) {
  49. if ($this->checkPointOnExists($point)) {
  50. throw new WrongArgumentException(
  51. 'use unique points (' . $point . ') for your peers'
  52. );
  53. }
  54. self::$ring[$point] = $label;
  55. }
  56. $this->peers[$label]['object'] = $peer;
  57. $this->peers[$label]['points'] = $points;
  58. $this->peers[$label]['stat'] = array();
  59. $this->alive = true;
  60. ksort(self::$ring);
  61. self::$ground = reset(self::$ring);
  62. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  63. QueueLogger::INFO('Add peer "' . $label . '"', 'cache');
  64. } else {
  65. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  66. QueueLogger::INFO('Peer "' . $label . '" is dead, dont add to cluster!', 'cache');
  67. }
  68. return $this;
  69. }
  70. /**
  71. * Снимает с кольца все точки пира
  72. *
  73. * @param string $label
  74. */
  75. private function dropFromRing($label)
  76. {
  77. // Снимаем с кольца все точки этого пира
  78. self::$ring = array_filter(
  79. self::$ring,
  80. create_function('$data', 'return ( $data != \''.$label.'\' );')
  81. );
  82. ksort(self::$ring);
  83. self::$ground = reset(self::$ring);
  84. }
  85. /**
  86. * Проверка точки на существование в уже добавленных в кольцо
  87. *
  88. * @param int $point
  89. * @return boolean
  90. */
  91. public function checkPointOnExists($point)
  92. {
  93. return isset(self::$ring[$point]);
  94. }
  95. /**
  96. * @return RingedCache
  97. **/
  98. public function dropPeer($label)
  99. {
  100. if (!isset($this->peers[$label]))
  101. throw new MissingElementException(
  102. "there is no peer with '{$label}' label"
  103. );
  104. $this->dropFromRing($label);
  105. unset($this->peer[$label]);
  106. return $this;
  107. }
  108. public function checkAlive()
  109. {
  110. $this->alive = false;
  111. foreach ($this->peers as $label => $peer)
  112. if ($peer['object']->isAlive()) {
  113. $this->alive = true;
  114. } else {
  115. unset($this->peers[$label]);
  116. $this->dropFromRing($label);
  117. }
  118. return $this->alive;
  119. }
  120. /**
  121. * low-level cache access
  122. **/
  123. public function increment($key, $value)
  124. {
  125. $label = $this->guessLabel($key);
  126. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  127. QueueLogger::INSANE('key "' . $key . '" on value "' . $value . '". Cache: "' . $label . '"', 'cache');
  128. if ($this->peers[$label]['object']->isAlive())
  129. return $this->peers[$label]['object']->increment($key, $value);
  130. else
  131. $this->checkAlive();
  132. return null;
  133. }
  134. public function decrement($key, $value)
  135. {
  136. $label = $this->guessLabel($key);
  137. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  138. QueueLogger::INSANE('key "' . $key . '" on value "' . $value . '". Cache: "' . $label . '"', 'cache');
  139. if ($this->peers[$label]['object']->isAlive())
  140. return $this->peers[$label]['object']->decrement($key, $value);
  141. else
  142. $this->checkAlive();
  143. return null;
  144. }
  145. public function get($key)
  146. {
  147. $label = $this->guessLabel($key);
  148. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  149. QueueLogger::INSANE('key "' . $key . '" from cache: "' . $label . '"', 'cache');
  150. if ($this->peers[$label]['object']->isAlive())
  151. return $this->peers[$label]['object']->get($key);
  152. else
  153. $this->checkAlive();
  154. return null;
  155. }
  156. public function getList($indexes)
  157. {
  158. $labels = array();
  159. $out = array();
  160. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  161. QueueLogger::INSANE('get list', 'cache');
  162. foreach ($indexes as $index)
  163. $labels[$this->guessLabel($index)][] = $index;
  164. foreach ($labels as $label => $indexList)
  165. if ($this->peers[$label]['object']->isAlive()) {
  166. if ($list = $this->peers[$label]['object']->getList($indexList))
  167. $out = array_merge($out, $list);
  168. } else
  169. $this->checkAlive();
  170. return $out;
  171. }
  172. public function delete($key)
  173. {
  174. $label = $this->guessLabel($key);
  175. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  176. QueueLogger::INSANE('key "' . $key . '" from cache: "' . $label . '"', 'cache');
  177. if (!$this->peers[$label]['object']->isAlive()) {
  178. $this->checkAlive();
  179. return false;
  180. }
  181. return $this->peers[$label]['object']->delete($key);
  182. }
  183. /**
  184. * @return RingedCache
  185. **/
  186. public function clean()
  187. {
  188. foreach ($this->peers as $peer)
  189. $peer['object']->clean();
  190. $this->checkAlive();
  191. return parent::clean();
  192. }
  193. public function getStats()
  194. {
  195. $stats = array();
  196. foreach ($this->peers as $label => $peer)
  197. $stats[$label] = $peer['stat'];
  198. return $stats;
  199. }
  200. public function append($key, $data)
  201. {
  202. $label = $this->guessLabel($key);
  203. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  204. QueueLogger::INSANE('key "' . $key . '" from cache: "' . $label . '"', 'cache');
  205. if ($this->peers[$label]['object']->isAlive())
  206. return $this->peers[$label]['object']->append($key, $data);
  207. else
  208. $this->checkAlive();
  209. return false;
  210. }
  211. protected function store(
  212. $action, $key, $value, $expires = Cache::EXPIRES_MINIMUM
  213. )
  214. {
  215. $label = $this->guessLabel($key);
  216. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  217. QueueLogger::INSANE('label: "' . $label . '"', 'cache');
  218. if ($this->peers[$label]['object']->isAlive())
  219. return
  220. $this->peers[$label]['object']->$action(
  221. $key,
  222. $value,
  223. $expires
  224. );
  225. else
  226. $this->checkAlive();
  227. return false;
  228. }
  229. /**
  230. * brainfuck
  231. **/
  232. protected function guessLabel($key)
  233. {
  234. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  235. QueueLogger::INSANE('key: "' . $key . '"', 'cache');
  236. $class = $this->getClassName();
  237. $keyPoint = crc32($key) >> 1;
  238. $selectedLabel = self::$ground;
  239. foreach(self::$ring as $point => $label) {
  240. if ($point > $keyPoint) {
  241. $selectedLabel = $label;
  242. break;
  243. }
  244. }
  245. if (QueueLogger::me()->onMap(QueueLogger::WATCH_CACHE))
  246. QueueLogger::INSANE('set cache "' . $selectedLabel . '"', 'cache');
  247. if (isset($this->peers[$selectedLabel]['stat'][$class]))
  248. ++$this->peers[$selectedLabel]['stat'][$class];
  249. else
  250. $this->peers[$selectedLabel]['stat'][$class] = 1;
  251. return $selectedLabel;
  252. }
  253. }
  254. ?>