PHPRedisにおけるLua Scriptingの書き方

PHPの有名なRedisライブラリPHPRedisのLuaの書き方をいつも忘れるので書き残しておきます。Luaを使えばアトミックになるのでそれなりに使う機会はあると思います。

コードを下記に置いておきました、一目瞭然かと思いますが、説明しますと、evalの引数は必須がひとつ、オプションが2つあります。必須はLuaスクリプト、オプションのひとつはarray、オプションのもう一つはarrayの要素数です。本来のRedisではKEYSだけでなくARGVも使えますが、どうやらPHPRedisだとKEYSだけ使用可能なようです (Redis Clusterを使わない限りは、RedisのLuaにおけるKEYSとARGVに違いはありません、詳しくはこの記事の前の記事を参照してください)。

コード

<?php

$redis = new Redis();
$redis->connect('localhost', 6379, 1.0);

$script1 = <<<EOT
	return {KEYS[1], KEYS[2], KEYS[3]}
EOT;

$keys = Array('key1', 'key2', 'key3');
$result1 = $redis->eval($script1, $keys, count($keys));
var_dump($result1);

$script2 = <<<EOT
	return KEYS
EOT;

$result2 = $redis->eval($script2, $keys, count($keys));
var_dump($result2);

結果
array(3) {
  [0]=>
  string(4) "key1"
  [1]=>
  string(4) "key2"
  [2]=>
  string(4) "key3"
}
array(3) {
  [0]=>
  string(4) "key1"
  [1]=>
  string(4) "key2"
  [2]=>
  string(4) "key3"
}

おまけ

内部でRedisを呼ぶには次のようにします。
<?php

$redis = new Redis();
$redis_status = $redis->connect(REDIS_HOST, REDIS_PORT, REDIS_TIMEOUT);

if ($redis_status === FALSE) {
	die("Internal Server Error.\n");
}

$luascript = <<<EOT
	local key1 = KEYS[1]
	local key2 = KEYS[2]
	local key3 = KEYS[3]
	local key1_exists = redis.call('EXISTS', key1)
	local key2_exists = redis.call('EXISTS', key2)
	return {key1_exists, key2_exists}
EOT;

$keys = array('value1', 'value2', 'value3');

$result = $redis->eval($luascript, $keys, count($keys));

var_dump($result);

出力例

array(2) {
  [0]=>
  int(1)
  [1]=>
  int(0)
}

HMSETについては、以下のようにやればできます。

<?php
$redis = new Redis();
$redis_status = $redis->connect(REDIS_HOST, REDIS_PORT, REDIS_TIMEOUT);

if ($redis_status === FALSE) {
	die("Internal Server Error.\n");
}

$luascript = <<<EOT
	local key1 = KEYS[1]
	local key2 = KEYS[2]
	local key3 = KEYS[3]
	redis.call('hmset', 'hmset1', 'f1', key1, 'f2', key2, 'f3', key3)
	
	return redis.call('hgetall', 'hmset1')
EOT;

$keys = array('one', 'two', 'three');

$result = $redis->eval($luascript, $keys, count($keys));

var_dump($result);

出力例

array(6) {
  [0]=>
  string(2) "f1"
  [1]=>
  string(3) "one"
  [2]=>
  string(2) "f2"
  [3]=>
  string(3) "two"
  [4]=>
  string(2) "f3"
  [5]=>
  string(5) "three"
}

RedisのLua ScriptingのKEYSとARGVの違いについて調査

結論から言いますと、通常の使い方の場合は違いを意識する必要はないようです。KEYSとARGVの2つを使う必要があるのはRedis Clusterなどのときのようです。

RedisのLua機能で、引数を使えるのですが、KEYSとARGVという2つの変数を渡すことが出来ます。それはそれでいいんですが、KEYSとARGVの違いが分かりません。使い分けなどあるのでしょうか。公式のドキュメントを読みますと↓

The first argument of EVAL is a Lua 5.1 script. The script does not need to define a Lua function (and should not). It is just a Lua program that will run in the context of the Redis server.

The second argument of EVAL is the number of arguments that follows the script (starting from the third argument) that represent Redis key names. This arguments can be accessed by Lua using the KEYS global variable in the form of a one-based array (so KEYS[1], KEYS[2], …).

All the additional arguments should not represent key names and can be accessed by Lua using the ARGV global variable, very similarly to what happens with keys (so ARGV[1], ARGV[2], …).

http://redis.io/commands/eval


こんな感じです。うーん、違いが分かりませんね。。。いろいろと探してみると、RedisのGoogle Groupにありました。

Any differences between “KEYS” and “ARGV” in scripting?

Yes, it is for Redis Cluster, and for sharding in general. If you will
never use Redis Cluster, or if you never plan on sharding your data,
you can effectively treat them the same.


なるほど、Redis Clusterやシャーディングのときに使うみたいですね。とは言えなぜそのときに2つの変数が必要になるのか私にはいまいち分かりませんが。。。

PHPとRedisでロック

追記@2014-11-05

自分の要件を考えてみると、わざわざこんなことしなくても素直にMySQLを使えばよいことに気が付きました><


DB鯖がたくさん

それらに一貫性のある書き込みをしたい

分散一意IDの発行みたいなのは出来ない(emailとかusernameみたいな一意なやつを書きたい)

Redisでロック・アンロックして、一意性のある書き込みの場合はたくさんのDB鯖に書き込めるプロセスを一個にしよう

という動機でRedisを使ったlock/unlockメソッドを書いてみました。詳しくはソース内↓に書いてます。けっこう苦労しました(ヽ’ω`)。
ロック周りは大変ですね。これが正解かは分かりません(というかCAP定理より全部を満たすことは出来ないですね)。おそらくロック用Redisサーバーを専用に用意する必要があるかと思います。というのも、これはロックを得るための待ち行列ができるからです。Redisで「 $ ./src/redis-cli -p 26379 monitor」としてみると、アクセスがたくさんあるのが分かるかと思います。

<?php
/**
 * PHPとRedisを用いてロックを実現するメソッドと使用例。
 * 
 * 分散された複数のデータベースサーバーについて、一貫性のある書き込みやupdateを行うために、Redisをロックサーバーとして用いる。
 * lockとunlockメソッドがある。
 * 使用する際は、lockメソッドでロックしてから、書き込みやアップデートして、後始末としてアンロックする。
 * 具体的な使い方は下記にある。
 * 
 * 留意点:
 * - DB_LOCK_TIMEOUT_FOR_DEADLOCK_SEC > (処理したい内容に掛かる時間) とせよ
 * → デッドロック回避のためにタイムアウトが指定されている。
 * → つまりロック後に、指定した時間以上に時間が掛かる処理だとロックが自動的に解除されて、
 * → 他のプロセスやスレッドがロックしてアクセスしてきて、一貫性が乱れる可能性がある。
 * 
 * - lockメソッドでロックを取得できない場合がある
 * → あまりにも多くのプロセスがロックを得ようとして、いつまでも待たされて、
 * → DB_LOCK_TIMEOUT_TOO_BUSY_SEC で設定した時間を超えるとFALSEが返ることがある。そのため、
 * → DB_LOCK_TIMEOUT_TOO_BUSY_SEC > (処理したい内容に掛かる時間) * 想定されるプロセス数
 * → となるようにせよ。
 * → 今回の場合は、1sec掛かる処理を行っており、DB_LOCK_TIMEOUT_TOO_BUSY_SECは10である。
 * → つまり、並列プロセス数10程度でロック取得失敗が起きる可能性がある。実際このプログラムを12並列で動かすと、
 * → 「*** Too busy. can not get a lock. ***」が起きる。
 * 
 * - DB_LOCK_WAIT_MSを小さい値にしすぎるとRedisへの負担が掛かる
 * → プロセスがロックがあるかどうかとDB_LOCK_WAIT_MSの周期でRedisに尋ねるので、Redisの負担になる。
 * → 大きすぎず小さすぎずの値にしておくべきだと思う。0.1secつまり100msあたりが妥当かと。
 * → 定量的に考えてみる、一般的なマシンでRedisは10万req/sec程度さばくことが出来る。
 * → 1プロセスあたりの1秒間のrequestは1000/DB_LOCK_WAIT_MS。たとえばDB_LOCK_WAIT_MS=100なら10req/sec
 * → 10並列のプロセスで100req/sec、100並列で1000req/secとなる。
 * → 一般論として同時コネクション数は100程度にしておくことが無難であり、Redisへのsetnxのアクセスは限界の10%つまり1万req/sec程度に
 * → しておけば大きな負荷にはならないだろう。ということは、1プロセスあたり一秒間に100回問い合わせして良いということなので、
 * → DB_LOCK_WAIT_MSの最小値は10と考えられる。これ以下にすると危険であろう。
 * → 特にレイテンシが求められるわけでもないのであれば、結局最初に書いた通り100に設定するのが妥当。
 * 
 * 疑問点:
 * ロックのためのkeyとvalueを書き込んでいるが、valueが必要かどうか良く分からない。
 * さらにunlockではvalueの値を確認して削除しているが、必要なのかどうか。
 * valueは必要ない気がする。消すか悩む。
 * → 消してみたら何かおかしくなった。きちんとしていない。ので、valueは残しておく。
 * 
 * 蛇足:
 * - $ ./src/redis-cli -p 26379 monitor
 * とすることで、Redisへの命令とその時間を眺めることが出来る。
 * 
 * 参考資料:
 * - http://ameblo.jp/principia-ca/entry-11770810115.html
 * - https://engineering.gosquared.com/distributed-locks-using-redis
 */

// ==================== 定数とメソッド ====================
define('DB_LOCK_KEY_PREFIX', 'lock:');
define('DB_LOCK_VALUE_PREFIX', 'value:');
define('DB_LOCK_TIMEOUT_FOR_DEADLOCK_SEC', 3); // means 3 sec。処理したい内容に掛かる処理時間よりも2倍以上大きな値にせよ!
define('DB_LOCK_TIMEOUT_TOO_BUSY_SEC', 10); // means 10 sec。想定されるプロセス数*(処理したい内容に掛かる処理時間)よりも大きな値にせよ!
define('DB_LOCK_WAIT_MS', 100); // means 100 ms。10ms程度が限界だと思われる。
function lock(Redis $redis, $lock_key, $lock_value) {
	$now = time();
	while (TRUE) {
		$res = $redis->setnx(DB_LOCK_KEY_PREFIX . $lock_key, DB_LOCK_VALUE_PREFIX . $lock_value);
		if ($res === TRUE) {
			// デッドロック回避のためタイムアウトを指定
			$redis->setTimeout(DB_LOCK_KEY_PREFIX . $lock_key, DB_LOCK_TIMEOUT_FOR_DEADLOCK_SEC);
			return TRUE;
		}

		// 競合が多すぎて指定された時間(= DB_LOCK_TIMEOUT_TOO_BUSY_SEC)内に取れない場合、FALSE
		if (time() > $now + DB_LOCK_TIMEOUT_TOO_BUSY_SEC) {
			return FALSE;
		}

		// 待機
		usleep(DB_LOCK_WAIT_MS * 1000);
	}
}
function unlock(Redis $redis, $lock_key, $lock_value) {
	$res = $redis->get(DB_LOCK_KEY_PREFIX . $lock_key);
	if ($res === DB_LOCK_VALUE_PREFIX . $lock_value) {
		$redis->delete(DB_LOCK_KEY_PREFIX . $lock_key);
		return TRUE;
	} else {
		return FALSE;
	}
}

// ==================== 以下、活用例 ====================

define('REDIS_HOST', '127.0.0.1');
define('REDIS_PORT', 26379);
define('REDIS_TIMEOUT_SEC', 2.0); // means 2.0 sec

$redis = new Redis();
$redis->connect(REDIS_HOST, REDIS_PORT, REDIS_TIMEOUT_SEC);

echo "========================================\n";
echo "Going to get a lock...\n";
if (lock($redis, 'key1', 'value1') === TRUE){
	echo "Lock ok.\n";
	echo "Going to do a task...\n";
	sleep(1); // task = 1 secs sleep!
	echo "Task done. Going to unlock.\n";
	if (unlock($redis, 'key1', 'value1') === TRUE){
		echo "Unlock ok.\n";
		echo "Yay! Everything done well!\n";
		echo "========================================\n";
	} else {
		echo "*** Unlock failed. ***\n";
		exit();
	}
} else {
	echo("*** Too busy. can not get a lock. ***\n");
	exit();
}

RedisのZSETのサイズの圧縮効率

RedisのZSETでは圧縮機能があります。デフォルトだと128個以内で1要素64Byte以下ならば圧縮されます(正確に言えば圧縮とは言えず、データ構造が異なる、と言うべきでしょうか)。
該当項目はredis.confのこの部分↓です。

redis.conf

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

* 検証方法
– zsetの要素数128個のkeyを1万個作る
– redis.confでzset-max-ziplist-entriesを128(圧縮される)と127(圧縮されない)を設定して、Redisを立ち上げる
– 検証用コード(後述)を使って、メモリ使用量をそれぞれ測定して比較

* 結果
KEYの数 要素数 zset-max-ziplist-entries サイズ(MB)
10000 128 128 20.268
10000 128 127 140.759

* 考察
128を閾値にすると20.268MB、127を閾値にすると140.759MBと、7倍もの違いがありました。もちろんkeyの長さや要素の長さによって変わってくるでしょうが、これは大きいですね。

* 環境
– Mac OSX 10.8.5
– Redis 2.9.11 (8f52173b/0) 64 bit
– gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)

* ふろく

ちなみに圧縮しているかどうかは、redis-cliで、
127.0.0.1:6379> object encoding u1
"ziplist"
として確かめることが出来ます。ziplist->圧縮されている、skiplist->無圧縮

ちなみに、検証用コード(後述)で、
>$element = gmp_strval(gmp_init($i + $j + ELEMENTS_OFFSET, 10), 62);
として、base62にしてみても、サイズは変わりありませんでした。Redisのコードを読んでいないのですが、INTが入ってくると型を認識してINTとして格納しているのかもしれません。

検証用コード
<?php

define('NUM_ELEMENTS', 128);
define('NUM_KEYS', 10000);
define('ELEMENTS_OFFSET', (int) (4294967295 / 100));

$redis = new Redis();
$redis->connect('localhost', 6379, 1.0);
$redis->flushAll();

$before = getRedisMemory($redis);

for ($i = 0; $i < NUM_KEYS; $i++) {
	$pipe = $redis->multi(Redis::PIPELINE);
	for ($j = 1; $j <= NUM_ELEMENTS; $j++) {
		$element = $i + $j + ELEMENTS_OFFSET;
		//$element = gmp_strval(gmp_init($i + $j + ELEMENTS_OFFSET, 10), 62);
		$pipe->zAdd('u' . $i, time() + $i + $j, $element);
	}
	$pipe->exec();
}
$after = getRedisMemory($redis);

printf("%d : # of elements\n", NUM_ELEMENTS);
printf("%d : # of keys\n", NUM_KEYS);
printf("%.3f [MB]: before\n", $before / 1024 / 1024);
printf("%.3f [MB]: after\n", $after / 1024 / 1024);
printf("%.3f [MB]: diff\n", $after / 1024 / 1024 - $before / 1024 / 1024);

function getRedisMemory(Redis $redis) {
	$res = $redis->info('MEMORY');
	return $res['used_memory'];
}


Redis 2.8系で Redis Sentinel を試してみた

前回の記事では、serf を試してみました。serf は、

– ノードのリストを提示
– 障害検知
– イベントフック

をしてくれるものでした。しかしながらノード単位の検出です。もう少し細かい粒度で、つまりプロセス単位でやりたいですね。。。

私はよく Redis を使います。そこで今回は、Redis プロセスを監視し、フェイルオーバーしてくれる Redis Sentinel を使ってみました。イベントフックも出来るようですが試していません、そのうちやります。

今回試したことは、
– フェイルオーバー
のみです。

Redis の2.6系と2.8系では、項目名など異なるようなので注意してください。
公式ドキュメントとして、Redis Sentinel Documentation を参考にしました。

今回は一台のマシンで試してみました。
本実験で一台のマシンに存在するプロセスは、

– Redis のマスター (1個。127.0.0.1:6379)
– Redis のスレイブ (2個。127.0.0.1:16379, 127.0.0.1:16380)
– Redis Sentinel (3個。127.0.0.1:26379, 127.0.0.1:26380, 127.0.0.1:26381)

です。合計6個です。

まず、マスター*1とスレイブ*2を立ち上げます。ターミナルのタブを大量に使います^^;

# マスター
# redis-2.8.x (x は人それぞれ)のディレクトリに移動して
./src/redis-server

# (新しくターミナルのタブを開いて) スレイブその1
./src/redis-server --port 16379 --slaveof 127.0.0.1 6379

# (新しくターミナルのタブを開いて) スレイブその2
./src/redis-server --port 16380 --slaveof 127.0.0.1 6379

実際に動作するか確認してみます。

# マスターに対して、値をセットしてみます。
$ ./src/redis-cli
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> get key1
"value1"

# スレイブその1から、値を見てみます (↑の値、value1 が出てくればOKです)
$ ./src/redis-cli -p 16379
127.0.0.1:16379> get key1
"value1"

# スレイブその2
$ ./src/redis-cli -p 16380
127.0.0.1:16380> get key1
"value1"

次に、Sentinel を3個立ち上げます。なぜ1つではなく複数の Sentinel を立ち上げるかといえば、投票を行い閾値を超えた充足があるかどうか判定するとのことです。

まず、コンフィグファイルです。3つ必要です。以下のようなファイルを作ります。

sentinel01.conf
# port <sentinel-port>
port 26379
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 5000
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 900000
# sentinel parallel-syncs <master-name> <numslaves>
sentinel config-epoch mymaster 2

sentinel02.conf
# port <sentinel-port>
port 26380
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 5000
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 900000
# sentinel parallel-syncs <master-name> <numslaves>
sentinel config-epoch mymaster 2

sentinel03.conf
# port <sentinel-port>
port 26381
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 5000
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 900000
# sentinel parallel-syncs <master-name> <numslaves>
sentinel config-epoch mymaster 2

># sentinel monitor
>sentinel monitor mymaster 127.0.0.1 6379 2

↑この が閾値となります。この場合ですと sentinel プロセスの2個以上が判断を下したら、そのとおりに行うという意味になります。今回は3つの sentinel プロセスを使いますので、多数決として、quorum には2を選びました。sentinel プロセスが5個なら、quorum は3が妥当なところかなと思います。

なお、このconfファイルは、運用していてファイルオーバーなどが起きると書き換えが起こります。具体例を挙げますと、フェイルオーバーして slave がマスターに昇格すると、.confファイルの監視対象のマスターのアドレスとポートが書き換えられます。一旦sentinelを落としても次回起動時は、素直に.confファイルを読むだけで問題なく動作するというわけです。手動書き換えが必要ないということです。

さて、↑に挙げた3つのコンフィグファイルを使って、sentinel プロセスを立ち上げます。

# sentinel その1の立ち上げ
$ ./src/redis-sentinel sentinel01.conf

# 別のターミナルのタブで、sentinel その2の立ち上げ
$ ./src/redis-sentinel sentinel02.conf

# 別のターミナルのタブで、sentinel その3の立ち上げ
$ ./src/redis-sentinel sentinel03.conf

↑で、その3を立ち上げた時には以下のようになります。

$ ./src/redis-sentinel sentinel03.conf 
[4288] 02 Jan 02:47:23.210 * Max number of open files set to 10032
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 2.8.2 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26381
 |    `-._   `._    /     _.-'    |     PID: 4288
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

[4288] 02 Jan 02:47:23.211 # Sentinel runid is a8b457ca95c446dccb9a35fbc5c3befaed7899c1
[4288] 02 Jan 02:47:23.212 * +slave slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:47:23.212 * +slave slave 127.0.0.1:16380 127.0.0.1 16380 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:47:23.490 * +sentinel sentinel 127.0.0.1:26380 127.0.0.1 26380 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:47:24.716 * +sentinel sentinel 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 6379

↑slave一覧と、sentinel一覧が表示されていますね。どうやらRedisのpubsub機能を用いて、リスト管理しているようです。賢いですね。

さて、ひとまず
– Redis のマスター * 1
– Redis のスレイブ * 2
– マスターを監視する Sentinel * 3
を立ち上げました。

次に、マスターを落としてみて、slave のどちらかが昇格するかどうか見てみます。

# マスターのプロセスを探します
$ ps aux | grep redis-server
root      3793  0.0  0.0  37944  2344 pts/0    Sl+  02:29   0:00 ./src/redis-server *:6379
root      3895  0.0  0.0  37944  2372 pts/1    Sl+  02:30   0:00 ./src/redis-server *:16379                              
root      3899  0.0  0.0  37944  2372 pts/2    Sl+  02:30   0:00 ./src/redis-server *:16380                              
root      4454  0.0  0.0   7828   880 pts/9    S+   02:54   0:00 grep redis-server

# ↑より、どうやら 3793 のプロセスIDのようなので、それを kill します。
$ kill -9 3793

↑が行われたとき、sentinel プロセスがどういうログを出力しているか見てみます。

[4288] 02 Jan 02:47:23.211 # Sentinel runid is a8b457ca95c446dccb9a35fbc5c3befaed7899c1
[4288] 02 Jan 02:47:23.212 * +slave slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:47:23.212 * +slave slave 127.0.0.1:16380 127.0.0.1 16380 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:47:23.490 * +sentinel sentinel 127.0.0.1:26380 127.0.0.1 26380 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:47:24.716 * +sentinel sentinel 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:24.790 # +sdown master mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:24.848 # +odown master mymaster 127.0.0.1 6379 #quorum 2/2
[4288] 02 Jan 02:54:24.848 # +new-epoch 3
[4288] 02 Jan 02:54:24.848 # +try-failover master mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:24.848 # +vote-for-leader a8b457ca95c446dccb9a35fbc5c3befaed7899c1 3
[4288] 02 Jan 02:54:24.849 # 127.0.0.1:26379 voted for a8b457ca95c446dccb9a35fbc5c3befaed7899c1 3
[4288] 02 Jan 02:54:24.849 # 127.0.0.1:26380 voted for a8b457ca95c446dccb9a35fbc5c3befaed7899c1 3
[4288] 02 Jan 02:54:24.948 # +elected-leader master mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:24.948 # +failover-state-select-slave master mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:25.006 # +selected-slave slave 127.0.0.1:16380 127.0.0.1 16380 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:25.006 * +failover-state-send-slaveof-noone slave 127.0.0.1:16380 127.0.0.1 16380 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:25.062 * +failover-state-wait-promotion slave 127.0.0.1:16380 127.0.0.1 16380 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:25.815 # +promoted-slave slave 127.0.0.1:16380 127.0.0.1 16380 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:25.815 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:25.872 * +slave-reconf-sent slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:26.817 * +slave-reconf-inprog slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:27.842 * +slave-reconf-done slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:27.893 # +failover-end master mymaster 127.0.0.1 6379
[4288] 02 Jan 02:54:27.893 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 16380
[4288] 02 Jan 02:54:27.893 * +slave slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 16380
[4288] 02 Jan 02:54:27.900 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 16380
[4288] 02 Jan 02:54:32.963 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 16380

↑では、
>[4288] 02 Jan 02:54:24.790 # +sdown master mymaster 127.0.0.1 6379
で127.0.0.1:6379のプロセスが落ちている、と判断しています。sdown は subjectively downですね。次に、
>[4288] 02 Jan 02:54:24.848 # +odown master mymaster 127.0.0.1 6379 #quorum 2/2
となっています。odown、つまり objectively down となります。quorum を満たしたことが分かります(他の2つのsentinelプロセスも、sdown 判定したということ)。
そして、リーダーを選出したりスレイブの書き換えが行われています。
>sentinel down-after-milliseconds mymaster 5000
と設定したとおり、約5秒で切り替わっていますね。

>[4288] 02 Jan 02:54:25.815 # +promoted-slave slave 127.0.0.1:16380 127.0.0.1 16380 @ mymaster 127.0.0.1 6379
とありますが、2つのスレイブのうち、127.0.0.1:16380 の方がマスターに昇格したようです。
実際に見てみます↓

$ ./src/redis-cli -p 16380
127.0.0.1:16380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=16379,state=online,offset=69658,lag=0
master_repl_offset:69658
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:69657
127.0.0.1:16380> 

確かに、”role:master”となっていますね。さらに、slaveも再設定されています。いたせりつくせりですね、さすが Redis という感じがします。

ちなみに、自動的に書き換わった sentinel のコンフィグファイルを見てます。

$ cat sentinel01.conf 
# port <sentinel-port>
port 26379
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 16380 2
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 5000
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 900000
# sentinel parallel-syncs <master-name> <numslaves>
sentinel config-epoch mymaster 3
# Generated by CONFIG REWRITE
dir "/root/my_repos/software/redis-2.8.2"
sentinel known-slave mymaster 127.0.0.1 16379
sentinel known-slave mymaster 127.0.0.1 6379
sentinel known-sentinel mymaster 127.0.0.1 26380 0a6f3fbd3ed7f5323918ef4f1cadc3a798b68bfa

sentinel known-sentinel mymaster 127.0.0.1 26381 a8b457ca95c446dccb9a35fbc5c3befaed7899c1

↑実際に書き換わっていますね。

今回は以上になります。イベントフックや、アプリケーション・サーバーがどのようにして昇格したマスターにアクセスするか(/etc/hosts を書き換えるとか、iptables だとか、VIP だとかいろいろとありますね)についてはいつか検証して見る予定です。