php-resque超便利。
こちらの記事等を元に設定して、redisが受けてくれたキューをWorkerでガンガンイベントドリブンで処理できるんだけど、一つ困った点が…。
Workerを立ち上げている最中に接続先のredisサーバが落ちれば、サーバ側が復帰しても再接続してくれません。
Workerは受け取った処理に応じた処理クラスをプラグイン的に追加して、全体の起動を司るデーモンは標準スクリプトの
./vendor/bin/resque
を実行させる仕組みなので、エラーを検知したら再接続させるということができません。
というか接続エラーを検知する事すら至難の業…(エラーログを吐かせて、別プロセスから監視とか?)
もちろん、オープンソースなので ./vendor/bin/resque に手を入れてしまうというのも手なのですが、そこまでやるのは「拳銃は最後の武器だ」的なところで…。
よくあるユースケースと思うので、なにか見落としてるかも知れず…ご存知の方、おられませんでしょうか…。
【追記18:17】
結局最後の武器を使ってしまいました。
lib/Resque/Redis.phpを変更(飲み会迫ってて時間ないのでdiffとか後で取ります)
<?php
/**
* Wrap Credis to add namespace support and various helper methods.
*
* @package Resque/Redis
* @author Chris Boulton <chris@bigcommerce.com>
* @license http://www.opensource.org/licenses/mit-license.php
*/
class Resque_Redis
{
/**
* Redis namespace
* @var string
*/
private static $defaultNamespace = 'resque:';
private $server;
private $database;
private $_driver = null; // Add by kochizufan
/**
* @var array List of all commands in Redis that supply a key as their
* first argument. Used to prefix keys with the Resque namespace.
*/
private $keyCommands = array(
'exists',
'del',
'type',
'keys',
'expire',
'ttl',
'move',
'set',
'setex',
'get',
'getset',
'setnx',
'incr',
'incrby',
'decr',
'decrby',
'rpush',
'lpush',
'llen',
'lrange',
'ltrim',
'lindex',
'lset',
'lrem',
'lpop',
'blpop',
'rpop',
'sadd',
'srem',
'spop',
'scard',
'sismember',
'smembers',
'srandmember',
'zadd',
'zrem',
'zrange',
'zrevrange',
'zrangebyscore',
'zcard',
'zscore',
'zremrangebyscore',
'sort'
);
// sinterstore
// sunion
// sunionstore
// sdiff
// sdiffstore
// sinter
// smove
// rename
// rpoplpush
// mget
// msetnx
// mset
// renamenx
/**
* Set Redis namespace (prefix) default: resque
* @param string $namespace
*/
public static function prefix($namespace)
{
if (strpos($namespace, ':') === false) {
$namespace .= ':';
}
self::$defaultNamespace = $namespace;
}
public function __construct($server, $database = null)
{
$this->server = $server;
$this->database = $database;
//Delete by kochizufan : lazy creating driver object
}
//Add by kochizufan
//Creating driver object if it is noy initialized yet.
//If error occured (redis server is not ready), driver object keep null.
private function driver() {
if ($this->_driver == null) {
if (is_array($this->server)) {
try {
$this->_driver = new Credis_Cluster($this->server);
} catch (\Exception $e) {
$this->_driver = null;
}
}
else {
$port = null;
$password = null;
$host = $this->server;
// If not a UNIX socket path or tcp:// formatted connections string
// assume host:port combination.
if (strpos($this->server, '/') === false) {
$parts = explode(':', $this->server);
if (isset($parts[1])) {
$port = $parts[1];
}
$host = $parts[0];
}else if (strpos($this->server, 'redis://') !== false){
// Redis format is:
// redis://[user]:[password]@[host]:[port]
list($userpwd,$hostport) = explode('@', $this->server);
$userpwd = substr($userpwd, strpos($userpwd, 'redis://')+8);
list($host, $port) = explode(':', $hostport);
list($user, $password) = explode(':', $userpwd);
}
try {
$this->_driver = new Credis_Client($host, $port);
if (isset($password)){
$this->_driver->auth($password);
}
} catch (\Exception $e) {
$this->_driver = null;
}
}
if ($this->_driver !== null && $this->database !== null) {
$this->_driver->select($this->database);
}
}
return $this->_driver;
}
/**
* Magic method to handle all function requests and prefix key based
* operations with the {self::$defaultNamespace} key prefix.
*
* @param string $name The name of the method called.
* @param array $args Array of supplied arguments to the method.
* @return mixed Return value from Resident::call() based on the command.
*/
public function __call($name, $args) {
if(in_array($name, $this->keyCommands)) {
if(is_array($args[0])) {
foreach($args[0] AS $i => $v) {
$args[0][$i] = self::$defaultNamespace . $v;
}
} else {
$args[0] = self::$defaultNamespace . $args[0];
}
}
//Modified by kochizufan
try {
// if driver is not initialized yet, return false.
$driver = $this->driver();
if ($driver === null) return false;
$ret = $driver->__call($name, $args);
//if method name is "blpop" and return value is not array, driver should be re-initializing.
if ($name === 'blpop' && !is_array($ret)) {
$this->_driver = null;
return false;
}
return $ret;
}
catch(CredisException $e) {
//if exception occured, driver should be re-initializing.
$this->_driver = null;
return false;
}
}
public static function getPrefix()
{
return self::$defaultNamespace;
}
public static function removePrefix($string)
{
$prefix=self::getPrefix();
if (substr($string, 0, strlen($prefix)) == $prefix) {
$string = substr($string, strlen($prefix), strlen($string) );
}
return $string;
}
}
?>
起動時にredisサーバが落ちていたらエラーで起動しない、途中でredisサーバが落ちたらデーモンは落ちはしないけど二度と復帰しない、という状況だったので、DBドライバを遅延接続にして、エラーが出る限りはつなぎ直しにいく、エラーが出だしたらまたつなぎ直しにいく、というふうに変更しました。
とりあえず最低限の変更で対応したので、執拗的な再接続を繰り返しますし、個人的要件のためだけに作ったのでこのコードだとblpopでの処理しか扱えませんが、参考になれば…。