lua-resty-r3
https://github.com/toritori0318/lua-resty-r3
nginx+lua上で動作するURLルーティングライブラリです。
lua+ffiでr3をバインディングしています。
r3(libr3)について
リポジトリはこちら。
https://github.com/c9s/r3
高速なルーターライブラリということを謳っているようです。
perl/python/ruby/node.js/Haskell などのバインディングが存在しています。
書いた動機
- nginx + lua でいい感じのルーティングモジュールが欲しかった 1
- より高速なルーティングがしたかった
- ffi モジュール書いてみたかった
実装方針
libr3には様々なAPIがありますが、今回はroutingに特化したAPIのみ対応しています 2
また、マッチした時の戻り値は functionブロックのみ対応しています。
さらにlibr3は比較的低レイヤー(matchする箇所など)を担当しているため
そのままラップしただけのものでは使いづらいものになってしまいます。
そのためlua-resty-r3のインタフェースは自分でわりと好きに定義してみました。
使い方
ほぼこちらそのままですが。。
https://github.com/toritori0318/lua-resty-r3#synopsys
-- マッチした時に実行されるハンドラ例
function foo(tokens, params)
ngx.say("fooooooooooooooooooooooo")
end
-- r3router
local r3router = require "resty.r3";
-------------------------------------------------------------
-- route登録パターン1:newした後で明示的に get / post などを指定
-------------------------------------------------------------
local r = r3router.new()
-- routing
r.get("/", function(tokens, params)
ngx.say("hello r3!")
end)
r.get("/foo", foo)
r.get("/foo/{id}/{name}", foo)
r.post("/foo/{id}/{name}", foo)
-- パターン1の時はcompileを忘れずに!
r.compile()
-------------------------------------------------------------
-- route登録パターン2:初期化時にルーティング直接指定しちゃう
-- こっちがオススメかも
-------------------------------------------------------------
local r = r3router.new({
{"GET", "/", function(t, p) ngx.say("hello r3!") end },
{"GET", "/foo", foo},
{{"GET","POST"}, "/foo/{id}/{name}", foo},
})
-------------------------------------------------------------
-- dispatch_ngx() ngx変数から自動でそれっぽく処理してくれる
-------------------------------------------------------------
local ok = r:dispatch_ngx()
-------------------------------------------------------------
-- dispatch() 自分で明示的に渡すのもOK
-------------------------------------------------------------
local ok = r.dispatch(ngx.var.request_method, ngx.var.uri, ngx.req.get_uri_args(), ngx.req.get_post_args())
if not ok then
ngx.status = 404
ngx.print("Not found")
end
ベンチマーク
スクリプトはこちらに置いてます。
比較対象は router.lua です。
環境
EC2のc4.largeで検証しました。
dispatchのみ
resty-cli を利用し以下の様に実行しました。短いほど良い結果です。
dispatchを1000万回ループしています。
resty test_lua_resty_r3.lua
項目 | get / | get /foo/bar/baz/hoge/fuga/piyo | get /foo/?/? |
---|---|---|---|
lua-resty-r3 | 1.35 sec | 2.87 sec | 5.19 sec |
apitools-router | 3.46 sec | 19.46 sec | 14.85 sec |
r3速いですね!
HTTP経由
wrk を利用しHTTP経由で以下の様に実行しました。大きいほど良い結果です。
wrk -t10 -c200 -d15s http://127.0.0.1:80/...
項目 | get / | get /foo/bar/baz/hoge/fuga/piyo | get /foo/?/? |
---|---|---|---|
nginx | 45375 req/sec | 41675 req/sec | 44270 req/sec |
lua-resty-r3 | 36382 req/sec | 34591 req/sec | 32827 req/sec |
apitools-router | 41918 req/sec | 36967 req/sec | 38844 req/sec |
(; ・∀・) あ、あれ、遅くなってる…
試しに1リクエスト内で複数dispatchループすると逆転するので、
おそらくリクエスト受けた時の処理が重いんでしょうねー。
また当初クラス化してないバージョンで書いてたのですが、試しにそのバージョンで動かしてみたらもっと高速に動作しました。
もしかしたらクラスオブジェクト化するところが重いのかも。。
まとめ
nginx + lua 上で高速に動作する r3バインディングを書いてみました。
http経由だと遅いという残念な結果になってしまいましたが
改修してリベンジしたいと思います…
またffiで書いたの初めてなので変なところがあればツッコミお願いします:)
-
Luaで同じようなモジュールだと、APItoolsのrouter.luaがあります。 ↩
-
本当はinsert_pathやmatch_pathにも対応してたんですが、使わなさそうなので消しちゃいました :) ↩