mirror of
https://github.com/idanoo/php-resque
synced 2025-07-01 13:42:22 +00:00
Initial commit
This commit is contained in:
commit
cb4205d508
37 changed files with 2808 additions and 0 deletions
22
lib/Redisent/LICENSE
Normal file
22
lib/Redisent/LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2009 Justin Poliey <jdp34@njit.edu>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
67
lib/Redisent/README.markdown
Normal file
67
lib/Redisent/README.markdown
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Redisent
|
||||
|
||||
Redisent is a simple, no-nonsense interface to the [Redis](http://code.google.com/p/redis/) key-value store for modest developers.
|
||||
Due to the way it is implemented, it is flexible and tolerant of changes to the Redis protocol.
|
||||
|
||||
## Getting to work
|
||||
|
||||
If you're at all familiar with the Redis protocol and PHP objects, you've already mastered Redisent.
|
||||
All Redisent does is map the Redis protocol to a PHP object, abstract away the nitty-gritty, and make the return values PHP compatible.
|
||||
|
||||
require 'redisent.php';
|
||||
$redis = new Redisent('localhost');
|
||||
$redis->set('awesome', 'absolutely');
|
||||
echo sprintf('Is Redisent awesome? %s.\n', $redis->get('awesome'));
|
||||
|
||||
You use the exact same command names, and the exact same argument order. **How wonderful.** How about a more complex example?
|
||||
|
||||
require 'redisent.php';
|
||||
$redis = new Redisent('localhost');
|
||||
$redis->rpush('particles', 'proton');
|
||||
$redis->rpush('particles', 'electron');
|
||||
$redis->rpush('particles', 'neutron');
|
||||
$particles = $redis->lrange('particles', 0, -1);
|
||||
$particle_count = $redis->llen('particles');
|
||||
echo "<p>The {$particle_count} particles that make up atoms are:</p>";
|
||||
echo "<ul>";
|
||||
foreach ($particles as $particle) {
|
||||
echo "<li>{$particle}</li>";
|
||||
}
|
||||
echo "</ul>";
|
||||
|
||||
Be aware that Redis error responses will be wrapped in a RedisException class and thrown, so do be sure to use proper coding techniques.
|
||||
|
||||
## Clustering your servers
|
||||
|
||||
Redisent also includes a way for developers to fully utilize the scalability of Redis with multiple servers and [consistent hashing](http://en.wikipedia.org/wiki/Consistent_hashing).
|
||||
Using the RedisentCluster class, you can use Redisent the same way, except that keys will be hashed across multiple servers.
|
||||
Here is how to set up a cluster:
|
||||
|
||||
include 'redisent_cluster.php';
|
||||
|
||||
$cluster = new RedisentCluster(array(
|
||||
array('host' => '127.0.0.1', 'port' => 6379),
|
||||
array('host' => '127.0.0.1', 'port' => 6380)
|
||||
));
|
||||
|
||||
You can then use Redisent the way you normally would, i.e., `$cluster->set('key', 'value')` or `$cluster->lrange('particles', 0, -1)`.
|
||||
But what about when you need to use commands that are server specific and do not operate on keys? You can use routing, with the `RedisentCluster::to` method.
|
||||
To use routing, you need to assign a server an alias in the constructor of the Redis cluster. Aliases are not required on all servers, just the ones you want to be able to access directly.
|
||||
|
||||
include 'redisent_cluster.php';
|
||||
|
||||
$cluster = new RedisentCluster(array(
|
||||
'alpha' => array('host' => '127.0.0.1', 'port' => 6379),
|
||||
array('host' => '127.0.0.1', 'port' => 6380)
|
||||
));
|
||||
|
||||
Now there is an alias of the server running on 127.0.0.1:6379 called **alpha**, and can be interacted with like this:
|
||||
|
||||
// get server info
|
||||
$cluster->to('alpha')->info();
|
||||
|
||||
Now you have complete programatic control over your Redis servers.
|
||||
|
||||
## About
|
||||
|
||||
© 2009 [Justin Poliey](http://justinpoliey.com)
|
137
lib/Redisent/Redisent.php
Normal file
137
lib/Redisent/Redisent.php
Normal file
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
/**
|
||||
* Redisent, a Redis interface for the modest
|
||||
* @author Justin Poliey <jdp34@njit.edu>
|
||||
* @copyright 2009 Justin Poliey <jdp34@njit.edu>
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
* @package Redisent
|
||||
*/
|
||||
|
||||
define('CRLF', sprintf('%s%s', chr(13), chr(10)));
|
||||
|
||||
/**
|
||||
* Wraps native Redis errors in friendlier PHP exceptions
|
||||
*/
|
||||
class RedisException extends Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Redisent, a Redis interface for the modest among us
|
||||
*/
|
||||
class Redisent {
|
||||
|
||||
/**
|
||||
* Socket connection to the Redis server
|
||||
* @var resource
|
||||
* @access private
|
||||
*/
|
||||
private $__sock;
|
||||
|
||||
/**
|
||||
* Redis bulk commands, they are sent in a slightly different format to the server
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
private $bulk_cmds = array(
|
||||
'SET', 'GETSET', 'SETNX', 'ECHO',
|
||||
'RPUSH', 'LPUSH', 'LSET', 'LREM',
|
||||
'SADD', 'SREM', 'SMOVE', 'SISMEMBER'
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a Redisent connection to the Redis server on host {@link $host} and port {@link $port}.
|
||||
* @param string $host The hostname of the Redis server
|
||||
* @param integer $port The port number of the Redis server
|
||||
*/
|
||||
function __construct($host, $port = 6379) {
|
||||
$this->__sock = fsockopen($host, $port, $errno, $errstr);
|
||||
if (!$this->__sock) {
|
||||
throw new Exception("{$errno} - {$errstr}");
|
||||
}
|
||||
}
|
||||
|
||||
function __destruct() {
|
||||
fclose($this->__sock);
|
||||
}
|
||||
|
||||
function __call($name, $args) {
|
||||
|
||||
/* Build the Redis protocol command */
|
||||
$name = strtoupper($name);
|
||||
if (in_array($name, $this->bulk_cmds)) {
|
||||
$value = array_pop($args);
|
||||
$command = sprintf("%s %s %d%s%s%s", $name, trim(implode(' ', $args)), strlen($value), CRLF, $value, CRLF);
|
||||
}
|
||||
else {
|
||||
$command = sprintf("%s %s%s", $name, trim(implode(' ', $args)), CRLF);
|
||||
}
|
||||
|
||||
/* Open a Redis connection and execute the command */
|
||||
fwrite($this->__sock, $command);
|
||||
|
||||
/* Parse the response based on the reply identifier */
|
||||
$reply = trim(fgets($this->__sock, 512));
|
||||
switch (substr($reply, 0, 1)) {
|
||||
/* Error reply */
|
||||
case '-':
|
||||
echo $command."\n";
|
||||
throw new RedisException(substr(trim($reply), 4));
|
||||
break;
|
||||
/* Inline reply */
|
||||
case '+':
|
||||
$response = substr(trim($reply), 1);
|
||||
break;
|
||||
/* Bulk reply */
|
||||
case '$':
|
||||
if ($reply == '$-1') {
|
||||
$response = null;
|
||||
break;
|
||||
}
|
||||
$read = 0;
|
||||
$size = substr($reply, 1);
|
||||
do {
|
||||
$block_size = ($size - $read) > 1024 ? 1024 : ($size - $read);
|
||||
$response = fread($this->__sock, $block_size);
|
||||
$read += $block_size;
|
||||
} while ($read < $size);
|
||||
fread($this->__sock, 2); /* discard crlf */
|
||||
break;
|
||||
/* Multi-bulk reply */
|
||||
case '*':
|
||||
$count = substr($reply, 1);
|
||||
if ($count == '-1') {
|
||||
return null;
|
||||
}
|
||||
$response = array();
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$bulk_head = trim(fgets($this->__sock, 512));
|
||||
$size = substr($bulk_head, 1);
|
||||
if ($size == '-1') {
|
||||
$response[] = null;
|
||||
}
|
||||
else {
|
||||
$read = 0;
|
||||
$block = "";
|
||||
do {
|
||||
$block_size = ($size - $read) > 1024 ? 1024 : ($size - $read);
|
||||
$block .= fread($this->__sock, $block_size);
|
||||
$read += $block_size;
|
||||
} while ($read < $size);
|
||||
fread($this->__sock, 2); /* discard crlf */
|
||||
$response[] = $block;
|
||||
}
|
||||
}
|
||||
break;
|
||||
/* Integer reply */
|
||||
case ':':
|
||||
$response = substr(trim($reply), 1);
|
||||
break;
|
||||
default:
|
||||
throw new RedisException("invalid server response: {$reply}");
|
||||
break;
|
||||
}
|
||||
/* Party on */
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
138
lib/Redisent/RedisentCluster.php
Normal file
138
lib/Redisent/RedisentCluster.php
Normal file
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
/**
|
||||
* Redisent, a Redis interface for the modest
|
||||
* @author Justin Poliey <jdp34@njit.edu>
|
||||
* @copyright 2009 Justin Poliey <jdp34@njit.edu>
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
* @package Redisent
|
||||
*/
|
||||
|
||||
require 'redisent.php';
|
||||
|
||||
/**
|
||||
* A generalized Redisent interface for a cluster of Redis servers
|
||||
*/
|
||||
class RedisentCluster {
|
||||
|
||||
/**
|
||||
* Collection of Redisent objects attached to Redis servers
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
private $redisents;
|
||||
|
||||
/**
|
||||
* Aliases of Redisent objects attached to Redis servers, used to route commands to specific servers
|
||||
* @see RedisentCluster::to
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
private $aliases;
|
||||
|
||||
/**
|
||||
* Hash ring of Redis server nodes
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
private $ring;
|
||||
|
||||
/**
|
||||
* Individual nodes of pointers to Redis servers on the hash ring
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
private $nodes;
|
||||
|
||||
/**
|
||||
* Number of replicas of each node to make around the hash ring
|
||||
* @var integer
|
||||
* @access private
|
||||
*/
|
||||
private $replicas = 128;
|
||||
|
||||
/**
|
||||
* The commands that are not subject to hashing
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
private $dont_hash = array(
|
||||
'RANDOMKEY', 'DBSIZE',
|
||||
'SELECT', 'MOVE', 'FLUSHDB', 'FLUSHALL',
|
||||
'SAVE', 'BGSAVE', 'LASTSAVE', 'SHUTDOWN',
|
||||
'INFO', 'MONITOR', 'SLAVEOF'
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a Redisent interface to a cluster of Redis servers
|
||||
* @param array $servers The Redis servers in the cluster. Each server should be in the format array('host' => hostname, 'port' => port)
|
||||
*/
|
||||
function __construct($servers) {
|
||||
$this->ring = array();
|
||||
$this->aliases = array();
|
||||
foreach ($servers as $alias => $server) {
|
||||
$this->redisents[] = new Redisent($server['host'], $server['port']);
|
||||
if (is_string($alias)) {
|
||||
$this->aliases[$alias] = $this->redisents[count($this->redisents)-1];
|
||||
}
|
||||
for ($replica = 1; $replica <= $this->replicas; $replica++) {
|
||||
$this->ring[crc32($server['host'].':'.$server['port'].'-'.$replica)] = $this->redisents[count($this->redisents)-1];
|
||||
}
|
||||
}
|
||||
ksort($this->ring, SORT_NUMERIC);
|
||||
$this->nodes = array_keys($this->ring);
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes a command to a specific Redis server aliased by {$alias}.
|
||||
* @param string $alias The alias of the Redis server
|
||||
* @return Redisent The Redisent object attached to the Redis server
|
||||
*/
|
||||
function to($alias) {
|
||||
if (isset($this->aliases[$alias])) {
|
||||
return $this->aliases[$alias];
|
||||
}
|
||||
else {
|
||||
throw new Exception("That Redisent alias does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
/* Execute a Redis command on the cluster */
|
||||
function __call($name, $args) {
|
||||
|
||||
/* Pick a server node to send the command to */
|
||||
$name = strtoupper($name);
|
||||
if (!in_array($name, $this->dont_hash)) {
|
||||
$node = $this->nextNode(crc32($args[0]));
|
||||
$redisent = $this->ring[$node];
|
||||
}
|
||||
else {
|
||||
$redisent = $this->redisents[0];
|
||||
}
|
||||
|
||||
/* Execute the command on the server */
|
||||
return call_user_func_array(array($redisent, $name), $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes to the proper server node
|
||||
* @param integer $needle The hash value of the Redis command
|
||||
* @return Redisent The Redisent object associated with the hash
|
||||
*/
|
||||
private function nextNode($needle) {
|
||||
$haystack = $this->nodes;
|
||||
while (count($haystack) > 2) {
|
||||
$try = floor(count($haystack) / 2);
|
||||
if ($haystack[$try] == $needle) {
|
||||
return $needle;
|
||||
}
|
||||
if ($needle < $haystack[$try]) {
|
||||
$haystack = array_slice($haystack, 0, $try + 1);
|
||||
}
|
||||
if ($needle > $haystack[$try]) {
|
||||
$haystack = array_slice($haystack, $try + 1);
|
||||
}
|
||||
}
|
||||
return $haystack[count($haystack)-1];
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue