PageRenderTime 57ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/predis/Network/MasterSlaveReplication.php

https://bitbucket.org/ashwanthkumar/blueignis-spout
PHP | 423 lines | 256 code | 50 blank | 117 comment | 28 complexity | a19090e4fd04a7e1907afaa2df316ecf MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Predis package.
  4. *
  5. * (c) Daniele Alessandri <suppakilla@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Predis\Network;
  11. use Predis\Commands\ICommand;
  12. use Predis\NotSupportedException;
  13. /**
  14. * Defines the standard virtual connection class that is used
  15. * by Predis to handle replication with a group of servers in
  16. * a master/slave configuration.
  17. *
  18. * @author Daniele Alessandri <suppakilla@gmail.com>
  19. */
  20. class MasterSlaveReplication implements IConnectionReplication
  21. {
  22. private $disallowed = array();
  23. private $readonly = array();
  24. private $readonlySHA1 = array();
  25. private $current = null;
  26. private $master = null;
  27. private $slaves = array();
  28. /**
  29. *
  30. */
  31. public function __construct()
  32. {
  33. $this->disallowed = $this->getDisallowedOperations();
  34. $this->readonly = $this->getReadOnlyOperations();
  35. }
  36. /**
  37. * Checks if one master and at least one slave have been defined.
  38. */
  39. protected function check()
  40. {
  41. if (!isset($this->master) || !$this->slaves) {
  42. throw new \RuntimeException('Replication needs a master and at least one slave.');
  43. }
  44. }
  45. /**
  46. * Resets the connection state.
  47. */
  48. protected function reset()
  49. {
  50. $this->current = null;
  51. }
  52. /**
  53. * {@inheritdoc}
  54. */
  55. public function add(IConnectionSingle $connection)
  56. {
  57. $alias = $connection->getParameters()->alias;
  58. if ($alias === 'master') {
  59. $this->master = $connection;
  60. }
  61. else {
  62. $this->slaves[$alias ?: count($this->slaves)] = $connection;
  63. }
  64. $this->reset();
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. public function remove(IConnectionSingle $connection)
  70. {
  71. if ($connection->getParameters()->alias === 'master') {
  72. $this->master = null;
  73. $this->reset();
  74. return true;
  75. }
  76. else {
  77. if (($id = array_search($connection, $this->slaves, true)) !== false) {
  78. unset($this->slaves[$id]);
  79. $this->reset();
  80. return true;
  81. }
  82. }
  83. return false;
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function getConnection(ICommand $command)
  89. {
  90. if ($this->current === null) {
  91. $this->check();
  92. $this->current = $this->isReadOperation($command) ? $this->pickSlave() : $this->master;
  93. return $this->current;
  94. }
  95. if ($this->current === $this->master) {
  96. return $this->current;
  97. }
  98. if (!$this->isReadOperation($command)) {
  99. $this->current = $this->master;
  100. }
  101. return $this->current;
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. public function getConnectionById($connectionId)
  107. {
  108. if ($connectionId === 'master') {
  109. return $this->master;
  110. }
  111. if (isset($this->slaves[$connectionId])) {
  112. return $this->slaves[$connectionId];
  113. }
  114. return null;
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. public function switchTo($connection)
  120. {
  121. $this->check();
  122. if (!$connection instanceof IConnectionSingle) {
  123. $connection = $this->getConnectionById($connection);
  124. }
  125. if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) {
  126. throw new \InvalidArgumentException('The specified connection is not valid.');
  127. }
  128. $this->current = $connection;
  129. }
  130. /**
  131. * {@inheritdoc}
  132. */
  133. public function getCurrent()
  134. {
  135. return $this->current;
  136. }
  137. /**
  138. * {@inheritdoc}
  139. */
  140. public function getMaster()
  141. {
  142. return $this->master;
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. public function getSlaves()
  148. {
  149. return array_values($this->slaves);
  150. }
  151. /**
  152. * Returns a random slave.
  153. *
  154. * @return IConnectionSingle
  155. */
  156. protected function pickSlave()
  157. {
  158. return $this->slaves[array_rand($this->slaves)];
  159. }
  160. /**
  161. * {@inheritdoc}
  162. */
  163. public function isConnected()
  164. {
  165. return $this->current ? $this->current->isConnected() : false;
  166. }
  167. /**
  168. * {@inheritdoc}
  169. */
  170. public function connect()
  171. {
  172. if ($this->current === null) {
  173. $this->check();
  174. $this->current = $this->pickSlave();
  175. }
  176. $this->current->connect();
  177. }
  178. /**
  179. * {@inheritdoc}
  180. */
  181. public function disconnect()
  182. {
  183. if ($this->master) {
  184. $this->master->disconnect();
  185. }
  186. foreach ($this->slaves as $connection) {
  187. $connection->disconnect();
  188. }
  189. }
  190. /**
  191. * {@inheritdoc}
  192. */
  193. public function writeCommand(ICommand $command)
  194. {
  195. $this->getConnection($command)->writeCommand($command);
  196. }
  197. /**
  198. * {@inheritdoc}
  199. */
  200. public function readResponse(ICommand $command)
  201. {
  202. return $this->getConnection($command)->readResponse($command);
  203. }
  204. /**
  205. * {@inheritdoc}
  206. */
  207. public function executeCommand(ICommand $command)
  208. {
  209. return $this->getConnection($command)->executeCommand($command);
  210. }
  211. /**
  212. * Returns if the specified command performs a read-only operation
  213. * against a key stored on Redis.
  214. *
  215. * @param ICommand $command Instance of Redis command.
  216. * @return Boolean
  217. */
  218. protected function isReadOperation(ICommand $command)
  219. {
  220. if (isset($this->disallowed[$id = $command->getId()])) {
  221. throw new NotSupportedException("The command $id is not allowed in replication mode");
  222. }
  223. if (isset($this->readonly[$id])) {
  224. if (true === $readonly = $this->readonly[$id]) {
  225. return true;
  226. }
  227. return call_user_func($readonly, $command);
  228. }
  229. if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
  230. $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
  231. if (isset($this->readonlySHA1[$sha1])) {
  232. if (true === $readonly = $this->readonlySHA1[$sha1]) {
  233. return true;
  234. }
  235. return call_user_func($readonly, $command);
  236. }
  237. }
  238. return false;
  239. }
  240. /**
  241. * Checks if a SORT command is a readable operation by parsing the arguments
  242. * array of the specified commad instance.
  243. *
  244. * @param ICommand $command Instance of Redis command.
  245. * @return Boolean
  246. */
  247. private function isSortReadOnly(ICommand $command)
  248. {
  249. $arguments = $command->getArguments();
  250. return ($c = count($arguments)) === 1 ? true : $arguments[$c - 2] !== 'STORE';
  251. }
  252. /**
  253. * Marks a command as a read-only operation. When the behaviour of a
  254. * command can be decided only at runtime depending on its arguments,
  255. * a callable object can be provided to dinamically check if the passed
  256. * instance of a command performs write operations or not.
  257. *
  258. * @param string $commandID ID of the command.
  259. * @param mixed $readonly A boolean or a callable object.
  260. */
  261. public function setCommandReadOnly($commandID, $readonly = true)
  262. {
  263. $commandID = strtoupper($commandID);
  264. if ($readonly) {
  265. $this->readonly[$commandID] = $readonly;
  266. }
  267. else {
  268. unset($this->readonly[$commandID]);
  269. }
  270. }
  271. /**
  272. * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
  273. * the behaviour of a script can be decided only at runtime depending on
  274. * its arguments, a callable object can be provided to dinamically check
  275. * if the passed instance of EVAL or EVALSHA performs write operations or
  276. * not.
  277. *
  278. * @param string $script Body of the Lua script.
  279. * @param mixed $readonly A boolean or a callable object.
  280. */
  281. public function setScriptReadOnly($script, $readonly = true)
  282. {
  283. $sha1 = sha1($script);
  284. if ($readonly) {
  285. $this->readonlySHA1[$sha1] = $readonly;
  286. }
  287. else {
  288. unset($this->readonlySHA1[$sha1]);
  289. }
  290. }
  291. /**
  292. * Returns the default list of disallowed commands.
  293. *
  294. * @return array
  295. */
  296. protected function getDisallowedOperations()
  297. {
  298. return array(
  299. 'SHUTDOWN' => true,
  300. 'INFO' => true,
  301. 'DBSIZE' => true,
  302. 'LASTSAVE' => true,
  303. 'CONFIG' => true,
  304. 'MONITOR' => true,
  305. 'SLAVEOF' => true,
  306. 'SAVE' => true,
  307. 'BGSAVE' => true,
  308. 'BGREWRITEAOF' => true,
  309. 'SLOWLOG' => true,
  310. );
  311. }
  312. /**
  313. * Returns the default list of commands performing read-only operations.
  314. *
  315. * @return array
  316. */
  317. protected function getReadOnlyOperations()
  318. {
  319. return array(
  320. 'EXISTS' => true,
  321. 'TYPE' => true,
  322. 'KEYS' => true,
  323. 'RANDOMKEY' => true,
  324. 'TTL' => true,
  325. 'GET' => true,
  326. 'MGET' => true,
  327. 'SUBSTR' => true,
  328. 'STRLEN' => true,
  329. 'GETRANGE' => true,
  330. 'GETBIT' => true,
  331. 'LLEN' => true,
  332. 'LRANGE' => true,
  333. 'LINDEX' => true,
  334. 'SCARD' => true,
  335. 'SISMEMBER' => true,
  336. 'SINTER' => true,
  337. 'SUNION' => true,
  338. 'SDIFF' => true,
  339. 'SMEMBERS' => true,
  340. 'SRANDMEMBER' => true,
  341. 'ZRANGE' => true,
  342. 'ZREVRANGE' => true,
  343. 'ZRANGEBYSCORE' => true,
  344. 'ZREVRANGEBYSCORE' => true,
  345. 'ZCARD' => true,
  346. 'ZSCORE' => true,
  347. 'ZCOUNT' => true,
  348. 'ZRANK' => true,
  349. 'ZREVRANK' => true,
  350. 'HGET' => true,
  351. 'HMGET' => true,
  352. 'HEXISTS' => true,
  353. 'HLEN' => true,
  354. 'HKEYS' => true,
  355. 'HVELS' => true,
  356. 'HGETALL' => true,
  357. 'PING' => true,
  358. 'AUTH' => true,
  359. 'SELECT' => true,
  360. 'ECHO' => true,
  361. 'QUIT' => true,
  362. 'OBJECT' => true,
  363. 'SORT' => array($this, 'isSortReadOnly'),
  364. );
  365. }
  366. /**
  367. * {@inheritdoc}
  368. */
  369. public function __sleep()
  370. {
  371. return array('master', 'slaves', 'disallowed', 'readonly', 'readonlySHA1');
  372. }
  373. }