HiveServerを使用してPHPからHiveQLを実行する

Hadoopを触る機会があり手探りで構築しているのですが PHP から HiveQLを実行させたいとgoogle先生に伺うと HiveServerを使用してPythonやPerlからHiveQLを実行する の記事があったので、こちらを参考に試してみました。 今回はHadoopのインストールから行います。

まずはHadoopのインストールからです。初めはソースからインストールしていたのですが実際運用となるとやはりパッケージ管理したほうが無難です。そこでClouderaのHadoopディストリビューションパッケージCDH3をインストールします。

環境

  • CentOS 6.3 64bit
  • Hadoop CDH3u5
  • PHP 5.3.16

Javaのダウンロード & インストール

HadoopはJavaソフトウェアの為、Javaのインストールが必要となります。以下のコマンドでJavaのバージョンが低い、またはそもそもインストールされていない場合はインストールする必要があります。 CDH3が推奨しているバージョンは Oracle JDK 1.6, update 8 以上です。

1
$ java -version

Java SE Downloads から Java SE 6 Update xx の JDK を選択します。

自身の環境に沿ったファイルをダウンロードします。ボクの場合は、jdk-6uxx-linux-x64-rpm.binをダウンロードします。 ダウンロードしたファイルはSCP等で転送しましょう。

インストール

実行権限を与えて、インストールを行います。

1
2
$ chmod a+x jdk-6uxx-linux-x64-rpm.bin
$ ./jdk-6uxx-linux-x64-rpm.bin

環境変数の設定

インストールが完了したら環境変数の設定を行います。どのユーザにおいてもその設定が反映するよう /etc/bashrc に以下を追加します。

1
2
3
4
$ vi /etc/bashrc

export JAVA_HOME=/usr/java/default
export PATH=$PATH:$JAVA_HOME/bin

設定を反映させ、正しく設定されているか確認します。

1
2
3
$ source ~/.bashrc
$ printenv JAVA_HOME
/usr/java/default

Hadoop のインストール

擬似分散モードでのHadoopインストールになります。

リポジトリの登録

CDH3+Installation を参考に環境に沿った方法でインストールしてください。

ボクの場合は、リポジトリを追加してyum でインストールします。

1
wget -O /etc/yum.repos.d/cloudera-cdh3.repo http://archive.cloudera.com/redhat/6/x86_64/cdh/cloudera-cdh3.repo

CDH3にもバージョンがあり、CDH3u5 から WebHDFSがサポートされたようです。 なるべく新しいもの使いたいよねということで CDH3u5 をインストールします。その為に先ほどダウンロードしたリポジトリの修正を行います。

1
2
3
4
5
6
7
$ vi /etc/yum.repos.d/cloudera-cdh3.repo

mirrorlist=http://archive.cloudera.com/redhat/6/x86_64/cdh/3/mirrors

↓ 変更

mirrorlist=http://archive.cloudera.com/redhat/6/x86_64/cdh/3u5/mirrors

インストール

修正したら Hadoop のインストールです。時間がかかるので適当に待ちましょう。

1
2
$ yum install hadoop-0.20 hadoop-0.20-native
$ yum install hadoop-0.20-conf-pseudo hadoop-0.20-datanode  hadoop-hive-server

サービスの起動 & 確認

1
2
$ for service in /etc/init.d/hadoop-*; do sudo $service start; done
$ jps

以下が表示されていることを確認します。

  • secondarynamenode
  • datanode
  • namenode
  • jobtracker
  • tasktracker

MapReduceの動作確認

実際に円周率を計算するサンプルを動作させてみます。

1
2
3
4
5
$ hadoop jar /usr/lib/hadoop/hadoop-examples.jar pi 4 2000

・・・ 省略 ・・・
Job Finished in 29.955 seconds
Estimated value of Pi is 3.14100000000000000000

ちゃんと動作していることが確認できましたね。Hadoopには HDFS(Hadoop分散ファイルシステム)を操作するコマンドがあるのですが、長ったらしいのでエイリアスを設定しておきます。

エイリアスの設定

1
2
3
4
5
6
7
8
9
$ vi ~/.bashrc

alias dfsls='/usr/lib/hadoop/bin/hadoop dfs -ls'       # ls¬
alias dfsrm='/usr/lib/hadoop/bin/hadoop dfs -rm'       # rm¬
alias dfscat='/usr/lib/hadoop/bin/hadoop dfs -cat'     # cat¬
alias dfsrmr='/usr/lib/hadoop/bin/hadoop dfs -rmr'     # rm -r¬
alias dfsmkdir='/usr/lib/hadoop/bin/hadoop dfs -mkdir' # mkdir¬
alias dfsput='/usr/lib/hadoop/bin/hadoop dfs -put'     # HDFS に転送¬
alias dfsget='/usr/lib/hadoop/bin/hadoop dfs -get'     # HDFS から転送¬

Hiveの設定

Hive Metastoreの設定

Hive の Metastoreは組み込みのApache Derbyにメタデータを格納するように構成されるている為、単一のユーザーが一度にMetastoreにアクセスすることしかできません。新規ユーザー向けのセットアップを簡単にする為には、代わりにMySQLデータベースを使用するように強く奨励しています。

とマニュアルに記載してあるので、その通りにします。

MySQL JDBC Connector のダウンロード

MySQL JDBC Connectorのバージョンは現在の最新のものを利用してます。

1
2
3
$ cd /tmp
$ curl -L 'http://www.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.22.tar.gz/from/http://mysql.he.net/' | tar xz
$ cp mysql-connector-java-5.1.22/mysql-connector-java-5.1.22-bin.jar /usr/lib/hive/lib/

metastoreデータベースの作成

mysqlにログインしDBを作成します。DB名、ユーザー名、パスワードは適時置き換えて下さい。

1
2
3
4
5
6
7
CREATE DATABASE metastore;
USE metastore;
SOURCE /usr/lib/hive/scripts/metastore/upgrade/mysql/hive-schema-0.7.0.mysql.sql;

CREATE USER 'hiveuser'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT,INSERT,UPDATE,DELETE ON metastore.* TO 'hiveuser'@'localhost';
REVOKE ALTER,CREATE ON metastore.* FROM 'hiveuser'@'localhost';

Hiveの設定

MySQLのMetaStore用のDBを作成したら、Hiveの設定ファイルの編集を行います。 ホスト名、DB名、ユーザー名、パスワードは先程作成した情報に合わせて下さい。

1
$ vi /etc/hive/conf/hive-site.xml
/etc/hive/conf/hive-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<property>
  <name>javax.jdo.option.ConnectionURL</name>
  <value>jdbc:mysql://localhost/metastore</value>
</property>

<property>
  <name>javax.jdo.option.ConnectionDriverName</name>
  <value>com.mysql.jdbc.Driver</value>
</property>

<property>
  <name>javax.jdo.option.ConnectionUserName</name>
  <value>hiveuser</value>
</property>

<property>
  <name>javax.jdo.option.ConnectionPassword</name>
  <value>password</value>
</property>

<property>
  <name>datanucleus.autoCreateSchema</name>
  <value>false</value>
</property>

<property>
  <name>datanucleus.fixedDatastore</name>
  <value>true</value>
</property>

設定が完了したらHadoopを再起動します。

PHPから HiveQLを実行する

HiveServerはThriftプロトコルで接続することができます。PHPの場合は、Hiveパッケージの中に生成済みのライブラリが既に存在するのでこちらを利用します。しかしそのまま利用しようとすると色々不具合があるのでそれを修正していきます。

ライブラリの修正

まずは自身のワークスペースにライブラリをコピーします。

/etc/hive/conf/hive-site.xml
1
cp -a /usr/lib/hive/lib/php/* /my/workspace/thrift

ディレクトリ構造の変更

packagesディレクトリの hive_metastore, hive_service, queryplan, serde が何故か入れ子になっている為、以下の構成に変更します。

/etc/hive/conf/hive-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
▾ packages/
  ▾ fb303/
  |- FacebookService.php
  |- fb303_types.php
  ▾ hive_metastore/
  |- ThriftHiveMetastore.php
  |- hive_metastore_constants.php
  |- hive_metastore_types.php
  ▾ hive_service/
  |- ThriftHive.php
      |- hive_service_types.php
  ▾ queryplan/
      |- queryplan_types.php
  ▾ serde/
  |- serde_constants.php
  |- serde_types.php

とりあえずはデータベース一覧を出力するだけの簡単なスクリプトを作成して実行してみます。

hive.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$GLOBALS['THRIFT_ROOT'] = dirname(__FILE__) . '/lib/';
require_once $GLOBALS['THRIFT_ROOT'] . 'packages/hive_service/ThriftHive.php';
require_once $GLOBALS['THRIFT_ROOT'] . 'transport/TSocket.php';
require_once $GLOBALS['THRIFT_ROOT'] . 'protocol/TBinaryProtocol.php';

$transport = new TSocket('localhost',  10000);
$protocol = new TBinaryProtocol($transport);
$client = new ThriftHiveClient($protocol);
$transport->open();

$client->execute('SHOW DATABASES');
var_dump($client->fetchAll());
$transport->close();

実行すると以下のエラーが出力されます。GLOBALはPHPの予約語と重複しているのでこれをコメントアウトします。

hive.php
1
PHP Parse error:  syntax error, unexpected 'GLOBAL' (T_GLOBAL), expecting identifier (T_STRING) in /my/workspace/lib/packages/hive_metastore/hive_metastore_types.php on line 20
lib/packages/hive_metastore/hive_metastore_types.php
1
2
3
4
5
6
final class metastore_HiveObjectType {
//  const GLOBAL = 1;
  const DATABASE = 2;
  const TABLE = 3;
  const PARTITION = 4;
  const COLUMN = 5;

再度実行するとまたエラーが発生します。

lib/packages/hive_metastore/hive_metastore_types.php
1
PHP Fatal error:  Uncaught exception 'TException' with message 'TSocket: timed out reading 4 bytes from localhost:10000' in /my/workspace/lib/transport/TSocket.php:228

エラーからソケットタイムアウトをしていることがわかります。 TSocketクラスの関数でタイムアウトが設定できる & デフォルトの設定だと短すぎる為、これを先程作成したスクリプトに追加します。

hive.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$GLOBALS['THRIFT_ROOT'] = dirname(__FILE__) . '/lib/';
require_once $GLOBALS['THRIFT_ROOT'] . 'packages/hive_service/ThriftHive.php';
require_once $GLOBALS['THRIFT_ROOT'] . 'transport/TSocket.php';
require_once $GLOBALS['THRIFT_ROOT'] . 'protocol/TBinaryProtocol.php';

$transport = new TSocket('localhost',  10000);
//timeout設定 millisecond
$transport->setSendTimeout(600 * 1000); //追加
$transport->setRecvTimeout(600 * 1000); //追加
$protocol = new TBinaryProtocol($transport);
$client = new ThriftHiveClient($protocol);
$transport->open();

$client->execute('SHOW DATABASES');
var_dump($client->fetchAll());
$transport->close();

タイムアウトの設定を10分に設定しました。そこまで複雑なHQLを実行することもないのでとりあえず良しとします。 実際に運用する場合は、タイムアウト設定もconfig等で変更できるようにします。

実行結果

hive.php
1
2
3
4
5
6
array(2) {
  [0] =>
  string(7) "default"
  [1] =>
  string(4) "hoge"
}

案外さっくりいけますね。実際に集計スクリプトを実装する場合は、ThriftHiveClientインスタンスを返却するようなクラスをひとつかましてあげればうまくいくかな。

参考