MySQL で slave を複数台立てて参照分散するには、HAProxy を利用してロードバランスと切り離しを行うと手軽に使えて便利です。
option mysql-check という設定で、HAProxy 自身が mysqld に接続してヘルスチェックが可能です。
listen mysql-slave bind 127.0.0.1:3307 mode tcp option mysql-check user haproxy balance roundrobin server slave1 192.168.1.11 check server slave2 192.168.1.12 check server slave3 192.168.1.13 check
なのですが、この設定だと以下のように少々不便なことがあります。
- mysqldに接続できるかどうかのみを死活の判断にしているので、レプリケーションが停止しているような場合にも構わず振り分けてしまう
- 結果、ユーザに古い情報が見え続けてしまう事故が起きうる
- 作業をするので一時的に特定のホストだけ外したい、という場合に切り離し作業が面倒
ということで、Having HAProxy check mysql status through a xinetd script | Sysbible を参考にして、HTTP経由での死活監視をしてみました。
HTTPリクエストを受けて、localhost の mysqld に接続して 200 / 500 を返すような daemon を実装します。
- 接続できなければ Status 500
- SHOW SLAVE STATUS の結果が得られなければ Status 500
- SHOW SLAVE STATUS の結果を Status 200、JSON で返す
という挙動をします。
#!/usr/bin/env perl use strict; use Plack::Runner; use DBI; use Try::Tiny; use JSON; sub response { my ($code, $message) = @_; my $content = encode_json({ message => $message }); my $header = [ "Content-Type" => "application/json", "Content-Length" => length($content), ]; return [ $code, $header, [ $content ] ]; } sub app { my $env = shift; my $result; my $res = try { my $dbh = DBI->connect( 'dbi:mysql:database=test;host=127.0.0.1;port=3306', 'haproxy', '', { RaiseError => 1, AutoCommit => 1 }, ); $dbh->ping; $result = $dbh->selectrow_hashref("SHOW SLAVE STATUS"); $dbh->disconnect; undef; } catch { my $e = $_; response 500, $e; }; return $res if defined $res; if (ref $result) { response 200, $result; } else { response 500, "This mysqld is not a slave."; } } my $runner = Plack::Runner->new; $runner->parse_options(@ARGV); $runner->run(\&app);
これを slave が動作しているホストそれぞれで動作させ、そこに対して HAProxy が HTTP でのヘルスチェックを行うように設定します。死活監視用のユーザには REPLICATION CLIENT の権限を与えておきます。
listen mysql bind 127.0.0.1:3307 mode tcp balance roundrobin option httpchk server slave1 192.168.1.11:3306 check port 5000 server slave2 192.168.1.12:3306 check port 5000 server slave3 192.168.1.13:3306 check port 5000
これで接続できなかったり、master になっていたりした場合には HTTP 500 が返るので HAProxy がダウン検知して切り離されます。MHA で slave が master に昇格しても大丈夫。
特定のローカルファイルが存在していたら 503 を返すようにすれば、一時的に切り離したいときには touch で行えます。レプリケーションの状態を見て一定以上遅れていたら切り離しなど、アプリケーションの都合により柔軟に切り離し条件を設定できるので、便利になりますね。
もちろん死活監視用の daemon が落ちたら mysqld が生きていても切り離されてしまうので、万が一落ちても再度起動できるように daemontools 等から起動するとよいかと思います。
[追記]
ヘルスチェック用 httpd の Go 版も書きました。 fujiwara/mysql-slave-healthcheck-agent · GitHub