Erlangにhttpcなんてのがあるんだよな、Kyoto Tycoonたたくのが楽すぎる
R14Bの完熟走行ついでにtutorialにあるように、Kyoto Tycoonをたたいてみる。
以下、すごく大雑把なメモ。
1> inets:start(). ok 2> httpc:request("http://192.168.1.2:1978/rpc/set?key=korea&value=seoul"). {ok,{{"HTTP/1.1",200,"OK"}, [{"date","Thu, 18 Nov 2010 07:30:20 GMT"}, {"server","KyotoTycoon/0.9.8"}, {"content-length","0"}, {"content-type","text/tab-separated-values"}], []}} 3> httpc:request("http://192.168.1.2:1978/rpc/get?key=korea"). {ok,{{"HTTP/1.1",200,"OK"}, [{"date","Thu, 18 Nov 2010 07:37:18 GMT"}, {"server","KyotoTycoon/0.9.8"}, {"content-length","11"}, {"content-type","text/tab-separated-values"}], "value\tseoul\n"}}
おー、簡単。
手元の本*1だとgen_tcpとかでlow levelの設定をした関数を定義して、、とかやっていたのが嘘のようだ。新しい本*2なら常識なのかな。
しかし、keep aliveとか細かい話をするなら、そこまで下りていかないとだめかしら、、いやいや、、httpc:request/4でも、下記のようにREST風にも叩けるし、httpc:request/5だとかなり細かい指定ができるようだ。Erlangで明示的にコード書くよりbuilt-in関数の方が速いかな?
4> httpc:request(delete, {"http://192.168.1.2:1978/korea",},,). {ok,{{"HTTP/1.1",204,"No Content"}, [{"date","Thu, 18 Nov 2010 08:06:08 GMT"}, {"server","KyotoTycoon/0.9.8"}, {"content-length","0"}], }}
そもそも何故MnesiaやCouch-DBを使わずにKyotoTycoon/Kyoto Cabinetなのか?
そりゃ、まずは好奇心。特に、いろんな特性のあるキャッシュDBをとっかえひっかえ試せて、Luaによる拡張も自由にできるところが楽しい。*3
そして、それぞれのDBの特性は、作者の豊富な経験が反映されている(と思う)。たとえば60秒でcacheからexpireするようにした場合、こんな応答が返ってくる。
19> httpc:request("http://192.168.1.2:1978/rpc/set?key=japan&value=tokyo&xt=60"). {ok,{{"HTTP/1.1",200,"OK"}, [{"date","Thu, 18 Nov 2010 09:21:39 GMT"}, {"server","KyotoTycoon/0.9.8"}, {"content-length","0"}, {"content-type","text/tab-separated-values"}], []}} 20> httpc:request("http://192.168.1.2:1978/rpc/get?key=japan"). {ok,{{"HTTP/1.1",200,"OK"}, [{"date","Thu, 18 Nov 2010 09:21:51 GMT"}, {"server","KyotoTycoon/0.9.8"}, {"content-length","26"}, {"content-type","text/tab-separated-values"}], "value\ttokyo\nxt\t1290072159\n"}}
1分待つ
21> httpc:request("http://192.168.1.2:1978/rpc/get?key=japan"). {ok,{{"HTTP/1.1",450,"Logical Inconsistency"}, [{"date","Thu, 18 Nov 2010 09:23:39 GMT"}, {"server","KyotoTycoon/0.9.8"}, {"content-length","34"}, {"content-type","text/tab-separated-values"}], "ERROR\tDB: 7: no record: no record\n"}}
セッションデータはいちいち消すのが面倒だから、この手のDBに入れておいて、DBが勝手にやってくれるならありがたい。さらに、メモリやディスクが限られている場合には、expireするのを待たずに容量の上限で消す、という設定のDBまである。
こういう機能はrobustなサーバー構築には結構効く。試行錯誤でチューニングしている暇は現場には無いし、自分でwork aroundするよりも、ずっと信頼性高いソリューションになるだろう。
Erlang的にやりたいこと
あとは
- shardごとにプロセスを立ち上げ(spawn)
するだけなんだけどなー。なんで手がつかないかなぁ。
ちなみに上記の監視プロセスのschemeについては、Erlangには"supervisor behaviour"というのがあって、one to one, one_for_all, rest_for_oneというのが選べるが、scale_outやfault torelantとかdegradableってのは無いので、自作する必要がある。そこがchallengeだな。*4
my_supervisor behaviourを自作するとすれば
参照:http://www.trapexit.org/Defining_Your_Own_Behaviourとかhttp://d.hatena.ne.jp/m-hiyama/20070913/1189669297
こんな感じ?監視しているといっても、自動的にプロセス再起動ではなくて、DBサーバーが落ちていたら、プールからはずす、ということもしたい。それを明示的にやるAPIとしてscan_availabiltyを付け加えるとこんな感じか。(そこを隠すべきかは悩ましい)
-module(my_supervisor). -export([behaviour_info/1]). behaviour_info(callbacks) -> [ % init(WorkerSpec) -> {State, Spec} {init, 1}, % attach_worker(State) -> NewState {attach_worker, 1}, % detach_worker(State) -> NewState {detach_worker, 1} % scan_availability(State) -> NewState {scan_availability, 1} ]; behaviour_info(_Other) -> undefined.
まー、コード作る前に暇作んないとな。
追記:接続poolingか、監視か
my_supervisorは監視プロセスを統括すべきなのかしら。
接続poolingが効くのか、野放図にエンドユーザからのアクセス1つごとにErlangのプロセス1つ生成してしまうのがいいのかしら。
たとえば、同時接続数がめちゃくちゃ多くて、それぞれが低速なclientだったりすると、workerが枯渇してqueueが伸びることになる。
対応としては、clientの面倒をみるプロセスはどんどこ生成しておいて、そこからのアクセスをまとめてからconsistent hashingするということになるだろうか。
本当?
まー、やってみればいいんだな。Erlangなら「非常に多数の(数万)低速なclientからの同時接続」という状況を作るのは簡単だ。