2014年12月4日木曜日

MySQL Fabricつらい(PerlのDBD::mysqlを対応させる旅路)

この記事は Perl Advent Calendar 2014 の4日目の記事です。

最近 MySQL Fabric という "MySQLにお手軽HA & シャーディング、しかもプロキシ不要!" なんてことを謳っているミドルウェアで遊んでいるわけですが、このMySQL Fabric、プロキシ不要な代わりに対応しているコネクターが必要になっています(ある意味、コネクターがプロキシ機能を持っている感じ)

さてこのMySQL Fabric対応コネクター、今のところの選択肢は
* Oracle公式
  * GA版
    * Connector/J
    * Connector/Python
  * 開発版(ラボ版)
    * Connector/C
* PHPコミュニティ製
  * 開発版(pre-alpha quority)
    * mysqlnd
だけです。

( ゚д゚) えっ

折角テスト環境作って、テキトーなコード書いてほげほげしようと思っても、普段使いのPerlがリストされてなくてつらい。じゃあPHPでいっかーと思った時期もあるんですが、"Sharding is the only use case supported by the plugin to date."(2014/12/4現在)だそうで、HAの検証ができない。

 迷 わ ず ラ ボ 版 の Connector/C に し ま し た (にこ

( ´-`).oO(このあたり(Connector/C使ってるあたり)は このへん に。。
( ´-`).oO(調子に乗ってそれをmysqlコマンドラインクライアントにポートしたのは このへん に。。

mysqlコマンドラインクライアントのFabric-aware化をやってて気が付いたんですが、PerlのDBD::mysqlもlibmysqlclient(= Connector/C)使ってるんですよね。ということはそれを呼び出す側を実装するのはそう難しくはないはず。(同じことで、libmysqlclientを使ってないドライバーはMySQL Fabric対応しようとすると自前でその部分をまるまる書き上げないといけないのでかなりつらいと思う。PHPとかそれでシャード機能しかまだサポートしてないし、Rubyも大変そう(どっちもlibmysqlclient使ってる実装もあったはずだけど))

という訳で雑にパッチしました。

Support MySQL Fabric in mysql_dr_connect. · 1052599 · yoku0825/DBD-mysql

ざっと言うと、
* もちろんMySQL Fabric対応のConnector/Cをインクルードしてリンクしてコンパイルする必要がある
* まだHA対応の部分しか書いてない。シャードには未対応(Cでもまだ調べてない)
* MySQL Fabric経由で接続する場合にはmysql_initとmysql_real_connectの間にmysql_optionsでMYSQL_OPT_USE_FABRICを押し込んでやる必要がある
  * ここは他のオプションとそんなに変わらないんですが
* mysql_real_connectした *後* で mysql_options{,4}でグループ名とかHAモードを指定してやらないといけない
  * 他の(既存の)オプションは全てmysql_initとmysql_real_connectの間に記述するようになってるので、ここ面倒くさい
    * 今は接続時に全てDSNから受け取って決めうちにしてる。
    * 接続後にグループとかHAモードを書き換えられるから、$dbh->{Mode} = 'ro'; とかで簡単に変えられる未来が来る気がする(まだやってない)
な感じで実装しています。

使い方はこんな感じ。まずはConnector/Cをコンパイル(URLなどは2014/12/4現在のものです。


$ wget http://downloads.mysql.com/snapshots/pb/mysql-connector-c-6.2.0-labs/mysql-connector-c-6.2.0-labs-src.tar.gz
$ tar xzf mysql-connector-c-6.2.0-labs-src.tar.gz
$ cd mysql-connector-c-6.2.0-labs-src
$ cmake -DCMAKE_INSTALL_PREFIX=/usr/mysql/connector .
$ make
$ sudo make install

他のと混じらないように、/usr/mysql/connectorの下に突っ込みました。




$ git clone https://github.com/yoku0825/DBD-mysql
$ cd DBD-mysql/
$ perl Makefile.PL
..
Can't find mysql_config. Use --mysql_config option to specify where mysql_config is located
..

む、Connector/Cにはmysql_configが入ってないから怒られた。取り敢えずyumで突っ込んで、後からMakefileを手で修正することにするか。


$ sudo yum install mysql
$ perl Makefile.PL
$ cp -ip Makefile{,.orig}
$ vim Makefile
..
$ diff -c Makefile{.orig,}
*** Makefile.orig       2014-12-04 05:29:00.748424064 +0000
--- Makefile    2014-12-04 05:33:38.050242874 +0000
***************
*** 161,167 ****
  PARENT_NAME = DBD
  DLBASE = $(BASEEXT)
  VERSION_FROM = lib/DBD/mysql.pm
! INC = -I$(DBI_INSTARCH_DIR) -I/usr/include/mysql55 -g -pipe -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -fno-strict-aliasing -fwrapv -fPIC  -fPIC -g -static-libgcc -fno-omit-frame-pointer -fno-strict-aliasing -DMY_PTHREAD_FASTMUTEX=1 -DDBD_MYSQL_WITH_SSL -DDBD_MYSQL_INSERT_ID_IS_GOOD -g
  OBJECT = $(O_FILES)
  LDFROM = $(OBJECT)
  LINKTYPE = dynamic
--- 161,167 ----
  PARENT_NAME = DBD
  DLBASE = $(BASEEXT)
  VERSION_FROM = lib/DBD/mysql.pm
! INC = -I$(DBI_INSTARCH_DIR) -I/usr/mysql/connector/include -g -pipe -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -fno-strict-aliasing -fwrapv -fPIC  -fPIC -g -static-libgcc -fno-omit-frame-pointer -fno-strict-aliasing -DMY_PTHREAD_FASTMUTEX=1 -DDBD_MYSQL_WITH_SSL -DDBD_MYSQL_INSERT_ID_IS_GOOD -g
  OBJECT = $(O_FILES)
  LDFROM = $(OBJECT)
  LINKTYPE = dynamic
***************
*** 331,340 ****
  # DBD::mysql might depend on some other libraries:
  # See ExtUtils::Liblist for details
  #
! EXTRALIBS = -L/usr/lib64/mysql -lmysqlclient -lz -lssl -lcrypto
! LDLOADLIBS = -L/usr/lib64/mysql -lmysqlclient -lpthread -lz -lm -lssl -lcrypto -ldl
  BSLOADLIBS =
! LD_RUN_PATH = /usr/lib64/mysql:/lib64:/usr/lib64


  # --- MakeMaker const_cccmd section:
--- 331,340 ----
  # DBD::mysql might depend on some other libraries:
  # See ExtUtils::Liblist for details
  #
! EXTRALIBS = -L/usr/mysql/connector/lib -lmysqlconc -lz -lssl -lcrypto
! LDLOADLIBS = -L/usr/mysql/connector/lib -lmysqlconc -lpthread -lz -lm -lssl -lcrypto -ldl
  BSLOADLIBS =
! LD_RUN_PATH = /usr/mysql/connector/lib:/lib64:/usr/lib64


  # --- MakeMaker const_cccmd section:
$ make
$ sudo make install

gccに渡される-Iオプションをさっきインストールしたディレクトリ/includeに、-Lをさっきのディレクトリ/libに、-lmysqlclientを-lmysqlconcに書き換えます。
これで、MySQL Fabric(のHA機能だけだけど)に対応したBDB::mysqlができました。

サンプルコードはこんな感じになります。


$ vim test.pl
  1 #!/usr/bin/perl
  2
  3 use strict;
  4 use warnings;
  5 use DBI;
  6 use Data::Dumper;
  7
  8 my $count;
  9 my $conn= DBI->connect("dbi:mysql::127.0.0.1;port=32275;" .
 10                        "fabric_group=my_third_fabric;" .
 11                        "fabric_real_user=msandbox;" .
 12                        "fabric_real_password=msandbox;" .
 13                        "fabric_default_mode=ro", "admin", "xxxx") or die;
 14
 15 for (my $n= 1; $n <= 1000; $n++)
 16 {
 17   my $port= $conn->selectrow_arrayref("SELECT \@\@port")->[0];
 18   $count->{$port}++;
 19 }
 20
 21 print Dumper $count;
 22
 23 exit 0;

$ perl test.pl
Using Fabric for MYSQL connection
DBD::mysql::db selectrow_arrayref warning:  at test.pl line 17.
$VAR1 = {
          '20887' => 209,
          '20889' => 193,
          '20890' => 182,
          '20886' => 236,
          '20888' => 180
        };

DBI->connectのDSN部分、portとかmysql_socketとかをセミコロン区切りで書くあそこに、
* fabric_group= MySQL Fabricのグループ名
* fabric_real_user= MySQL Fabric経由でつながる実際のMySQLのユーザー名(アプリケーションユーザーになる)
* fabric_real_password= 同、パスワード。
* fabric_default_mode= "ro"(=全ノードで分散), "rw"(=マスターのみ)のいずれか。
を書きます。本来のユーザー, パスワード(↑で"admin", "xxxx"になってるところ)には、*MySQL Fabricのユーザー名とパスワード* (mysqlfabric manage setupした時に設定したやつ)を指定します。

"Using Fabric for MYSQL connection"が出てしまうのはConnector/C側にハードコードされてるから。そのうち直るとは思いますが、探せばさっくり消せる位置にあるので消してないのは単なる手抜きです(sql-common/client.c:CLI_MYSQL_REAL_CONNECT参照)

ともあれ、DSNに書き加えてやるだけでMySQL FabricのHA機能が使えるようになったはずです。試してみましょう。


mysql> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `num` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `val` varchar(32) DEFAULT NULL,
  `dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY `num` (`num`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

$ vim test2.pl
#!/usr/bin/perl

use strict;
use warnings;
use DBI;
use Data::Dumper;

my $conn= DBI->connect("dbi:mysql::127.0.0.1;port=32275;" .
                       "fabric_group=my_third_fabric;" .
                       "fabric_real_user=msandbox;" .
                       "fabric_real_password=msandbox;" .
                       "fabric_default_mode=rw", "admin", "xml",
                       {RaiseError => 0, PrintError => 0}) or die;
$conn->{mysql_auto_reconnect}= 1;

for (my $n= 1; $n <= 1000; $n++)
{
  eval
  {
    my $port= $conn->selectrow_arrayref("SELECT \@\@port")->[0];
    $conn->do("INSERT INTO d1.t1 SET val= ?", undef, $port);
  };

  if ($@)
    {next;}
  sleep 1;
}

exit 0;

1秒に1回ずつ、d1.t1に接続先(今回は"rw"に設定しているので、常にマスターになるはず)と時刻を書き込みます。


$ perl test2.pl
Using Fabric for MYSQL connection
DBD::mysql::db selectrow_arrayref warning:  at test2.pl line 20.

mysql> SELECT * FROM d1.t1;
+-----+-------+---------------------+
| num | val   | dt                  |
+-----+-------+---------------------+
|   1 | 20886 | 2014-12-04 19:58:49 |
|   2 | 20886 | 2014-12-04 19:58:50 |
|   3 | 20886 | 2014-12-04 19:58:51 |
+-----+-------+---------------------+
3 rows in set (0.00 sec)

$ kill -9 master-mysqld master-mysqld_safe

$ mysqlfabric group lookup_servers my_third_fabric
xPassword for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

                         server_uuid         address    status       mode weight
------------------------------------ --------------- --------- ---------- ------
163c889f-7ba3-11e4-ae68-fa163e020fd0 127.0.0.1:20886    FAULTY READ_WRITE    1.0
21482fb7-7ba3-11e4-ae68-fa163e020fd0 127.0.0.1:20887 SECONDARY  READ_ONLY    1.0
21e48205-7ba3-11e4-ae68-fa163e020fd0 127.0.0.1:20888 SECONDARY  READ_ONLY    1.0
22a941a2-7ba3-11e4-ae68-fa163e020fd0 127.0.0.1:20889 SECONDARY  READ_ONLY    1.0
2345a5b7-7ba3-11e4-ae68-fa163e020fd0 127.0.0.1:20890   PRIMARY READ_WRITE    1.0

切り替わりで20890ポート(=slave4)が新しいマスターになったっぽいです。


$ ./s4
mysql> SELECT * FROM d1.t1;
+-----+-------+---------------------+
| num | val   | dt                  |
+-----+-------+---------------------+
|   1 | 20886 | 2014-12-04 19:58:49 |
|   2 | 20886 | 2014-12-04 19:58:50 |
|   3 | 20886 | 2014-12-04 19:58:51 |
..
|  41 | 20886 | 2014-12-04 19:59:29 |
|  42 | 20886 | 2014-12-04 19:59:30 |
|  43 | 20886 | 2014-12-04 19:59:31 |
|  44 | 20890 | 2014-12-04 19:59:38 |
|  45 | 20890 | 2014-12-04 19:59:39 |
|  46 | 20890 | 2014-12-04 19:59:40 |
..
| 112 | 20890 | 2014-12-04 20:00:46 |
| 113 | 20890 | 2014-12-04 20:00:47 |
| 114 | 20890 | 2014-12-04 20:00:48 |
+-----+-------+---------------------+
114 rows in set (0.00 sec)

切り替わりました!! 今回は7秒くらいで切り替わってるんですねー。

ということで、MySQLの最新機能をPerlのDBD::mysqlで遊ぶ紹介でした。とっても簡単に遊べるので、みなさんも是非DBD::mysqlにパッチしてみてください。

Have fun!! :)

0 件のコメント :

コメントを投稿