この雑な文章は、2015年5月中旬頃に調べていた内容をほじくり返してきたものです。あしからず。
はじめに
何だか当たり前のように聞くようになったNode.jsですが、よくわからないので調べてみました。
ほぼ全く知らない状態から調べてます。ぜひ間違いや補足等あればご指摘よろしくお願いします。
Node.jsってなに?
Node.jsはサーバサイドJavaScriptです。ノンブロッキングI/Oとイベントループという二つのモデルにより、大量の処理に対応できます。
この時点で「サーバサイドJavaScriptってなに?」「ノンブロッキングI/O?イベントループ?」と分からないことだらけなので、まずは四の五の言わず試してみます。
Node.jsでWebサーバを立ててみる
Node.jsのインストールは、anyenvから行います。今回は最新版である0.12.2をインストールしました。
(注意・今現在の最新版は公式を確認してください)
$ anyenv install ndenv
$ ndenv install v0.12.2
$ ndenv global v0.12.2
$ node -v
v0.12.2
それでは、公式サイトにサンプルがあるので、それを実行してWebサーバを立てたいと思います。
まずexample.js
を作成して、以下のコードを書きます。
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
そして、以下のコマンドで実行します。
$ node example.js
Server running at http://127.0.0.1:1337/
たったこれだけでWebサーバが立ち上がります。
http://127.0.0.1:1337
にアクセスすると、Hello Worldが確認できました。
それではコードを見ていきます。
Node.jsでは、モジュールの単位で機能を分割することができます。
モジュールが1つのjsファイルとなります。それぞれのグローバルスコープはそれぞれのjsファイル内で"閉じた"状態になり、exportsオブジェクトを通して外部に渡すことができます。モジュールの参照はrequire関数を使います。読み込んだモジュールのexportsオブジェクトを返してくれます。
なので、初めのvar http = require('http');
では、HTTPモジュールを参照し、HTTPオブジェクトを作成しています。
次に続くcreateServer
はHTTPモジュールのメソッドです。引数として、HTTPリクエストを処理してレスポンスを返すような関数を渡します。
このサンプルでは「リクエストの内容にかかわらず、ステータスコード 200 OKでヘッダにContent-Type: text/plainを返し、レスポンスボディとしてHello Worldを書き出すレスポンスを返す」という無名関数を渡しています。
createServer
メソッドはHTTPサーバオブジェクトを返します。
さらに続くlisten
はHTTPサーバオブジェクトのメソッドで、1337番ポートとループバックアドレス(127.0.0.1)にバインド(結び付け)をして、サーバを開始します。
Node.jsでサーバを立てるメリット
ひとまず「こんな感じでJavaScriptを使ってWebサーバを立てられるんだ!」ということが分かりました。
でも、それだけでは何だかApache/Nginxでも良さそうに思えます。
Node.jsでサーバを立てるメリットは以下のとおりです。
- JavaScriptでサーバが立てられる
- 軽量で効率的に多くの処理を捌ける
- ライブラリが充実している
JavaScriptでサーバが立てられる
Node.jsはサーバサイドJavaScriptです。Google Chromeに搭載されたJavaScriptエンジンV8を、サーバで実行できるようにしたというイメージです。
サーバサイドJavaScriptによって、フロントエンドからサーバサイドまでをJavaScriptのみで記述することができます。
JavaScriptはWeb開発に関わる多くの人が触っている言語なので、新規の学習コストが非常に低くなります。特に、これまでサーバサイドに手を出せなかった人が、サーバサイドまで通貫して行えるようになるというのは、かなり大きな利点です。
また、JavaScriptエンジンであるV8の処理は、他のPHP/Perl/Ruby/Pythonなどのスクリプト言語の処理エンジン(PHPならZend Engine、RubyならYARVなど)よりも高速です。
V8ではJavaScriptをJIT(実行時)コンパイルして機械語に変換されたものを実行するのに対して、他のスクリプト言語では仮想マシン(PHPならZend Engine、RubyならYARVなど)で実行できるようにコンパイルし仮想マシンで実行するためです。
軽量で効率的に多くの処理を捌ける
しかし、実はこれらの特徴はサーバサイドJavaScriptの特徴であって、Node.js特有のものではありません。
例えば、V8をApacheのモジュールとして利用できるようにするv8cgiなどもあります。
Node.jsが注目された理由は「軽量で効率的に多くの処理を捌ける」ことにあります。
これを実現している仕組みが、先ほど触れた「ノンブロッキングI/O」と「イベントループ」です。
ノンブロッキングI/O
JavaScriptの特徴の一つにシングルスレッドがあります。Node.jsもシングルスレッドです。デッドロックなどを意識する必要がない反面、一度に一つの処理しかできないため複数の処理は捌けません。
これを解決するのがノンブロッキングI/Oです。
データの送受信(I/O)の完了を待たず(ブロックせず)に、次の処理を受け付け、並列に実行します。前の処理が終わったら、その結果はコールバックで受け取ります。
例えば、DBにアクセスして検索結果を取得するとき、一般的なWebサーバでは、問い合わせをしてから結果が返ってくるまで呼び出し元の処理を止めておきます。DBへのアクセスや通信などの処理は時間がかかるため、これは遅延の原因になります。
Apacheだと、これをマルチスレッドで解決できますが、「C10K問題(クライアント1万台問題)」にぶちあたります。アクセスするクライアントが一万を超えると、Webサーバのスレッドが増えすぎて、メモリなどのリソースが不足してしまうという問題です。
この問題により、プロセッサの処理能力には余裕があっても、サーバの台数を増やさなければいけない、ということが起こります。
ノンブロッキングI/Oでは、シングルスレッドで複数の処理を非同期に行うことができます。DBに問い合わせをしたら、呼び出し元はその結果を待たずに、次の処理に移ります。DBの結果が出たというコールバックが返ってきたら、その結果を受取ります。シングルスレッドなので、アクセスが増えてもメモリの消費量は少なく済みます。
このように、ノンブロッキングI/Oによって、少ないメモリ消費で多くの処理を行うことができます。
スタバ方式で「C10K問題」を解消 : Webアプリサーバーの常識を覆す「Node.js」 | ITPro
イベントループ
イベントループは、リクエストやコールバックをイベントとして扱い、そのイベントに関する処理が終わった際に次のイベントを処理するように動作することです。
これの対義語はスレッドになります。
例えば、Apacheはスレッドモデル、Nginxはイベントループモデルで動作します。前述のとおり、スレッドモデルの場合はスレッドが増えるほどメモリの使用率が上昇します。イベントループモデルではシングルスレッドなので、メモリの使用率は少なく済みます。
しかし、イベントループモデルにも弱点があります。イベントが終わった後に次のイベント、それが終わってからその次、というようにイベントが進むので、どこかで処理がブロックされると全体のイベントが止まってしまいます。
そこでNode.jsでは、先ほどのノンブロッキングI/Oを強制させるという訳です。
イベントループモデルには、実は他にもPythonのTwisted、RubyのEventMachineなどがありましたが、これらはノンブロッキングI/Oを強制してはいないので、同様の処理を行うスレッドモデルよりも遅くなる場合があります。
ノンブロッキングI/Oとイベントループがあることで、少ないメモリ消費で、複数の処理を非同期に行うことができます。
元々、JavaScriptはシングルスレッドとイベントループという特徴を持っていたので、JavaScriptが採用されたに過ぎません。これが他のサーバサイドJavaScriptとの違いです。
ライブラリが充実している
Node.jsでは、npm(node package manager)と呼ばれるパッケージ管理ツールがあります。
現在時点で15万近くのライブラリパッケージが存在しています。
例えば、Gruntやglupのようなタスクランナー、パッケージ管理ツールであるbower、チャットボット作成フレームワークHubot、PhoneGap、SaSSやLESS、CoffeeScriptなどもNode.jsで動作します。
まとめ
Node.jsはノンブロッキングI/Oとイベントループにより、軽量で効率のよい動作を実現したサーバサイドJavaScriptです。
本質的に優れているのはそのパフォーマンスですが、副次的にフロントエンドとサーバサイドの言語の統一できたり、新規の学習コストを低くしたりといったメリットもあります。
一方、デメリットとしては、以下の様なことも言われるみたいです。
- 並行処理を行わないため、マルチプロセッサ環境を活かすことができない
- JavaScriptはオブジェクト指向言語としては不完全であるため、大規模で複雑なアプリケーションの開発において影響を及ぼす可能性がある
- コードの文法エラーによってサーバが停止してしまう
- イベントを受けるコールバックが多くなり、ソースコードの見通しが悪くなる(いわゆるコールバック地獄)
しかしこれらのデメリットも、AltJSとの併用、例外処理、サーバ監視モジュールの使用によってある程度解決できます。
次回は、Node.jsとよく一緒に使用されるMVCフレームワークExpressや、WebSocketを簡単に扱えるライブラリSocket.ioについても調べてみたいなーと思っています。