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

カイワレの大冒険 Third

技術的なことや他愛もないことをたまに書いてます

流行の技術に乗り遅れたくなくて、NodeとRedisとMongoとPythonとjQuery Mobileを一気に動かしてみた

プログラミング プログラミング-node.js

最初はNodeをやってみたいと思っていただけでした。
勉強会とかでLTとか聞いてると、わりと出てくるNodeの話題。JavaScriptは嫌いじゃないし、いつかやってみたいと思ってたけど、なかなか手を出す気にならなくてやってませんでした。
ただ、やろうと思ったら色々ライブラリとか揃ってきてるみたいだし、なんかテンションあがって、色々動かしてしまったという話し。基本はこんな感じ。

  • Nodeは絶対動かす。ブラウザからアクセスできるようにする
  • 中身は普通のHTMLじゃ面白くないから、jQuery Mobileにしよう
  • ただ、そのままレスポンス返すのも味気ないし、NoSQLから取り出した方が早いんじゃね
  • じゃ、RedisとMongoDB使うか
  • あとNodeでValue取り出すし、勉強ついでにPythonからINSERTしてみるか

ってなこと考えました。ということでやってみた。
ほんとはそれぞれ別にまとめたほうがよいのだろうけど、面倒なので、つらつらと。
あと、試行錯誤してて記憶で書いている部分もあるので、間違いあったらご指摘お願いします。

できたのは、これ。「サンプル

使ってるOSは、CentOS5.5。32bitなのが悔やまれる。特にMongo使うならねぇ。

MongoDBのインストール

MongoDB入れましょう。yumで最新版入るし、あまりMongoは詳しくないので、yumに甘えた。

# vi /etc/yum.repos.d/10gen-mongodb.repo
#こっちが32bit版
[10gen]
name=10gen Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/i686
gpgcheck=0
enable=0

64bitはこっち使いましょう
[10gen]
name=10gen Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64
gpgcheck=0
enable=0

yum install --enablerepo=10gen mongo-10gen mongo-10gen-server

設定ファイルは詳しくないので割愛。勉強しなきゃなぁ。

あとは起動。

# /etc/init.d/mongod start

とりあえず、簡単に入れてみる。

$ mongo
> db.diary.save({mykey:"testvalue"});
> db.diary.find();
{ "_id" : ObjectId("4df1a3b9b0cbf00145016c07"), "mykey" : "testvalue" }

DBに接続しなくても、saveすれば繋がるのがMongoのすごいところ。BSONは読みやすいですな。

redisのインストール

次にredisのインストール。2011/6/10に2.2.9が出て、使ってみたかったので、ソースから入れた。
本当はyumと混ぜない方がよいだろうけど、使いたかったからしょうがない。

cd /usr/local/src
wget http://redis.googlecode.com/files/redis-2.2.9.tar.gz

cd /usr/local/
tar xvzf /usr/local/src/redis-2.2.9.tar.gz
cd redis-2.2.9/
make

ln -s /usr/local/redis-2.2.9/ /usr/local/redis
mkdir -p /usr/local/redis-2.2.9/var

#2.0系と互換性取る
ln -s /usr/local/redis/src/redis-server /usr/local/redis/
ln -s /usr/local/redis/src/redis-cli /usr/local/redis/


#この辺使う実行スクリプト作っておいたほうが楽かも。
/usr/local/redis/redis-server /usr/local/redis/redis.conf

設定ファイルはこだわる。

デーモン化させる
#daemonize no
daemonize yes

タイムアウトは短く。そもそもKVSで遅いとかあったらやだ。
#timeout 300
timeout 15

まぁ、ログはこれぐらいの情報量が妥当かと。
#loglevel verbose
loglevel notice

保存先指定。
#logfile stdout
logfile /var/log/redis.log

ファイルには書きださない。
#save 900 1
#save 300 10
#save 60 10000

CPU負荷高まるし、ネットワークが不安定になったりするので、圧縮はしない。
そんな容量削減するほど使わないし。
#rdbcompression yes
rdbcompression no

どこでもよいけど。
#dir ./
dir /usr/local/redis/var/

これは迷うけど、個人的にはエラーを返してほしい。というか、これスレーブの設定のような気もするから、一台構成ならいらないかも。
#slave-serve-stale-data yes
slave-serve-stale-data no

多いと死ぬので、とりあえず。少ないかなぁ。
# maxclients 128
maxclients 20

設定メモリの上限を超えた場合のキーを消す順序。これは期限が切れそうなものから消すポリシー。
# maxmemory-policy volatile-lru
maxmemory-policy volatile-ttl


今回の用途では絶対2MBも使わない。メモリもったいない。
# maxmemory
maxmemory 2MB

まぁ、CPU下げたいしなぁ。ここはどっちがよいのかまだ分かってない。
#activerehashing yes
activerehashing no

あと、VMは使わない。OOMキラー発動させるかも微妙だし、Redisは発動させないこと推奨してるけど、
まぁ、vm.overcommit_memoryは1にしておく。2でrate調整でもよいと思うけどね。
ここら辺はほかの人のポリシー聞きたいところ。

まぁ、話しは反れましたが、起動。

/etc/init.d/redis start
# ps aux | grep redis

cd /usr/local/redis
./redis-cli
>info

#自動起動させるなら、chkconfigで追加しておきましょう。

やっとNodeのお話

ここまではNoSQLのセットアップの話し。本題のNodeです。
バージョンの管理は、Naveにやらせます。
ソースから入れるとあとで管理が大変なので。

この記事はかなり参考になるかと。
http://d.hatena.ne.jp/bellbind/20110530/1306764093

Nodeまわりはユーザ管理下で管理を行います。

$ cd ~/local/src
$ git clone http://github.com/isaacs/nave.git
$ cd nave
$ ./nave.sh use latest
$ node -v
v0.4.8

これで最新版のNodeが使えるようになる。もちろんこのユーザのみで。

あとは次回ログイン以降も使えるようにprofileに書いておく。シェルにあわせてくだしあ。

vi ~/.bash_profile
#ネットワークに常につながってるなら、これでもいいかもなぁ。
$HOME/local/src/nave/nave.sh use latest

まぁ、無難にいくなら、naveへのパスを張っておく。
alias nave=$HOME/work/nave/nave.sh

NodeからNoSQLへ

NoSQLとNodeでやり取りしてみます。

まずNodeから使えて有名どころのmongooseを使ってみる。
$ npm install -g mongoose
「-g」を付けることで、ローカルに展開されず、ちゃんとnpmの管理下に展開されて、整理しやすくなる。これ大事。

Redisのも入れておきましょう。
$ npm install -g redis

んで、ちゃんと呼べるかパスを確認。
$ node -e require.paths
[ '/home/user/.nave/installed/0.4.8/lib/node',
'/home/user/.node_modules',
'/home/user/.node_libraries',
'/home/user/.nave/installed/0.4.8/lib/node' ]

Nodeのモジュールはここに入ってる模様
$ ls /home/user/.nave/installed/0.4.8/lib/node_modules/
hiredis/ mongoose/ npm/ redis/

追加してあげた
export NODE_PATH=$HOME/.nave/installed/0.4.8/lib/node_modules

こんな感じに変わった
$ node -e require.paths
[ '/home/user/.nave/installed/0.4.8/lib/node_modules',
'/home/user/.node_modules',
'/home/user/.node_libraries',
'/home/user/.nave/installed/0.4.8/lib/node' ]
]

じゃ、Nodeのコード書いてみる

まず、MongoDBからデータ取ってみる。
http://blog.learnboost.com/blog/mongoose/
ほぼパク(ry

var mongoose = require('mongoose/').Mongoose,
    db = mongoose.connect('mongodb://localhost/test'),
    Collection = mongoose.noSchema('test',db); // collection name

Collection.find({}).each(function(doc){
  // do something
  console.dir(doc);
});

まぁ、これで動くほど世の中甘くない。

The latest version doesn't require you to use ('mongoose').Mongoose. Just
use require('mongoose')
That should fix your problem!

: )
http://groups.google.com/group/mongoose-orm/browse_thread/thread/5faa75e81da1a8f2

とか言われたので、直したけど、大事なことに気づいた。

参考にし(ぱくっ‥)たサイトを見るとこう書いてある…

Update: Mongoose 1.0 has been released, and we recommend going to our dedicated website for the most up-to-date information and code

んで、見たらスキーマ必要になったり、めちゃ手間増えてて、ちょっと書いたけど、めんどくさくなった…
とりあえず、バージョンアップによって色々変わるのがNodeの世界だと思い始めた瞬間。

ってことでネイティブの入れてみた。

$ npm install -g mongodb

$ cd /home/masuda/.nave/installed/0.4.8/lib/node_modules/mongodb
$ ls
$ ./install.sh

んで、コード書いてみた。

var http = require('http');
var mongodb = require('mongodb');

http.createServer(function(req, res) {

    var server = new mongodb.Server("127.0.0.1", 27017, {});
    new mongodb.Db('test', server, {}).open(function (error, client) {
        if (error) throw error;
        var collection = new mongodb.Collection(client, 'testcol');
        collection.findOne({mykey:'testvalue'}, function(err, docs) {
            res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
            res.end(docs.mykey);←ここが全然いうこときかない
        });
    });
}).listen(8000, 'localhost');

orz。findOneのコールバックでdocsもらってきてんだけど、そのままBSONで返せても、特定のバリューだけ表示したいということが、これだけではできない。もちろん、JSONと大して変わらないし、方法あるんだろうけど、別に実験コンテンツだし、めんどくさくなってやめた。

一番しっくりきたのはこれかな。「mongoskin」。

$ npm install -g mongoskin

これでやっとうまくいった
var http = require('http'),
    mongo = require('mongoskin');

http.createServer(function(req, res) {
    mongo.db('localhost:27017/test').collection('diary').findOne({key: 'testkey'},function(err, items){
      if (err) {
          throw err;
      }
        res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
        res.end(items.content);
    });
}).listen(8000, 'localhost');

こんな感じで書けるライブラリ求めてました。コールバック増えそうだから、本格的にやるとそこの対策は必要だろうな。ナムラさんのLTでも、そんなはなしあったような気がするし。

Mongoに比べると、Redisは楽だったかも。

var http = require('http'),
    redis = require("redis"),
    key = 'mykey';

client = redis.createClient();

http.createServer(function(req, res) {
    client.get(key, function (err, reply) {
        if (err) {
            throw err;
        }
        res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
        res.end(reply.toString());
   });
}).listen(8000, 'localhost');

これでまずローカルホストでレスポンスがちゃんと返ってくることを確認。
「curl -G」でも、lynxでもw3mでも、適当に試してくだされ。

あとは外向けにapacheモジュール使って、バーチャルホストを立てておく。


     ServerName nodejs.masudak.net
     ProxyPass / http://127.0.0.1:8000/
     ProxyPassReverse / http://127.0.0.1:8000/
     ErrorLog logs/nodejs.masudak.net-error_log
     CustomLog logs/nodejs.masudak.net-access_log common

まぁ、もうちょっとログのフォーマットとか、やれることちゃんとやったほうがよいかもね。あとでなんかあったとき追えなくなっちゃうし。あとは、CNAMEつけてあげるなり、DNSまわりの設定して、done。

※とりあえず、ポート変えたり、いくつか手を加えてます。

jQuery Mobile使ってみた

んで、valueに入れる値が単なるHTMLだと、あまり面白くないので、せっかくだから、jQuery Mobile使ってみた。

記事としては西畑さん(@)のが読みやすかったかなぁ。
http://ascii.jp/elem/000/000/607/607169/

ドキュメント宣言からして適当だし(たいして調べてない…)、挙動だけ知りたかったので、手抜きでごめんなさい。
一応気づいたこと。

  • 正しいドキュメント宣言と、html要素の書き方は… 宗教っぽさまだ出せてない。
  • jQueryの類はCDNから呼べるのである意味楽。link要素とscript要素で解決できるから。
  • ただ名前解決の量が増えるので、パフォーマンスを考えたりするなら、ローカルにダウンロードさせて、それを呼ぶべきかな。
  • header, footerを分離させて、includeさせたりしたらいい感じかも。できるかはわからない。
  • テーマもいろいろ使いこなせるとかなり楽しいかも。
  • 「data-theme="b"」って感じでテーマを指定できる。
  • h3とかいっぱい作っちゃったけど、これだめじゃね…
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>jquery mobile test</title> 
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.css" />
<script src="http://code.jquery.com/jquery-1.5.2.min.js"></script>
<script src="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.js"></script>
</head>
<body>


<div data-role="page" id="index" data-theme="b">
<div data-role="header">
 <h1>実験コンテンツ</h1>
</div>
  <div data-role="content">
  <div data-role="content">
    <ul data-role="listview">
    <li data-role="list-divider">おしながき</li>
      <li><a href="#page2">

          <h3>スライドショー</h3>
      </a></li>
      <li><a href="#">
          <h3>ダミー</h3>
      </a></li>
      <li><a href="#">
          <h3>ダミー2</h3>

      </a></li>
      <li><a href="#">
          <h3>ダミー3</h3>
      </a></li>
    </ul>
  </div><!-- end of content -->

  <a href="#page2" data-role="button" data-transition="slide" data-icon="arrow-r" data-iconpos="right">画面遷移</a>

  </div><!-- end of content -->
  <div data-role="footer">
    <h4>&copy; 2011 masudaK.net</h4>
  </div>
</div>


<!-- page2 多分こことheader, footer分離させるべき -->
<div data-role="page" id="page2" data-theme="b">
  <div data-role="content">

  <h3>2枚目だったりする。</h3>
  </div>

  <a href="#index" data-role="button" data-transition="slide" data-icon="arrow-l" data-iconpos="left">戻る</a>  
  <div data-role="footer">
    <h4>&copy; 2011 masudaK.net</h4>
  </div>
</div>

</div>

</body>
</html>

こんな感じで、適当に書いてみたけど、一度ちゃんと書いたらUIとかたのしそーだなー。

PythonからINSERT

んで、入れる先はNoSQL、サーバはNode、とできたので、今度はValueを入れるわけですが、Nodeから入れたらあんま進展ないので、Pythonから入れてみる。

Python自体のバージョンの管理については、「Pythonの開発環境をよーく考えた上で構築する その1」の記事を読んでください。

んで、モジュールの管理も大事なので、PIPを使います。
ちゃんとモジュール管理できるようにしておきましょう。ソースから入れると、色んなバージョンのNoSQLクライアントとかが増えていくので。

じゃ、INSERTするスクリプト書いてみましょう。まずはMongo。

 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 import pymongo
 key = 'testkey'

 f = open('./index.html', 'r')
 content =  f.read()

 conn = pymongo.Connection()
 db = conn['test']
 coll = db['col']
 coll.insert({key: content})

 f.close()
 conn.disconnect()

open()でhtmlを読み込んで、pymongoを使ってINSERTするだけ。

次に、Redis。

 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 import redis
 key = 'testkey'

 f = open('./index.html', 'r')
 content =  f.read()

 r = redis.Redis(host='localhost', port=6379, db=0)
 r.set(key, content)
 f.close()

読み込んだり、サーバとして立ち上げるのに比べると、単に書き出すだけですから楽ですね。

総仕上げ

これで、RedisとMongoにvalueが入り、Nodeが返すことができるようになったので、あとはJSで両方を適当に返すように書き変えるだけ。
コードはこんな感じ。最初にリンクをはったサンプルも大体こんなコードで動いています。表示した時間(hour)の偶奇によって、redisとMongoを分けてます。キーとかもほんとはハッシュ化したほうがよいんだろうけどね。

var http = require('http'),
    mongo = require('mongoskin'),
    redis = require("redis"),
    key = 'sample_key',
    httphost = 'localhost',
    httpport = 1111,
    mongohost = 'localhost:127017/database',
    mongocollection = 'col',
    d = new Date(),
    h = d.getHours(),
    mongohtml = null,
    redishtml = null,
    reponsehtml = null;

http.createServer(function(req, res) {

    if( h % 2 == 0 ){
        //redis
        var responsehtml = _connectedRedis();
    }else{
        //mongo
        var responsehtml = _connectedMongo(mongohost,mongocollection,key);
    }
    //response content
    res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
    res.end(responsehtml);

}).listen(httpport, httphost);


function _connectedMongo(_mongohost, _mongocollection, _key ){
    mongo.db(_mongohost).collection(_mongocollection).findOne({key: _key}, function(err, items){
        if( err ){
            throw err;
        }
        mongohtml = items.content;
    });
    return mongohtml;
}

function _connectedRedis(){
    client = redis.createClient();
    client.get( key, function(err, reply) {
        if( err ){
            throw err;
        }
        redishtml = reply.toString();
    });
    return redishtml;
}

まぁ、仕組みはもうよいとして、書いてみた感想。

  • 思った以上に変数の扱いが面倒
  • Nodeはコールバックで色々処理することが多いんだけど、そこで得た値を変数に格納する場合、ローカル変数なので、使いたいとこで使えない場合が多い
  • この辺、グローバル変数使わず、どうするかが気になった
  • エラー処理がなんとも言えず
  • もうちょっとちゃんとしたエラー処理をしないと、デバッグ全然できない
  • 今回Firebugも使わず書いてみたんだけど、エラー処理をもっとしっかり書かないと、多分何かが原因で動かなくなるときとか、ちょーありそう
  • 今回のサンプルでもあったらごめんなさい

とりあえず、そんな感じでした。

常にサーバとして起動させておく

NodeでNoSQLから読み込んで、サーバとして立ち上げるのは上述した限りだけど、それだとsshのセッションを切ったら、子プロセスとして立ち上げたサーバがkillされちゃう。ので、常に起動させておけるようにしておきましょう。

nohupとか色々あると思うけど、npmのモジュールにforeverというのがあるので、それを使ってみます。
http://blog.nodejitsu.com/keep-a-nodejs-server-up-with-forever

これを使うと、デーモン化してくれます。

$ npm install -g forever
$ forever start sample.js

$ ps aux | grep js

まぁ、こんな感じでとりあえず動いてます。初めて書いたから、もしかしたらなんかあって止まったり、何かあるかもだけど。
そしたらごめんなさい。

終わりに

まぁ、一通りそれぞれ動かしてみたけど、どれも新しい技術っぽくて、数年後はもっと使いやすくなってるんだろうなぁという印象受けました。ここまで書いた内容を試行錯誤してる間にも、バージョンアップしてるものも色々あったし。

やはりバージョンアップによって使いやすくなったり使いづらくなったりすることもあれば、一気に書き方変わってたりして、なんかすごーく新鮮だったので、そのうち安定してくれること望みますです。以下、感想をリスト化。

  • Nodeは短いコードでサーバになるし、非同期はやっぱ惹かれる
  • モジュールも増えてきて、NoSQLとの相性はよさげ
  • Redisのレスポンスはやっぱよい
  • Pythonも読みやすくてよいね

といういい面はあるけど…

  • やっぱバージョンにすげー振り回される
  • ユーザのローカル環境に開発環境作る文化に慣れない
  • NodeのMongoライブラリは、昔のほうが使いやすかったっぽい。今のは心が折れる。
  • jQuery Mobileはやっぱシンプルでいいよ。ただ、コード量減らす工夫したいかな。とりあえず楽しい

とは言いつつも、結局、

  • 新しいもの触れるときの楽しさって独特だけど、やっぱ悪くない。新しい物好きにはたまらない
  • 数年後がたのしみだなー
  • 組み込み方とか用途をちゃんと覚えておいた方がよさそうかもなー

という感じでした。
ほぼ、備忘録という名のメモ書きですが、少しでも参考になる部分があれば幸いです。


P.S.
サンプル含め、何かあればぜひ@まででもよいですし、この記事のコメントでもよいので是非是非。
こういう風に書くと楽だよとか、もっといいものあるよとか、それだと問題あるよとか。

宜しくお願いしますです。
コード書くの上手くなりたいなぁ。ほんとさらしたくないw