8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【解…決…?】php-resqueで、redisサーバ側のダウン後復帰するには?

Last updated at Posted at 2014-04-15

php-resque超便利。
こちらの記事等を元に設定して、redisが受けてくれたキューをWorkerでガンガンイベントドリブンで処理できるんだけど、一つ困った点が…。

Workerを立ち上げている最中に接続先のredisサーバが落ちれば、サーバ側が復帰しても再接続してくれません。
Workerは受け取った処理に応じた処理クラスをプラグイン的に追加して、全体の起動を司るデーモンは標準スクリプトの

./vendor/bin/resque

を実行させる仕組みなので、エラーを検知したら再接続させるということができません。
というか接続エラーを検知する事すら至難の業…(エラーログを吐かせて、別プロセスから監視とか?)

もちろん、オープンソースなので ./vendor/bin/resque に手を入れてしまうというのも手なのですが、そこまでやるのは「拳銃は最後の武器だ」的なところで…。
よくあるユースケースと思うので、なにか見落としてるかも知れず…ご存知の方、おられませんでしょうか…。

【追記18:17】
結局最後の武器を使ってしまいました。
lib/Resque/Redis.phpを変更(飲み会迫ってて時間ないのでdiffとか後で取ります)

lib/Resque/Redis.php
<?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での処理しか扱えませんが、参考になれば…。

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?