Help us understand the problem. What is going on with this article?

nginx+luaで動作するルーティングライブラリ lua-resty-r3 を書きました

More than 3 years have passed since last update.

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で書いたの初めてなので変なところがあればツッコミお願いします:)


  1. Luaで同じようなモジュールだと、APItoolsのrouter.luaがあります。 

  2. 本当はinsert_pathやmatch_pathにも対応してたんですが、使わなさそうなので消しちゃいました :) 

tver-technologies
Innovate TVision, Designing Value / テレビ・動画配信の可能性を切り拓き、新しい価値をデザインしていきます。
https://www.tver-tech.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away