Node.jsとは何か、開発者ライアン・ダール氏が語る(前編)~ノンブロッキングとはどういうことか?
いま注目されているサーバサイドJavaScriptの火付け役となったNode.js。その開発者であるライアン・ダール(Ryan Dahl)氏自身がNode.jsを紹介した講演「Introduction to Node.js with Ryan Dahl 」のビデオが公開されています。
この講演は、サンフランシスコのPHP開発者の集まりであるThe SF PHP Meetup Groupが2月にダール氏を招待して行われたもの。
そのため、Node.jsを知らないデベロッパーに向けて、Node.jsがどのような特徴を持つプログラミング言語なのか、分かりやすく解説しています。内容を紹介しましょう。
Node.jsとPHPとの本質的な違いとは何か
Node.jsを触ってみよう。今日は特にスライドは用意してなくて、タイプしてどんなものかを動かしながら紹介していくつもりだ(注:ダール氏はここで最初に「ノードジェーエス」(Node.js)と言った後は、ずっと「ノード」(Node)で通しています)。
みなさんはダウンロード、してコンパイルすることにあまり慣れていないと思うけれどもNodeはそういったものの1つだ。まだ開発中のものなので、自分でソースからコンパイルしなければならない。
Nodeをダウンロードして、configure、make、make installする。今日はやらないけれども。
NodeにGUIはなく、Pythonのようにコマンドラインで操作する。こうしてプロンプトが出て、ここにJavaScriptを記述できる。
Nodeは、グーグルの頭のいい人たちが開発した、「V8」というChromeにも使われているJavaScriptエンジンの上に構築されている。
このV8の上に多くのライブラリを載せたものがNodeで、ネットワーク処理を得意にしている。もちろん一般的な処理もできるが、ネットワーク処理を適切に行うことに焦点を当てている。
そのため、プログラミングはちょっと変わっている。
まずはその前に簡単な例を出してみよう。足し算をする例を紹介しよう。add関数を定義してadd(1,2)を実行すると、ちゃんと答えが出る。
Nodeにはグローバル変数がある。ブラウザ上でJavaScripを実行するとウィンドウでのグローバル変数があるように、このグローバル変数はサイトに結びついている。
Nodeはブラウザに結びついていないので(ブラウザ上の)ウィンドウオブジェクトはなくて、それがプロセスになっている。このプロセスがグローバル変数になっていて、それがある意味でNodeの中心的存在となっている。
ではエディタを起動して、hello-world.jsを作ってみよう。setTimeoutで、1つ目の引数にコールバック関数、2つ目の引数に待ち時間を設定する。このプログラムがどう動くか想像できるかい?
コマンドラインから実行してみよう。まず“hello”と表示され、2秒待って“world”と表示された。
ここで1つ言えるのは、ブラウザ上でJavaScriptのプログラミングに慣れた人には、Nodeのプログラミングも分かりやすいだろうということ。
同じようなプログラミングをPHPでも作ってみよう。“hello”をプリントして、2秒スリープ、続いて“world”をプリントする(下記の画面の上の3行)。
この2つのプログラムの本質的な違いが分かるだろうか? (会場から「asynchronous」という声が)
PHPの方はsleepし、Nodeの方はtimeoutしている。PHPはsleepで止まってしまう、ここが本質的な違いだ。
Nodeは決して止まらないしスリープしない。Mutex(排他的)なロックも、実行の停止(Halting Execution)もない。アイドルになるだけだ。
この“hello”と“world”の2秒間のtimeoutはビジーループを回しているのではなく、アイドルになっている。CPU利用率はゼロになり、OSはほかの仕事をして、timeoutの時間がくるとプログラムの実行へと戻ってくる。
このサンプルをもう少し変更してみよう。hello-world2.jsを作る。
さっきのsetTimeoutをsetIntervalに変えよう。すると、“hello”を表示したあとで2秒ごとに“world”“world”“world”と連続で表示されるだろう。
実行してみよう。たしかにそうなる。
いま、会場からいい質問があった。最初のスクリプトは実行後終了してプロンプトに戻った。しかし2番目のスクリプトは終了せず動作し続け、プロンプトには戻らない。なぜか?
これはNodeの大事な一面を示している。
これはコールバックがネットワークコネクションなりインターバルタイマーなりの呼び出しをずっと待っていことができる、ということなのだ。
並列処理を実際に試してみる
ここまではJavaScriptをコンソールから試してきた。次は、Webサーバの例を見てみよう。Hello WorldのHTTPバージョンだ。
httpモジュールをロードしてHTTPサーバを作る。ここにコールバックを入れると、リクエストがくるたびにそれが実行される。
このサーバを「s」として、これにポートをバインドする。
ここに200のOKステータスをヘッダで返すようにする。そして“hello world”をボディとして返すようにする。
ではこれを実行してみよう。まだ何も起こらない。ポートをlistenしている状態だ。
curlでローカルホストを読んでみる。すると“hello world”が戻ってきた。うまくいった。
Webブラウザからも“hello world”がちゃんと帰ってくる。エクセレント。
ヘッダをチェックしてみよう。こうなっている。
content-typeは「text/plain」になっていて、それ以外に2つのヘッダがある。Connectionが「keep-alive」になっているのはなぜだろう? モダンなHTTPではパーシステントなコネクションができるようになっているからだ。
ではTransfer-Encodingが「chunked」になっているのは? そう、ストリーミングだ。
さっきのスクリプトを書き換えて、途中にsetTimeoutを入れてみよう。何が起こるだろうか?
動かしてみよう。“hello”と表示され、2秒あいてから“world”が表示される。
これは1つのリクエストに対して、“hello”から“world”まで1つのボディとして返している。これがTransfer-Encodingが「chunked」になっているということだ。
例えば、HTTPのヘッダを返した段階では、ボディがどれくらいの大きさなのか分からない。MySQLからの何百件もの検索結果が含まれているのかもしれない。
そういう場合、サーバ側ですべての結果をバッファリングするような複雑なことはしたくない。MySQLが結果を返したら、それをそのままクライアントに渡すほうがいい。HTTP 1.1ではコネクションを維持したまま複数のレスポンスを返せる。
細かい話はここまでにしておこう。
言いたかったのは、Nodeでは“hello”から“world”のあいだの2秒間もサーバは立ち上がっていて、スリープしているのではなくアイドルになっていて、ほかのコネクションを扱うことができるようになっている、ということだ。
例えば、Apache Bench(abコマンド)で、いまのサーバに対して100回のリクエストを送ってみよう。もしも頭の悪いサーバならば、2秒後に1つ目のリクエストが終わり、4秒後に2つ目のリクエストが終わり、6秒後に3つ目のリクエストが終わる。もしもsleepで2秒待つようになっていればそういうことだ。
しかし、このサーバはアイドル状態になってほかのコネクションを扱うことができる。実際にやってみよう。100のリクエストを並列性100で実行してみる。
100回テストを繰り返してかかった時間は2.026秒だ。
つまり、Nodeは並列性をうまく扱うことができる、決してHaltもsleepもせず、つねにコネクションをうまくハンドルできることができる、ということを示せた。
(続きは後編、「Node.jsとは何か、開発者ライアン・ダール氏が語る(後編)~ 複数コネクションの並列処理とデバッグ」へ) 。