CentOS6にQ4Mを入れて、Q4Mを使って他サーバPHPプログラムとの連携

#1. まえがき

私はWordPressを使ってたくさんのサイトを運用しています。
しかし、各サイトの連携をする手段が、データベースサーバーからデータをエクスポート・インポートのバッチ処理だと仕組みが大変です。

そこで、数年前に話題になり、今でもモバゲーなどで基幹システムでも採用しているQ4Mを使えばうまくいくかと考え、インストールの方法と流れにめどができたので、書くことにしました。

#2.考え方
Q4Mは、MySQLにプラグインとして実装するメッセージキューです。
>Q4M (Queue for MySQL) は GPL のもとにライセンスされるメッセージキューで、MySQLのプラグイン可能なストレージエンジンとして動作し、堅牢、高速、柔軟なように設計されています。既にプロダクションの品質であり、幾つかのwebサービスで利用されています(Q4Mのユーザを見てください)。
 出典：http://mogile.web.fc2.com/q4m/

既存のサーバーにMySQLプラグインとして追加する形ができますが、既存のデータベースへの影響は未知数です。
そこで、メッセージキューのシステムとしてのMySQLを独立した形で運用したほうが、障害発生時の原因の切り分けなどがラクなのでおススメします。
今回は、別MySQLでの作り方で記載します。
簡単には、メッセージキュー用MySQLは別領域でかつ別Port番号で受け付けるタイプです。

今回私のサーバー環境は以下の通りです。
```
[root@q4m ~]#uname -a
Linux q4m 2.6.32-573.7.1.el6.x86_64 #1 SMP Tue Sep 22 22:00:00 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
```

#3.各種ソフトのインストール

必要なパッケージを準備し、インストールします。

```
[root@q4m ~]# yum install -y git gcc gcc-c++ bison perl-Data-Dumper
```

gitからcloneされて自動でインストールされます。

```
[root@q4m ~]# cd /usr/local/src
[root@q4m ~]# git clone git://github.com/kazeburo/mysetup.git
[root@q4m ~]# cd mysetup/q4m_mysql56
[root@q4m ~]# sh ./setup.sh
```

cmake, libaio-devel, ncurses-devel は入っていなければこのスクリプトが自動的に yum から入れてくれます。
しばらく待つとビルドが完了し 「メッセージキュー用MySQL」が起動してくれます。

ここまではQiitaの記事に記載があります。
>「CentOS 7 に Q4M をインストールする」
>　出典：http://qiita.com/akishin/items/bbe90f892e9fff2e105c

なお、setup.shのファイルにはバージョン記載があるので、取得するバージョンの確認が必要です。

```
[root@q4m ~]# cat /usr/local/src/mysetup/q4m_mysql56/setup.sh
#!/bin/sh
set -e

MYVER=5.6.20
Q4MVER=0.9.14

CDIR=$(cd $(dirname $0) && pwd)
cd /usr/local/src
if [ -f $CDIR/mysql-$MYVER.tar.gz ]; then
    cp $CDIR/mysql-$MYVER.tar.gz ./
fi

if [ -d mysql-$MYVER ]; then
    rm -rf mysql-$MYVER
fi

if [ ! -f mysql-$MYVER.tar.gz ]; then
    wget http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-$MYVER.tar.gz
fi
tar zxf mysql-$MYVER.tar.gz

if [ -d q4m-$Q4MVER ]; then
    rm -rf q4m-$Q4MVER
fi
if [ ! -f q4m-$Q4MVER.tar.gz ]; then
    wget http://q4m.kazuhooku.com/dist/q4m-$Q4MVER.tar.gz
fi
tar zxf q4m-$Q4MVER.tar.gz
mv q4m-$Q4MVER mysql-$MYVER/storage/q4m
if [ ! -f mysql-$MYVER/storage/q4m/CMakeLists.txt ]; then
  curl -kL https://raw.github.com/q4m/q4m/$Q4MVER/CMakeLists.txt > mysql-$MYVER/storage/q4m/CMakeLists.txt
fi

cd mysql-$MYVER/storage/q4m
cat $CDIR/q4m_test_queuewait_noinnodb.patch | patch -p0
cd /usr/local/src

yum install -y cmake ncurses-devel libaio-devel
cd mysql-$MYVER
cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/q4m \
  -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_EXTRA_CHARSETS=all \
  -DWITH_ZLIB=bundled -DWITH_SSL=bundled -DWITH_READLINE=1 -DWITH_PIC=ON -DWITH_FAST_MUTEXES=ON \
  -DWITH_DEBUG=OFF \
  -DCOMPILATION_COMMENT="Q4M" -DMYSQL_SERVER_SUFFIX="-q4m" \
  -DMYSQL_USER=nobody -DMYSQL_UNIX_ADDR="/tmp/mysql_q4m.sock" -DMYSQL_TCP_PORT=13306 \
  -DWITH_DEFAULT_FEATURE_SET=xsmall \
  -DWITH_PARTITION_STORAGE_ENGINE=1 \
  -DWITHOUT_DAEMON_EXAMPLE_STORAGE_ENGINE=1 \
  -DWITHOUT_FTEXAMPLE_STORAGE_ENGINE=1 \
  -DWITHOUT_EXAMPLE_STORAGE_ENGINE=1 \
  -DWITHOUT_ARCHIVE_STORAGE_ENGINE=1 \
  -DWITHOUT_BLACKHOLE_STORAGE_ENGINE=1 \
  -DWITHOUT_FEDERATED_STORAGE_ENGINE=1 \
  -DWITHOUT_INNOBASE_STORAGE_ENGINE=1 \
  -DWITHOUT_PERFSCHEMA_STORAGE_ENGINE=1 \
  -DWITHOUT_NDBCLUSTER_STORAGE_ENGINE=1 \
  -DWITH_INNODB_MEMCACHED=OFF \
  -DWITH_EMBEDDED_SERVER=OFF \
  -DWITH_UNIT_TESTS=OFF
make
make install

mkdir -p /usr/local/q4m/etc
cp $CDIR/my.cnf /usr/local/q4m/etc
cp $CDIR/q4m.init /etc/init.d/q4m
chmod 755 /etc/init.d/q4m
chkconfig --add q4m

/usr/local/q4m/scripts/mysql_install_db --skip-name-resolve \
  --basedir=/usr/local/q4m --defaults-file=/usr/local/q4m/etc/my.cnf
rm -f /usr/local/q4m/my.cnf
chmod 755 /usr/local/q4m/var
/etc/init.d/q4m start

cat storage/q4m/support-files/install.sql | /usr/local/q4m/bin/mysql -S /tmp/mysql_q4m.sock
echo "show plugins" | /usr/local/q4m/bin/mysql -S /tmp/mysql_q4m.sock

[root@q4m ~]# 
```


#4.MySQLの併存方法
「メッセージキュー用MySQL」と既存のMySQLを併存することは可能です。
「メッセージキュー用MySQL」は初期インストール時にはポート番号13306で稼働しています。

##4.1　「メッセージキュー用MySQL」の初期Configファイル

「メッセージキュー用MySQL」の初期Configファイルは以下の通りです。

```
[root@q4m ~]# cat /usr/local/q4m/my.cnf
[client]
port            = 3306
socket          = /tmp/mysql_q4m.sock
#default-character-set = utf8

[mysql]
no-auto-rehash
#safe-updates
prompt          = '\u@\h mysql>'
# also can use utf8mb4
default_character_set = utf8

[mysqld]
basedir         = /usr/local/q4m
datadir         = /usr/local/q4m/var
user            = nobody
port            = 13306
socket          = /tmp/mysql_q4m.sock
skip-external-locking
key_buffer_size = 1M
max_allowed_packet = 64M
table_open_cache = 256
max_connections = 4096
max_connect_errors = 10000
sort_buffer_size = 128K
read_buffer_size = 128K
myisam_sort_buffer_size = 128K
thread_cache_size = 256
query_cache_size = 0
query_cache_type = 0
tmp_table_size = 64M
max_heap_table_size = 64M
skip-name-resolve
# also can use utf8mb4
character-set-server=utf8
default-storage-engine=MyISAM
default-tmp-storage-engine=MyISAM
transaction_isolation = REPEATABLE-READ
explicit_defaults_for_timestamp

sql_mode = "STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE"

slow_query_log = 1
long_query_time = 10
log_slow_admin_statements

[mysqldump]
quick
max_allowed_packet = 16M

[myisamchk]
key_buffer_size = 8M
sort_buffer_size = 8M
read_buffer = 1M
write_buffer = 0M

[mysqld_safe]
open-files-limit = 8192

[root@q4m ~/]# 
```

適宜改変してください。

##4.2　「メッセージキュー用MySQL」の起動停止方法

初期インストールの場合には、サービスに登録されているので、以下の通りで起動・停止します。

起動する手順

```
[root@q4m ~]# service q4m start
Starting MySQL-Q4M:                                        [  OK  ]
[root@q4m ~]# 
```

停止する手順

```
[root@q4m ~]# service q4m stop
Stopping MySQL-Q4M:                                        [  OK  ]
[root@q4m ~]# 
```


##4.3　「メッセージキュー用MySQL」のポート番号開け
iptablesなどでポート番号を開けます。（私のサーバー環境はiptablesではなくルーター設定のため割愛します）

##4.4　「メッセージキュー用MySQL」の設定
「メッセージキュー用MySQL」内部に接続用ユーザー、データベース作成を行います。
初期のデータベースでrootユーザーの場合には、パスワードが設定されていません。

```
[root@q4m ~]# cd /usr/local/q4m/bin/
[root@q4m bin]# ./mysql -u root 
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.6.20-q4m-log Q4M

Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

root@localhost mysql>
```

気を付けなければならないのが、環境変数PATHでコマンドが検索されます。
インストールした「メッセージキュー用MySQL」のディレクトリにPATHが通っていなければ、「mysql」と入力すると /usr/bin/ /usr/sbinなどのディレクトリへ検索しにいきます。
「メッセージキュー用MySQL」のコマンド「./mysql」には./をつけてください。

(1)接続用ユーザーを作成

```
mysql> CREATE USER 'setuzokuser'@'192.168.5.1' identified by 'abcdefg';
```

(2)メッセージキュー用データベースを作成

```
mysql> CREATE DATABASE wpdbmq;
```

(3)権限の設定

```
mysql> GRANT ALL PRIVILEGES ON wpdbmq.* TO 'setuzokuser'@'192.168.5.1' IDENTIFIED BY 'abcdefg';
```

(4)権限の即時反映

```
mysql> flush privileges;
```

(5)rootユーザーのパスワード設定

```
mysql> SET PASSWORD = PASSWORD('ab9w37daskk');

```

(6)接続オフ

```
mysql> quit;

```

#5. 「メッセージキュー用MySQL」を使ってメッセージキュー送受信

メッセージキューとしての送受信のからくりはこちらです。
> チュートリアル
> http://mogile.web.fc2.com/q4m/tutorial.html

#5.1 メッセージキューのテーブル作成

やりとりになるメッセージキューのテーブルを作ります。
```
mysql> CREATE TABLE my_queue (v1 int not null, v2 varchar(255)) ENGINE=queue;
```

#5.2 メッセージキュー受信のPHPプログラム
メッセージキュー受信用PHPプログラムを作ります。

```
[root@192.168.5.1 ~]# cat 1.php
<?php

$dsn = 'mysql:dbname=wpdbmq;host=q4m;port=13306';
$user = 'setuzokuser';
$password = 'abcdefg';

try{
    $dbh = new PDO($dsn, $user, $password);
    while(1){
        $sql = 'select queue_wait("my_queue")';
        foreach ($dbh->query($sql) as $row) {
            $sql = "select * from my_queue";
            foreach( $dbh->query($sql) as $result){
                $key = $result['v1'];
                print $result['v1'] . " " ;
                print $result['v2'] . " ";
                print "\n" ;
            }
            // $key が9の場合は終了処理
            if($key == 9){
                break;
            }
        }
        $sql = "select queue_end()";
        $dbh->query($sql);

        // $key が9の場合は終了処理
        if($key == 9){
            break;
        }
    }
}catch (PDOException $e){
    print('Error:'.$e->getMessage());
    die();
}

$dbh = null;
[root@192.168.5.1 ~]# 
```

##5.3 メッセージキュー送信
送信はプログラムでなくても、MySQLコマンド上から送信することで対応できます。

```
root@localhost mysql>INSERT INTO my_queue (v1, v2) VALUES (3, "hello world!123");
Query OK, 1 row affected (0.00 sec)

root@localhost mysql>INSERT INTO my_queue (v1, v2) VALUES (3, "hello world!123");
Query OK, 1 row affected (0.28 sec)

root@localhost mysql>INSERT INTO my_queue (v1, v2) VALUES (9, "hello world!123");
Query OK, 1 row affected (0.28 sec)
```

##5.4 メッセージ受信のPHPプログラムの動き
キーで9が来たらプログラムが終了します。

```
[root@192.168.5.1 ~]# php 1.php
3 hello world!123 
3 hello world!123 
9 hello world!123 
[root@192.168.5.1 ~]# 
```

##5.5　応用例
・メッセージキュー受信のPHPプログラムをCRONで定期的に起動させ、メッセージを受信したら何かをするようなことができます。
・反応性が必要とまではいかないものの、重いプログラム系にはメッセージキューのからくりには使えます。

```
mysql> SELECT queue_wait('my_queue');
```
・queue_wait関数が有限時間のタイムアウトでwaitしてくれるので、ずっとブロック（処理待ち）ならないのが助かります。


#6. まとめ
今回の例では、データベースをメッセージキュー専用MySQLとしてプロセスをデーモンとして起動させておくことがポイントになります。
受け付けるポート番号（初期値：１３３０６）を既存のMySQLと分けることで併存ができ、またメッセージキュー専用MySQLにすることで、データベースの容量は比較的コンパクトなサイズでの運用も可能です。
可能なら今流行りのDocker化して「メッセージキューサーバー」みたいな運用するとさらにいいかもしれません。
また、キャッシュサーバーとしてmemcachedなども連携すると、色々な広がりが見えてきそうです。

敷居の高そうなQ4Mも簡単に導入できるので、サーバーのWordPress連携などに使えそうです。