読者です 読者をやめる 読者になる 読者になる

node.jsでmemcachedを使う

node.jsでmemcachedを使う方法を書いていきます。

まずはインストール。

$ npm install memcache

実際の使い方は

var host = "localhost";
var port = "11211";

var memcached = require("memcache");
var client    = new memcached.Client( port, host );
client.connect();

client.set(key, value, function(err, result){
    if ( err ) { /* エラー処理 */ }

}, expire);
 
client.get(key, function(err, result){
    if ( err ) { /* エラー処理 */ }

    console.log( result );
});

client.delete(key, function(err, result){
    if ( err ) { /* エラー処理 */ }

});
 
client.close();

使い方は、clientのインスタンスを作ったらconnectでmemcachedに接続。

あとはキャッシュを作るsetだったり、取得するgetだったり、削除するdeleteだったりを使って操作していけばokです。

node.jsでpostgresqlを使う

node.jsでpostgresqlを使う方法を書いていきます。

使うモジュールはpgです。

まずはインストール。

$ npm install pg

実際の使い方は

var pg  = require("pg");
var dns = "tcp://username:@localhost:5432/dbname";

pg.connect(dns, function(err, client) {
    if ( err ) { /* エラー処理 */ }

    client.query("select * from table_t where id = $1, [1] function(err, result) {
        if ( err ) { /* エラー処理 */ }

        var rows = result.rows; // 検索結果の行
    });
});

使い方は簡単で、pgのインスタンスを作ったらconnectでDBに接続。
そしてそのコールバックのの引数であるclientに対してクエリを発行すればいいだけです。

selectのみを例にしましたけど、

client.query("insert into ~
client.query("update ~

sqlでしたらなんでもokです。

nginx + proxy cache

nginxでプロキシキャッシュの方法を書きます。

レスポンスのボディを任意のディレクトリ内に保存し、次回以降のアクセスから保存したデータを返すという仕組みです。
これを使うと負荷の面でだいぶ助けられます。


では実際の使い方を示します。

http {
    proxy_cache_path /var/www/nginx/cache/sample levels=1 keys_zone=cache-sample:64m max_size=5m inactive=7d;
    proxy_temp_path  /var/www/nginx/tmp;

    server {
        location / {
            if ($request_method = POST) {
                alias /var/www/html;
                break;
            }

            proxy_cache cache-sample;
            proxy_cache_valid  200 1d;
            proxy_cache_key $scheme://$host$request_uri;

            alias /var/www/html;
        }
    }
}

細かく説明しますと

proxy_cache_path /var/www/nginx/cache/sample levels=1 keys_zone=cache-sample:4m max_size=5m inactive=7d;
proxy_temp_path  /var/www/nginx/tmp;

ここではキャッシュの設定をしています。
server{}の外に書かないといけません。

proxy_cache_path [キャッシュするディレクトリ] levels=[保存するサブディレクトリの階層] keys_zone=[キャッシュのゾーン]:[キャッシュ格納メモリ数] max_size=[ゾーン内に保存できるキャッシュの最大値] inactive=[キャッシュを保持する時間];

proxy_cache_pathはこのように設定します。
proxy_cache_pathは複数作ることができますので、ディレクトリやゾーン単位で作りましょう。

proxy_temp_pathに関してはそんなに意識しなくて大丈夫です。
名前の通り一時的なものをおくので、適当なディレクトリを用意しましょう。


if ($request_method = POST) {}

この部分はメソッドPOSTの場合はキャッシュしない、返さないようにしています。
アプリの設計しだいですけど、POSTでキャッシュを返すことはまずないと思います。


proxy_cache cache-sample;
proxy_cache_valid  200 1d;
proxy_cache_key $scheme://$host$request_uri;

ここが実際にキャッシュを返す設定をしている部分です。

proxy_cache [キャッシュゾーン指定];
proxy_cache_valid  [ステータスコード] [キャッシュさせる時間];
proxy_cache_key [キャッシュのキー];

という風に設定します。
proxy_cacheには上の方で、proxy_cache_pathで設定したkeys_zoneを書きます。
proxy_cache_keyなんですが、$scheme://$host$request_uriで引数付きのurlをキーにします。
$scheme://$host$uriとすれば引数なしです。
引数でレスポンスを変わる、変わらないを判断して設定を変えましょう。

あとはnginxをリスタートすればキャッシュされるようになると思います。


試しに、

$ vim /var/www/html/index.html
test

として、http://localhost/にアクセスしてみてください。
次に

$ vim /var/www/html/index.html
sample

として先ほどと同じようにアクセスして、testと出ていれば成功です。


今度はキャッシュの削除方法を説明します。
といってもこれは簡単で、キャッシュされているディレクトリを削除すればいいだけです。
今回の例でいうと

$ rm -fr /var/www/nginx/cache/sample

これだけです。

Apacheはいらず、abはいる

ベンチマークとるのにabコマンドというものが存在します。

$ ab -n 100 -c 20 http://kojinbaibai.bundlebox.jp/

こんな感じで、ページ単位のベンチマークをとるもので、webサーバーのチェックなどをする時に重宝しています。

ただ一つ問題がありまして、このabコマンドはApacheのパッケージにしか入っていないということです。
もちろんApacheを使っている人は問題ないんですが、nginxなどしか使っていない人にとっては「わざわざApacheは入れたくないよな」と思っちゃいますよね。

ということで、abコマンドのみのインストール方法を書いておきます。


実際の手順は、

# wget http://ftp.tsukuba.wide.ad.jp/software/apache//httpd/httpd-2.4.4.tar.gz
# tar zxvf httpd-2.4.4.tar.gz
# cd httpd-2.4.4
# ./configure --enable-static-ab

ここで

configure: error: APR not found.  Please read the documentation.

というエラーが。

Apache 2.4 で configure 実行時に「configure: error: APR not found」と怒られる - ablog

調べてみるとバージョン2.4以降おこるエラーらしいです。
対処法も書いてあるんですが、abコマンド入れるためにわざわざそんなことしてられません。
バージョン落としてインストールします。

# wget http://ftp.tsukuba.wide.ad.jp/software/apache//httpd/httpd-2.2.24.tar.gz
# tar zxvf httpd-2.2.24.tar.gz
# ./configure --enable-static-ab
# make

ここまで実行すると./support/abというファイルが作成されています。
依存関係をチェックした上で/usr/local/bin以下に移動させましょう。

# ldd ./support/ab
# mv ./support/ab /usr/local/bin

これで完了です。
試しに

$ ab -n 100 -c 20 http://kojinbaibai.bundlebox.jp/

と打ってみてエラーが出てなければ完了です。

nginxでブラウザ判別

nginxでブラウザを判別する方法を書きます。

例としてIE8以下でアクセスした場合、別URLにリダイレクトさせる設定を示します。

modern_browser  unlisted;
ancient_browser "MSIE 4.0" "MSIE 5.0" "MSIE 5.5" "MSIE 6.0" "MSIE 7.0" "MSIE 8.0";

location / {
    if ( $ancient_browser ) {
        rewrite ^ http://kojinbaibai.bundlebox.jp/nosupport/ last;
        break;
    }

    ....
}    

これだけです。

ancient_browser

に特殊処理するブラウザを入れます。

if ( $ancient_browser ) {}

とすることで、そのブラウザでアクセスしてきた場合の処理を分岐できます。

試しに、http://kojinbaibai.bundlebox.jp/にIE8以下でアクセスしてみてください。
/nosupport/以下にリダイレクトされると思います。


例見てもらえると分かるかもしてませんが、個人売買掲示板はIE8以下にはサービスを対応させていません。
HTML5が当たり前になる昨今、IE8以下の人はもうバージョンアップまたは他のブラウザにしませんか?
と思ったけど、
日本のバージョン別ブラウザシェアグラフ (StatCounter Global Statsより)
ここ見る限り、IE8のシェアって結構あるんですね。

ハイパフォーマンスHTTPサーバ Nginx入門

ハイパフォーマンスHTTPサーバ Nginx入門

サブドメイン間のセッション共有

複数のサブドメイン間でセッションを共有させる方法を紹介します。
1つのアカウントで複数のアプリケーションを使えるようにしたいときなどに必須となってきます。

たびたび例に出して申し訳ないんですが、私が作っているプロジェクトBundleBoxもサブドメイン間でセッションを共有させるように作られています。

http://account.bundlebox.jp/
http://kojinbaibai.bundlebox.jp/

これらはドメインは別ですが、同一のセッションを共有しています。

やり方は非常に簡単です。
account.bundlebox.jpドメインにセッション情報を保存するのではなく、bundlebox.jpドメインに保存するように設定するだけです。

perlのCatalystでの例を示します。
共有したいアプリケーションのconfigファイルでセッション設定を以下のようにしてください。

session => {
    cookie_name    => "BundleBox",
    cookie_domain  => "bundlebox.jp",
    storage        => "/var/www/bundlebox/session/session_data",
    namespace      => "production",
},

もちろんそれぞれの環境に合わせてくださいね。

$user_id = $c->session->{user_id};

こんな感じでログイン情報を共有できます。

ログ監視

webアプリケーションを実際に運用していく上で必須なのがログの監視です。
エラーが出ているのを知らずに放っておくなんて危険です。

アプリ内で400番、500番台のエラーが出ていた場合にメールでお知らせしてくれるスクリプトを紹介します。
専用のツールもあるんですが今回は自作します。

実際のソースです。

#!/usr/bin/perl

use strict;
use warnings;
use utf8;

use FindBin;
use lib "$FindBin::Bin/../extlib/lib/perl5";
use local::lib "$FindBin::Bin/../extlib";

use Email::Sender::Simple 'try_to_sendmail';
use Email::MIME;

use File::Tail;
use Time::TAI64 qw/:tai64n/;
use Carp;
use Data::Dumper;

my $config = do "$FindBin::Bin/config.pl";
my $match  = $config->{match};
my $mail   = $config->{mail};

my $timeout;
my @files = map { File::Tail->new( name => $_ ) } keys %$match;

MAIN:
while (1) {
    my ( $nfound, $timeleft, @pending ) =
            File::Tail::select( undef, undef, undef, $timeout, @files );

    next MAIN unless $nfound;

    my @elogs;
    my @title_signs;
PENDING:
    foreach my $p (@pending) {
        my @reads_org = $p->read;
        next PENDING if scalar @reads_org <= 0;

        my @reads;
        my @include = @{ $match->{ $p->{input} }->{include} };
        my @exclude = @{ $match->{ $p->{input} }->{exclude} };

        for my $regexp ( @include ) {
            push @reads, (grep /$regexp/, @reads_org);
        }
        for my $regexp ( @exclude ) {
            @reads = grep !/$regexp/, @reads;
        }

        next PENDING if scalar @reads <= 0;

        if ( $match->{ $p->{input} }->{tai64} ) {
            my @reads_buff;
            for (@reads) {
                my($tai, $mes) = split(' ', $_, 2);
                push @reads_buff, sprintf("%s %s",tai64nlocal($tai), $mes);
            }
            @reads = @reads_buff;
        }

        # title_sign
        foreach (@reads) {
            if ($_ =~ /\" ([5|4]\d{2}) /) {
                push @title_signs, $1;
            }
            if ($_ =~ / \[(error|warn|fatal)\] /) {
                push @title_signs, 'E';
            }
        }

        push @elogs, {
            name => $p->{input},
            log  => join('', @reads),
        }
    }

    next MAIN unless @elogs;

    # 簡単に整形
    my $body_string = '';
    foreach (@elogs) {
        $body_string .= "■" . $_->{name} . "\n";
        $body_string .= $_->{log} . "\n\n";
    }

    my $mail_subject = $mail->{subject}
                        . ( (scalar(@title_signs) >= 1) ? " ... ".join(" ",@title_signs) : "" );

    my $email = Email::MIME->create(
      header_str => [
        From    => $mail->{from},
        To      => $mail->{to},
        Subject => $mail_subject,
      ],
      attributes => {
        content_type => 'text/plain',
        charset      => 'UTF-8',
        encoding     => 'base64',
      },
      body_str  => $body_string,
    );

    if ( try_to_sendmail($email) ) {
        carp "[info] try_to_sendmail success.";
    }
    else {
        carp "[error] try_to_sendmail error!";
    }
}

1;

ざっくりと説明すると
1.設定ファイルとしてconfig.plを取得
2.File::Tailを使い、ファイルを後ろから取得
3.ファイルの中身にconfig.plで設定した条件に一致したらメールを送信
こういう構成になってます。

config.pl 設定ファイルを作っていきます。
下記は1例です。

# vim config.pl
{
    match => {
        "/var/log/nginx/kojinbaibai.bundlebox.jp/access.log" => {
            include  => [
                qr/" 5\d{2} /,
            ],
            exclude  => [
            ],
        },
        "/var/log/nginx/account.bundlebox.jp/access.log" => {
            include  => [
                qr/" 5\d{2} /,
            ],
            exclude  => [
            ],
        }
    },
    mail => {
        from    => 'sample@sample.com',
        to      => 'sample@sample.com',
        subject => "error mail.",

	}
};

これを設定して自分の環境にカスタマイズしてください。

監視するログの条件を設定します。

{
    match = {
        "(監視するログファイル)" => {
            include  => [
                (監視対象にするパターンの正規表現), (...), (...)
            ],
            exclude  => [
                (includeの中から対象外にするパターンの正規表現), (...), (...)
            ],
        },
    }
};

今回書いた例で言うと
/var/log/nginx/kojinbaibai.bundlebox.jp/access.log
/var/log/nginx/account.bundlebox.jp/access.log
この2つのログファイルの500番台のエラーを検知するように設定しています。
これを自分の環境に合わせて、監視したい条件を書きます。
最初にエラーを知らせてくれると書きましたが、正規表現なのでなんでもありです。

次にメールの設定。
自分のメールアドレスに送信するようにします。

{
    mail =>{
        from => (メールの送信元アドレス),
        to => (メールの送信先アドレス),
        subject => (メールのタイトル),
    }
}

これで設定完了です。
実際動かしてみて問題ないようならデーモン化して終わりです。
エラーが出る度にメールが飛んでくるはずです。

デーモン化するのは下記を参考にしてください。
daemontoolsによるwebアプリケーション管理 - 城好きwebプログラマーの生態メモ - 仕事編