そのNode.jsの認識ちょっと違うんじゃないの?って思って調べて考えてみた


 

エッセンス:Node.jsの本当の姿 はこっちではというポイントを、整理してみました。

一緒に考えてみませんか?

よく見る Node.jsの説明にはこう特徴が書いてあるけど

  • サーバサイドJS
  • シングルスレッド
  • ノンブロッキングI/O
  • 大量に 省メモリでさばける


この時点で 大小様々な??が頭に浮かんだ。

せっかく色々なページで説明してくれているけれども、
核心を突くのではなく周辺の説明ばかりで、
肝心なポイントが説明されていないので
イマイチ理解が深まらなかった。 

まず先に、どうでもいいかもまたはあまり本質と関係ないと思われる
良く主張されているポイントを整理すると。

主張その1:「長所:通常は待ち時間が発生するため、
ページの表示が遅くなるが~~」

ノンブロッキングだから早いと説明しようとしているが

ノンブロッキングしたってページを返す処理時間は変わらない 

(スレッドだろうと、マルチプロセスだろうとこれは同じこと
  サーバの話と、クライアントの話がごっちゃになってる?
  読んだ人が Node.jsの動作なのか混乱して伝達されたのかな?)

⇛ブロックするかしないかで向上するポイントは、
単位時間あたりの性能の話。
むしろ非同期のほうが1リクエストあたりは逆に処理自体遅くる。
あとで説明するけれど接続にたいしてのノンブロッキング方式は
サーバ/クライアント間で 今は実現不可能(今は調整中)。

主張その2:「シングルスレッドだから早い」

⇛node.jsでなく、JavaScriptの話をしているのか?

 シングルだと同時にさばけないと思うし
同時に裁かないという前提はありえないだろうし。

何がどう早くなるという意味だろうか?
理論がわからん。

主張その3:「シングルだからメモリ使用量が抑えられる。」

⇛(まちがってないけど実装によるし、)それだと逐次処理していることになる。
さっきと同じように
それだと一番の目的である
”大量に同時にさばく” ことができていないでしょ。 

 

これらをまとめると

結局シングルでどう大量にさばいているのかが不明だった。

10サイトほど見てもみんな同じポイントしか説明されておらず、なかなかわからない。

再度、やはり気になったので後日仕組みを考えてみた。

node.jsの設計を想像する

まず、今の時点でわかる正しいポイントや事実は

おそらくどこかに"シングル"の話と"ノンブロッキング"の話はあるのだろう。
Node.jsが大量に1万件さばき、メモリ使用量も抑えているのは実験データから事実。

モデルケースとして、Google Map のような Ajaxのケースを考える


多数のリクエスト を 地域のエリアの区画ごとに画像を要求するケースで
ノンブロッキング方式を考える

まず考えた設計方針:
サーバからクライアントへ
「コールバックするのであれば処理が終わったら連絡くれていいよね。
受け付けたらすぐクライアントへ返答して欲しい。待つ必要無いから 。
だからノンブロッキング 。」この設計の筋はよいと思う。

どこに何を応答するのか、は配列のような形で覚えておける。
JMSのようなメッセージングキューのイメージ 。
しかも複雑な処理は必要ない 。
大量、多様だけれどもデータを返すだけ。

そもそも根本となる問題がある

具体的に考えてみると、明確になってきた部分がある。
その設計の筋では問題がある。
そもそもWebサーバは、サーバからクライアントへメッセージ送信できない
リクエストに対するレスポンスとしてなら返せる。
(たとえIPアドレスを知っていても一般の家庭などには高確率で返せない)
クライアント/サーバ間でのブラウザによるノンブロッキングに無理がある。

やはり「リクエストはノンブロッキングせずサーバ側で保持しっぱなし」で別の案を考える

次に考えた方針:
リクエストを受けたら、返さずに待たせ、内部に処理させる。
終わったら待たせてバインディングしていた コネクション経由で返す。
「ノンブロッキング」が指していたものはクライアントとの通信側ではなく、
サーバ内部側では?

ここに、ちょっとそれを匂わせる記述があった。
http://www.slideshare.net/forest1040/nodejs-7658878
24ページ

方向性はあっているようだけども
でもこの方式って劇的に新しさを感じない

Apacheでもやっている普通のマルチスレッドライクと変わらないような
シングルスレッドなら今までと変わらないような

単純な処理しかできないから、メモリを使わない前提で作られている。
重い処理だと Apacheと一緒のはず 。これはマルチスレッド方式。

(うーん最初に、やや戻ってきた感があるが、、、 そうか!)

こんな感じ!っぽい

肝心なのは、ガンガン引き受けまくって、
省メモリで済むデータのやり取り
(プログラム部分が少ない、ほぼどのリクエストも同じ処理のケース)
一プロセス上で、配列やハッシュを使ってリクエスト管理処理しているのではないか?

リクエスト引き受けて、処理のサイクルを早めるか
リクエストが有れば引き受けて、なくなったら処理して随時返す。
先に引き受け続けると破綻するから、必ず一つ以上処理している状態を Node.jsで管理しておく
(リソースを限界まで使うため、OSで扱うプロセスをまたがない)

つまり Apache Webサーバの省エネ版
OSのプロセス起動やスイッチングコスト、
OSのプロセス間通信のコストを0に近づける。
(複雑な処理の場合、それが限界性能になる)

TCPコネクションが来るたびに、
プロセスを立ちあげない(立ち上げっぱなしの)
なんらかの工夫があるはず 。

 

探してみると
どうやらlibev で Cで作られている 。
それと libeioで 非同期並行処理している らしい。

これらを考えるに
ここまでのマルチThreadよりもっと省エネの仕組みという考え方は正しそう。
マシン語に近いレベルで、ソケットと処理通信の連携を管理しているはずだ。

それだと柔軟なコネクション管理やリクエスト処理はできない。
それに長時間処理するとタイムアウトエラーの発生頻度が上がりやすいのはデメリットとして残る。

ただし、1個しか処理できないから、同時接続数(メモリ使用量)も食わない 。
また、JSにすることで、拡張や切り替え(デプロイ)がし易い メリットも有る。

 

ただ、大量にさばくのか瞬間的にさばいているのか、ここらへんがまだ曖昧。。。

一旦今の想定を整理してみる。

処理 はどうなっているか

先ほどのサイトのスライドのソースらしきもの見ながら、考えや流れ整理してみた。
-------------------------------

1. tcp受け付ける
    新規接続は タスク処理登録
    タスク処理中

2.受付た分ループ
    (a)1回目 データ受取 listnerから受信
         クライアントに処理させる ←ここがノンブロッキング
                or
     (b) 2回目 データ処理 処理しているclientから受信
              ↑おそらく、ここが jsを作って処理させバッファに書き込んでいるクライアント処理と推測する
         bufferに処理分を貯めこむ
         終わったらソケットクローズ

また1~を無限ループ

-------------------------------

つまり、TCPのソケット通信をcで作られたバイナリが、各ノードの単位で処理している。

シングルスレッドではない!!
大量にさばくため、ノードに分けて分散処理していた!

node.js のソースぐらい読んでおきたい! - by edvakf in hatena - http://goo.gl/iTgHF5

 

Webを探してみると、
Node.js のユーザページも見つけたし、(今更。。)
ソースを解読している人がいた。

まず、設計観点について経緯からまとめると

Node.js 日本ユーザグループの人の仕組みの説明にちゃんとあった。
(シングルスレッドではなくて)
http://d.hatena.ne.jp/badatmath/20101020/1287587240

Node.js開発経緯の課題は、C10Kという性能課題

スレッドで処理するか
イベントループで処理するか  の2項対立の課題があった。

これは手段論だが、本質の目的論で書き直すと

要するに、大量に捌(さば)く
(Apacheなどの現在の方法:スイッチングコストが高くトータル処理が少し落ちる )



メモリを抑え瞬間瞬間にさばくか
(昔の当初のシングルの方法:ブロックされると全部に遅延が伝播する )

ブロックする方法は、待ち行列理論などで、
このデメリットが大きいし、遅延が拡大することがわかってる。 

イベントループ方式のLeanさでスイッチングコストを抑えたまま
メモリ使用量を抑え、 ブロックしないようにできないか

まず、問題を具体的にみてみると

ブロックとは、通信やI/Oなど CPUの外と処理するもの これが重い。
(ネットワークやDISKなどもその対象
 何百万倍も遅いからね
 20~200msec 対 数/Gsec レベルぐらい違いがある)

ネットワークの歴史的にはノンブロッキングするために、
マルチプロセスやマルチスレッドがそもそも考えられている

ノンブロッキングI/Oを実現するのは結構大変。しかも遅かった。

経緯を読んで推測してみる。

その悩みを抱えていたところ
幸運なことに、epoll という高速ライブラリが合った!
しかも面倒なOSの差分を吸収してくれるlibeventもある !!(memcachedで使用)
さらに、その安定版の libev がありセットで libeio という ノンブロッキングI/Oができる物もあった!!!

JSバインディングができれば、ソケット通信と jsの処理を結び付けられる
その技術を chrome開発していた googleは持っていた !それがV8!

 

これで、機能的レイヤーで
TCP受付から、分散処理、JavaScript の連携はできるようになった。

おそらく v8のカスタマイズ版 を利用して、
JavaScriptをサーバ用途に処理し、ローカルリソースを扱えるようにしているはず。
(JavaScriptはクライアントサイドの技術だしそういう機能しかない)

このv8が tcp のnodeに分けて処理しているサーバと、
JavaScriptの間を仲立ちしている。
普通のソケット通信の方式とNode.jsの方式は本質的には変わらない。

libeio上でスレッドプールを使用している。


構成を図示してみると

Node.jsとは(図の左側の)サーバ入り口でソケットを受け付けて、
worker経由で JavaScriptのバイナリ(コンパイル済みコード)が、それぞれ処理している。

サーバ側のところでイベントループをし、
終わったかどうか(コールバック?か、バッファリスニングで)確認しながら処理している

サーバ worker JavaScript
worker JavaScript
worker JavaScript

補足:Node.jsは 自前でかなりゴテゴテと処理やテキストを
headerやhttpのURL切り分けから書かないといけない。

まとめ

Node.js は javascriptを指しているわけではなく
tcpのソケット通信部分と Node分散処理と、
ノンブロッキングI/Oや JavaScript解析用のV8カスタマイズ版で
できている。
それらはc++などで実装されている。

Node.jsは 引き受けた処理を node単位に分散処理させて、
それぞれのバッファキューで処理を管理している。

V8エンジンで JavaScriptをコンパイルしたバイナリで、
workerとなっている nodeに結果を返している。

分散処理として、イベントループ方式をとっているのが
Node.js(シングル方式ではない)。

つまり、1プロセスレベルでループしながら
スイッチングコストを抑えている。
本質的には Apache のprefork式と同じ。
タスクはもちろん切り替えている。

ネットワークコネクションを受け持っている部分は一つっぽい。

処理の重さ

prefork (子プロセス版) >  worker (子プロセスの中のマルチプロセス版) > Node.js (イベントループによる簡易キュー処理)

ソケット通信からダイレクトにJSにつながっているので、
余計なアプリケーションレイヤー間のコストもないから省エネで速い。

でも密結合だから、サーバ側の通信以外の処理をまるっとJSで頑張って書くしか無い。
つまり複雑なとくにバリエーションに富んだ処理をするのは大変。
→Node.jsのデメリット:複雑な処理は向かない。使う側の作りこみが大変。

起動時にメモリ上へJSも含めてネイティブバイナリにV8がコンパイルしているので、
変更する度プロセス立ち上げをし、JSの読み直しは必要になる。

JSを使うことで、学習コストも低いし拡張性も保持してある。
→つまり、JSの使いやすさと、スピードの両立を実現したのがNode.js

重い複雑な切り分けも含んだ処理をJSにさせると
Apache Webサーバの方式と性能は近づくかもしれないですよ。
というかNode.jsはあまりにも素そのものなので作るの大変

Node.jsは「サーバ側で動作するJS」というよりも、
「動作をJSで変更できる」方が語弊がないかも。

ちなみに、Node.jsではサーバ用途向けにしなければいけないので
ローカルファイルにアクセスするなどサーバ側の処理向けのAPIが
幾つか用意されている。

おさらい

冒頭の、4点について、おさらいしてみようと思う。

  • サーバサイドJS
  • シングルスレッド
  • ノンブロッキングI/O
  • 大量に 省メモリでさばける


サーバサイドJS→OK。処理を受け付けた後の処理はまるまる書ける/書かないといけない。つまり似たような単純処理向け。JSのライブラリが使えるが、JSはクライアントブラウザ上の処理が目的なので、サーバやディスクリソース等に関するライブラリは多分充実しきれていないと思う。

シングルスレッド→サーバクライアント間でリクエストを受け付けるところは、シングルスレッド。プリミティブに実装しているため(JSもプリコンパイルしていると思う)、省コスト。その分複雑なリクエスト処理はできない。

ノンブロッキングI/O→サーバの話なのに、これだけ話しのポイントが違うが、サーバ内部で各ワーカーに処理を渡した時に、待たない。クライアントは待たせていると思う。

大量に省メモリでさばける→2個上で話したとおり。ネットワークコネクション部分の話。

 

つまり

Node.jsはシングルではないよ!
ノンブロッキング方式で分散処理させる方法を省コストで編み出したのが Node.jsだよ。
JSで簡単に作れて処理できそうであれば、ApacheよりもNode.jsってことになるかもよー。

感想

Node.jsって アップしちゃいけないファイルをコントロールするには向かないので、
Webサーバの後ろでpublicサーバのように動かすのが良さそう。
小さなファイルを大量にリクエストされた時の用途など(同じような処理でしょ?)。 

あとは簡単にWebサーバが建てられるので、
開発用途としてもちょっとしたことには便利な気がする。

meteorとの比較しておくとよいかもしれませんね。

 

Bookmark this on Yahoo Bookmark
Buzz This
Bookmark this on Delicious
Bookmark this on Digg
Share on reddit
Share on LinkedIn
Bookmark this on @nifty clip
LINEで送る
email this
Pocket




コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です