PHP7からHHVMに移行する&ベンチマーク

HHVM(HipHop Virtual Machine)とは

HHVMは、Facebookが作成したPHP互換の仮想マシンです。

JITコンパイルを採用しており、パフォーマンス向上を目指しています。

割と歴史があるようで、PHP7のない2014年ごろから爆速仮想マシンと言われていたみたいですが、
PHP7が早いよすげーって感じで埋もれたようです。(2016年に日本語情報が全く無い)

でもこちらも長い時間をかけて速度向上しているようで、

今でもベンチマークをとった感じPHP7と比較しても30%以上早いんじゃ無いかという感じです。

というより早すぎてビビる。

裏でファイル情報を読み込みまくるプログラムなんかは、レスポンスが2倍くらい早くなりました。

この辺りネイティブコードが強いんだろうなという感じ。

Hack言語というAltPHPな言語も使えますが、PHP5.6ベースであり、そのままのPHPコードでも動作します。

(ここではHack言語には一切触れませんが、イメージとしてはJavaScriptとTypeScriptの関係みたいな感じです)

そのためPHP7で廃止になってしまったメソッドも含まれていますが、もちろんPHP7の機能も使えます。

ここまで利点があるのにでもやっぱり日本だと知名度無い・・・無く無い?

もう開発期間もかなり経ってますしFacebookで実運用されていることから、かなり安定しているのでは?と思うのですがね。

インストール

とりあえず公式を見ているとyumリポジトリも無くそもそもGetting Starterに無いので

「CentOSじゃなくてUbuntu/Debianを使え」って感じですが、

そんなこといってもこのサーバはCentOSなので、CentOS向けに構築します。

Ubuntu/Debianであれば、おなじみのapt-getで簡単セットアップできます。羨ましい。

building-and-installing-hhvm-on-centos-7.x

ここに書いてあるやり方で、最新バージョンではビルドが通りませんでした。

どうやら最新のバージョンでは、依存するライブラリのバージョン(GCCとか)がCentOS7の公式yumリポジトリでは古いため、

多くのライブラリやコンパイラをソースビルドする必要があるようです。

building-and-installing-hhvm-on-centos-6.6

ここに書いてある方法を実践すればCentOS6でも7でもいけると思いますが、

かなりのパッケージをソースビルドする必要があり、非常に時間がかかるためちょっと断念。

そのため、公式から現状で一番手軽に入れられるであろうプレビルド版3.15.3を使用します。

rpmを使用してインストールします。yumよりは手間ですが、ソースビルドを考えるともの凄く楽ですね。

Prebuilt-Packages-on-Centos-7.x

ここに書いてあることを実践すればすぐにインストールできました。

yum install cpp gcc-c++ cmake git psmisc {binutils,boost,jemalloc,numactl}-devel \
{ImageMagick,sqlite,tbb,bzip2,openldap,readline,elfutils-libelf,gmp,lz4,pcre}-devel \
lib{xslt,event,yaml,vpx,png,zip,icu,mcrypt,memcached,cap,dwarf}-devel \
{unixODBC,expat,mariadb}-devel lib{edit,curl,xml2,xslt}-devel \
glog-devel oniguruma-devel ocaml gperf enca libjpeg-turbo-devel openssl-devel \
mariadb mariadb-server make libc-client -y

rpm -Uvh http://mirrors.linuxeye.com/hhvm-repo/7/x86_64/hhvm-3.15.3-1.el7.centos.x86_64.rpm

hhvm --version
HipHop VM 3.15.3 (rel)
Compiler: tags/HHVM-3.15.3-0-g965cd8d30670726d88720412c3384225a2da011d
Repo schema: 7f22464926cb7fc3a19c6af88309c612a8581c55

ビルトインウェブサーバーで使用する

PHP仮想マシンなので、基本的にはPHPと同じように使えます。

テストとして、PHPと同じようにビルトインウェブサーバー機能を使ってみましょう。

PHPソースも特に変更する必要はありません。 phpinfo() も使えます。

# PHPの場合
cd ~/html/
php -S localhost:8000

# HHVMの場合
cd ~/html/
hhvm -m server -p 8000

Nginx+HHVM(FastCGI Mode)で使用

実環境するのであれば、Nginx+PHP-FPMが今の環境です。

やはり稼働も大変なんでしょう?と思いがちですが、
PHP-FPMをそのままHHVM(FastCGI Mode)に差し替えるだけでOK。

うまくやればNginx側の設定は一切変更なし or 一行変更でいけます。

設定ファイルは/etc/hhvm/server.iniで設定を行います。

しかし最初から最適化されていたりもするので、ほぼ設定なしでもそれなりに動きます。すげえぜ。

# ログディレクトリを設定
mkdir /var/log/hhvm/
chown nginx -R /var/log/hhvm/
chgrp nginx -R /var/log/hhvm/

# runディレクトリの設定
mkdir /var/run/hhvm
chown nginx -R /var/run/hhvm/
chgrp nginx -R /var/run/hhvm/

# php-fpmをストップ。この時点でPHPは動かなくなるので慎重に
systemctl stop php-fpm

# service
systemctl enable hhvm
systemctl start hhvm

/etc/hhvm/server.ini


; hhvm specific hhvm.pid_file = "/var/log/hhvm/pid" hhvm.server.port = 9001 ;hhvm.server.file_socket = /var/run/hhvm/hhvm.sock hhvm.server.type = fastcgi # 略 date.timezone = Asia/Tokyo

/etc/nginx/conf.d/nginx.conf

# 前略 ほぼ変更なし
        fastcgi_pass   127.0.0.1:9001;
        fastcgi_pass unix:/var/run/hhvm/hhvm.sock;
# 後略

WordPressでベンチマーク

簡単にApache Benchを使ったベンチマークもやってみましょう。

そのまま、このブログを対象としています。
そう、このブログは既にHHVMに移行済みです。

Nginx + php-fpm(PHP7)版

ab -n 500 -c 100 https://blog.potproject.net/

Concurrency Level:      100
Time taken for tests:   76.811 seconds
Complete requests:      500
Failed requests:        0
Total transferred:      41469000 bytes
HTML transferred:       41329000 bytes
Requests per second:    6.51 [#/sec] (mean)
Time per request:       15362.273 [ms] (mean)
Time per request:       153.623 [ms] (mean, across all concurrent requests)
Transfer rate:          527.23 [Kbytes/sec] received

Nginx + HHVM(FastCGI Mode)版

ab -n 500 -c 100 https://blog.potproject.net/

Concurrency Level:      100
Time taken for tests:   24.787 seconds
Complete requests:      500
Failed requests:        0
Total transferred:      41467500 bytes
HTML transferred:       41329000 bytes
Requests per second:    20.17 [#/sec] (mean)
Time per request:       4957.490 [ms] (mean)
Time per request:       49.575 [ms] (mean, across all concurrent requests)
Transfer rate:          1633.71 [Kbytes/sec] received

処理によっては速度はまちまちですし、場合によってはPHP7の方が早いかもしれませんが、
これだけでも爆速の恩恵がわかると思います。単純比較で3倍ですよ。こりゃ早え。

とりあえず、日本での知名度は上げたいですね。

まあ、スルーしていてどういうのかは自分も最近どういうものか知ったわけなので人のこと言えませんが・・・

(なんか名前的に難しそうで・・・HHVM/Hackだもんなあ・・・)

Facebook PHP SDK v3.2.3でGraph API のバージョンを変更する

今回はいつもと趣向を変えた、バッドノウハウです。

Facebook PHP SDK v3.2.3は、名前の通りFacebookのGraph APIを叩くためのSDKですが、
2015年1月の時点でdeprecated(非推奨)となっております。2年前です。

非推奨というだけで今までは使用できたのですが、このSDKはGraph APIv2.2を使用しているため、利用期限が過ぎ、完全にこのままでは使えなくなりました。

使用期限:2017年3月25日

https://developers.facebook.com/docs/apps/changelog?locale=ja_JP

なので、現在推奨されているFacebook SDK for PHP (v5)を使用しましょう。

で、本来はそこで終了なのですが、実際はそうもいかない人たちもいるようで・・・
Facebook SDK for PHP (v5)はPHP5.3非対応のため、PHP5.3の場合は、このSDKを使用しているところもあります。
PHPバージョンのシェア率を見ると、PHP5.3以下はまだ 25% も存在するようです。
自分もその被害者のため、完全にバッドノウハウなわけですが、対応方法を書いておきます。

この方法で、最新バージョンのv2.8まで動作しました。しかし、完全に動作するとは思わないほうがいいです。
何時使えなくなるかもわからない綱渡り的な状態であり、応急処置みたいなものなので、早いところPHPのバージョンを上げましょう。

参考:facebook graph api not work from 2.2 to 2.3
http://stackoverflow.com/questions/42994019/facebook-graph-api-not-work-from-2-2-to-2-3

まず、バージョンを変更します。デフォルトではv2.2を使用するようになっていますが、そこを明示的に指定することで、バージョンを指定することが出来ます。
v2.8であれば/v2.8/という風にURLを変更します。v2.3にしたほうが無難だと思います。

facebook-php-sdk/src/base_facebook.php 164行目付近

  public static $DOMAIN_MAP = array(
    'api'         => 'https://api.facebook.com/v2.8/',
    'api_video'   => 'https://api-video.facebook.com/v2.8/',
    'api_read'    => 'https://api-read.facebook.com/v2.8/',
    'graph'       => 'https://graph.facebook.com/v2.8/',
    'graph_video' => 'https://graph-video.facebook.com/v2.8/',
    'www'         => 'https://www.facebook.com/v2.8/',
  );

これだけでは動きません。Graph API v2.3から、アクセストークンを取得した時の振る舞いが変更され、配列からjsonを返すように変更されたためです。
そのため、返ってきたjsonをArrayに変換することで動くようにします。
2箇所同じような部分があるので、そこも同じように変更します。両方変更しないとユーザアクセストークンなどが取得できなかったりします。

facebook-php-sdk/src/base_facebook.php 413行目
facebook-php-sdk/src/base_facebook.php 808行目

    $response_params = array();
    parse_str($access_token_response, $response_params);

この部分を以下のように変更します。

    //$response_params = array();
    $response_params = json_decode($access_token_response, true);

これでとりあえずは指定したGraph APIで動作するようになります(v2.3,v2.8以外未確認です)。
OAuthログイン、/me/、/me/account/、/feed/などは動作確認済みです。

もう一度言いますが、PHP5.4以上の方はFacebook SDK for PHP (v5)を使用しましょう。

サーバー移管した話

potproject.net Advent Calendar 201612日目の記事です。

今日は手抜き。


ようやく、サーバの移管がほぼ完了したのでそれに関する簡単なTipsとメモです。
今回は10分くらいで書ける記事を目指しています。ついでに前の記事の宣伝です。たまには簡単に行きましょう。

20分くらいかかってしまった・・・

WordPress移行

基本的にはWordpressのデフォルト機能として備わっている インポート/エクスポート機能 で移行しました。やはりデフォルトなので一番わかりやすくかつ簡単だと思います。
デフォルト機能なのですが、プラグインが必要だったりします。
古いサーバから管理画面より「ツール」→「エクスポート」からxmlファイルとしてエクスポート、
新しいサーバでインポートで特殊でない大体の記事は問題なく動きます。
スキンやプラグインなどは反映されませんが、そこは数も少ないので手動でした。

Webサーバ構築

この部分は記事別にいろいろまとめています。おさらいです。
自分のサーバではnginx,node.js,sinatra(ruby)と3つほどWebサーバが稼働しています。
centos7+nginx+php-fpm+php7な新しい感じの環境を構築
nginxでRackアプリケーション(sinatra)を動かす

SSL(https)対応

このあたりも(ryです。
おかげさまでSSLオンリーのサイトになりました。
これからは個人サイトといえど必須だと私は思っています。自動更新も忘れずに。
Centos+nginxでLet’s encrypt(certbot)導入と自動更新まで
[nginx]Let’s EncryptでSSLのセキュリティをA+にするまで

Mysqlのダンプ取得

Mysqlのダンプはやはりphpmyadminからエクスポートするのが楽でいいですが、コマンドならmysqldump -u USERNAME -p DBNAME > OUTPUTですね。
取得したらmysql -u USERNAME -p DBNAME < OUTPUTでインポート。たまにデータベースを先に作成する必要があったりすることもあります。その場合は“create database DBNAME“`で作成。

古いサーバはまだ残っていて、放置してるとやはりお金が無駄に取れますので、早いところ解約しなきゃ・・・

nginxでRackアプリケーション(sinatra)を動かす

potproject.net Advent Calendar 2016
4日目の記事です。


うちのブログ初となるであろうRubyの記事です。

実際のところ、自分は全くというほどRubyを書けないのですが、

RubyのWebサービスに関してちょっと使いたいものがあるために構築しています。

なのでRubyの書き方とかSinatraやRailsの使い方ついては他でお願いしたい。

大体ネットに転がっているものを見るとやっぱりRailsばっかりなので、割と珍しいかもしれません。

OSはCentOS7です。

Rubyインストール

今回はちよっと古めなんですが事情によりRuby2.0を使いたいため、

centos7 baseリポジトリからそのままRubyをインストールします。

依存関係でrubygemsなんかも一緒にインストールされました。

で、まあRubyとしては定番であろうパッケージ管理ソフトウェアBundlerをgemからインストール。

rubygemsとbundlerと2つもパッケージ管理ソフトウェアがあるので紛らわしいと自分も思っているのですが、

bundlerはパッケージのバージョンを管理して一括インストールできるソフトウェアです。

プロジェクトごとに個別に設置するので、そのプロジェクトに合ったパッケージをインストールできます。
今回はちゃんとbundlerを使ってsinatraやunicornをインストールします。

# Rubyインストール
yum install ruby ruby-devel
# rubygemsが入っているか確認
gem -v
# rubygemsが古かったのでアップデート
gem install rubygems-update
# bundlerインストール
gem install bundler
# 入ってるgemパッケージを確認
gem list

ruby-develもCentOS7系なら入れておかないと多分動かない。

gemfile作成

# 何かディレクトリを作成する
mkdir sinatra
# Genfile作成
bundle init

このコマンドでどのgemパッケージをインストールするかを記載できるGemfileというファイルが作成されます。

Genfileにsinatraとunicornをインストールするよう記述。

Gemfile

source "https://rubygems.org"

gem "sinatra"

インストール。 パスはvendor/bundle にするのが一般的らしい。

bundle install --path vendor/bundle

webページ作成

hello.rb

require 'sinatra'

get '/' do
  'hello world!'
end

簡単なHello World!コードです。
これで直下に表示されます。

起動

bundle exec ruby hello.rb -p 3000

これで起動します。portも設定できます。デフォルトは4567です。

試しに別ユーザーで curl http://localhost:3000/でちゃんと hello world! と表示されました。

nignxでリバースプロキシ

/etc/nginx/conf.d/sinatra.conf

server {
    server_name (バーチャルホスト名);
    proxy_set_header Host $http_host;
    location / {
        proxy_pass http://localhost:3000;
    }
}

nginxは単なるリバースプロキシです。ここに関しては長くなるし、いろんなところが書いているので省略。

これで外部からも繋げるようになったのを確認できます。

本当はunicornも使ってパフォーマンスを上げたかったのですが、

途中でrubyのバージョンが古いこと絡みによる依存問題が出たのでこれめんどくさい奴だと思ってとりあえず切り捨てました。
rbenvとかいうrubyのバージョンを管理できるものもあるらしいので、次回があればちゃんとやります。


5日目 →

centos7+nginx+php-fpm+php7な新しい感じの環境を構築

記事の書き方をhtmlからMarkdownにしました。これで今までより捗るはず。真面目に今回はプログを滞りなく技術的なことを書いていきたい。何度目の正直だ。

ということで、いきなりですが技術系おなじみのadvent calendarもどきをやります。

目標としては12/1から12/25です。25記事ということです。

・・・無理そう。

なので、多分飽きたらやめるかもしれませんし辞めないかもしれません。

基本的に書くことはIT系の雑多です。

多分Wordpressとかサーバー構築系のWeb系から、アプリとかjavascriptまで。

あと、計画なんてありません。基本即興です。この一年で生かしたことをひねり出して書く。そんな感じです。

もはや技術関係ないものも出てくるかもしれませんし、まあとりあえず10記事続けばいいな…

目的としてMarkdownを覚えるということもある。
ということでここから開始。

potproject.net Advent Calendar 20161日目の記事です。


11/28くらいにこのブログは別のサーバに移管しました。

使っているところとしては
お名前.com VPSメモリ2GBプランです。

ここが12カ月一括なら税別1100円で、CPUも早いとか聞くのでコスパはかなりいいかなと。

せっかく借りたんだから環境も新しいほうがいい!さらに早い方がいい!

ということでApacheから脱却してこのタイトル通りの環境となります。

現在このサイトもこれで動いています。

最初の環境としては、centos7の最小設定をインストール、ネットワークに接続したところから開始。

基本的にroot権限前提です。

epelとremiリポジトリのインストール

まず、php7を入れる為にはepelリポジトリ及びremiリポジトリが必要です。
remiはともかく、epelリポジトリに関しては多分開発するうえでかなり使用すると思うので入れてて損はないです。

yum install epel-release

これでepelリポジトリが入ります。基本的にこれを使うためには、–enablerepo=epelを使用します。

remiリポジトリはyumコマンドではなく、このサイトからrpmをダウンロードします。

コマンドは載せますが、リンク切れや古くなったりとかもあり得るので、公式サイトから探して落としてきた方が無難です。

wgetも何か最小環境だと入ってなかったのでyum install wgetとかで入れましょう。

最小構成だとgitなんかも入ってなかったりするよね。
この辺は近年だと必須レベルだよなあ。

wget http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
rpm -Uvh remi-release-7.rpm

PHP7のインストール

php7をインストールしたい時には、remi-php70を使用します。

巻末がそのままバージョンなのでremi-php56でv5.6、remi-php71でv7.1なんかもインストールできます。

7.1ももうじき正式リリースされそうです。正式リリースになったら7.1でインストールするのもいいかも。
一応本番として動かすので7.1ではなく7.0を使ってました。チキンです。

yum install --enablerepo=remi,remi-php70 php php-devel php-mbstring php-pdo php-gd php-xml php-fpm php-mysql

インストールできたら、php -vで確認しよう。こんな感じに表示されていれば正常です。
後ろにいっぱい書いてあるのはパッケージです。php-fpmなど、ほぼ必須で使うだろといった感じのパッケージは入れておきます。

php -v
PHP 7.0.13 (cli) (built: Nov  8 2016 20:16:29) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies

php-fpm設定

/etc/php-fpm.d/www.conf がyumでインストールした時のデフォルト設定ファイルです。

こんな感じに設定。ownerやgroupはユーザにより変わります。

今回はnginxというユーザーを作成しています。

user = nginx
group = nginx
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0666

nginxのインストール

nginxはepelリポジトリに最新版がありましたのでインストール。

yum install --enablerepo=epel nginx

nginx設定

このあたりは本当に多彩過ぎて辛い。

本番はこれにSSL設定までしてるからすげえ長くなった。

Apacheより多くね?って感じです。

いろいろ試行錯誤していい感じの設定を探しましょう。

nginx -t で設定が正しいか確認できます。これで不慮のサーバダウンを防ぎましょう。

/etc/nginx/conf.d/nginx.conf

server{
  port   80;
  servername _;
  root   /var/www/html;
  index  index.html index.htm;

  location ~ \.php$ {
    fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include         fastcgi_params;
  }
}

php-fpm と nginx起動

service nginx start
service php-fpm start
//普通はこっちらしいよ
//systemctl start nginx
//systemctl start php-fpm

後は外部から確認して大体これで表示できねえ!なんでだ!ってなった時には大体SELinuxかfirewalldだと思います。その辺は長くなるのでここまで。

とりあえず動いてるかだけでも確認したいんならcurl http://localhost/ とか打ってなんかいろいろ出たらとりあえずサーバーは稼働している、と自分は良く調べます。

これで 502 Bad Gateway とか出る場合は、大体php-fpmとの連携がうまくいっていません。

権限とかphp-fpmが動いているか確認しましょう。

これが終わっても大体次はmysqlの構築設定の壁が立ちはだかる。先は長い。


次回に続けるときはwordpress+nginxかssl設定の話やります。はい。

2日目→ potproject.net Advent Calendar 2016 2日目

node-http-proxyでPOSTするとError: socket hang upが起きてた問題

実をいうと、これまで、このブログはPOSTを送るだけで落ちてたようです。えっ

まあブログ見る分ならPOSTすることはないだろうしー・・・ということで今まで放置してました。おい
Error: socket hang up
at createHangUpError (http.js:1476:15)
at Socket.socketCloseListener (http.js:1526:23)
at Socket.emit (events.js:95:17)
at TCP.close (net.js:465:12)

このまま毎回POSTするたびに落ちるのはちょっとまずいので、やっと修正しました。

元のコード:

var http = require('http');
var httpProxy = require('http-proxy');

var apacheProxy = new httpProxy.createProxyServer({
    target: {
    host: 'localhost',
    port: 8080
}});

var nodeProxy = new httpProxy.createProxyServer({
    target: {
    host: 'localhost',
    port: 8081
}});

var server = http.createServer(function ( req, res ) {
    if (req.headers.host == 'potproject.net') {
        nodeProxy.web( req, res );
    } else if (req.headers.host == 'blog.potproject.net') {
        apacheProxy.web( req, res );
    } else {
        res.writeHead(404);
        res.end();
    }

});

server.listen(80);

これに追加したコード。

apacheProxy.on('error', function(err, req, res) {
    res.end();
});
nodeProxy.on('error', function(err, req, res) {
    res.end();
});

ただerrorをキャッチするだけで修正なのか?と思いますが、これで正常に動きます。

元々、bodyParser middlewareを使用してPOSTをオブジェクト化し、req.bodyを受け取らないといけないようなのですが、
bodyParserなんて使っていないためreq.bodyがうまく受け取れずにerrorとして処理される・・・って感じらしいです。

参考:Connect ソースコードリーディング(4) – bodyParser
でも、proxyはPOSTを受け取って処理する必要はないし、問題はないのでこのままです。

一応ちゃんとした解決策として。これを解決したいなら、

  • body-parserモジュールを使用する。
  • Expressなどのbody-parserを行ってくれるフレームワークを使用する。
  • といった方法があるようです。

    参考(というかほぼ同じ問題のstackoverflow):socket hang up error with nodejs