OpenResty 中常用的三个 Lua Resty 库

API7.ai

January 13, 2023

OpenResty (NGINX + Lua)

プログラミング言語やプラットフォームについて学ぶことは、しばしば構文そのものではなく、標準ライブラリやサードパーティライブラリを理解することです。APIとパフォーマンス最適化技術を学んだ後、さまざまなlua-restyライブラリの使用を学び、OpenRestyの機能をより多くのシナリオに拡張する必要があります。

lua-restyライブラリはどこで見つけられるか?

PHP、Python、JavaScriptと比較して、現在のOpenRestyの標準およびサードパーティライブラリはまだ比較的乏しく、適切なlua-restyライブラリを見つけるのは容易ではありません。しかし、ここではそれらをより速く見つけるための2つの推奨ソースを紹介します。

最初の推奨は、Aapoがメンテナンスしているawesome-restyリポジトリです。このリポジトリは、OpenResty関連のライブラリをカテゴリ別に整理しており、NGINX Cモジュール、lua-restyライブラリ、Webフレームワーク、ルーティングライブラリ、テンプレート、テストフレームワークなどが含まれています。OpenRestyリソースの第一選択肢です。

Aapoのリポジトリで適切なライブラリが見つからない場合、luarockstopm、またはGitHubを参照することもできます。長期間オープンソース化されていないが注目されていないライブラリが存在するかもしれません。

以前の記事で、lua-resty-mlcachelua-resty-trafficlua-resty-shellなどの有用なライブラリについて学びました。今日、OpenRestyパフォーマンス最適化セクションの最後の記事で、コミュニティの開発者によって貢献された3つのユニークな周辺ライブラリを紹介します。

ngx.varのパフォーマンス向上

まず、Cモジュールのlua-var-nginx-moduleを見てみましょう。以前述べたように、ngx.varは比較的パフォーマンスを消費する操作です。したがって、実際にはngx.ctxをキャッシュ層として使用する必要があります。

では、ngx.varのパフォーマンス問題を完全に解決する方法はあるのでしょうか?

このCモジュールはこの分野でいくつかの実験を行い、その結果は顕著で、ngx.varよりも5倍のパフォーマンス向上を達成しました。FFIアプローチを使用しているため、まず以下のコンパイルオプションでOpenRestyをコンパイルする必要があります。

./configure --prefix=/opt/openresty \
         --add-module=/path/to/lua-var-nginx-module

次に、luarocksを使用して以下の方法でluaライブラリをインストールします:

luarocks install lua-resty-ngxvar

ここで呼び出されるメソッドも非常に簡単で、fetch関数の1行だけが必要です。これは、元のngx.var.remote_addrと同等に機能し、クライアントのIPアドレスを取得します。

content_by_lua_block {
    local var = require("resty.ngxvar")
    ngx.say(var.fetch("remote_addr"))
}

これらの基本的な操作を理解した後、このモジュールがどのようにして大幅なパフォーマンス向上を達成したのかについてさらに興味を持つかもしれません。私たちが常に言うように、「ソースコードの前には秘密はありません」。それでは、remote_addr変数を取得する方法を見てみましょう。

ngx_int_t
ngx_http_lua_var_ffi_remote_addr(ngx_http_request_t *r, ngx_str_t *remote_addr)
{
    remote_addr->len = r->connection->addr_text.len;
    remote_addr->data = r->connection->addr_text.data;

    return NGX_OK;
}

このコードを読むと、このLua FFIアプローチはlua-resty-coreのアプローチと同じであることがわかります。FFIを使用して変数を直接取得し、ngx.varの元の検索ロジックをバイパスするという明らかな利点があります。その欠点も明らかです:取得したい各変数に対してC関数とFFI呼び出しを追加する必要があり、時間と労力がかかります。

「なぜこれが時間と労力を要すると言うのか?上記のCコードはかなり実質的に見えるのではないか?」と疑問に思う人もいるかもしれません。これらのコードのソースを見てみましょう。これらはNGINXコードのsrc/http/ngx_http_variables.cから来ています。

static ngx_int_t
ngx_http_variable_remote_addr(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
    v->len = r->connection->addr_text.len;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = r->connection->addr_text.data;

    return NGX_OK;
}

ソースコードを見た後、謎が解けました!lua-var-nginx-moduleはNGINX変数コードのポーターであり、外側にFFIラッピングがあり、この方法でパフォーマンス最適化を達成しています。これは良いアイデアであり、最適化の良い方向性です。

ライブラリやツールを学ぶ際には、操作のレベルで止まらず、なぜそれを行うのかを問い、ソースコードを見ることが重要です。もちろん、より多くのNGINX変数をサポートするためにコードを貢献することも強く推奨します。

JSON Schema

ここで、lua-restyライブラリのlua-rapidjsonを紹介します。これは、Tencentがオープンソース化したJSONライブラリrapidjsonのラッパーであり、そのパフォーマンスで知られています。ここでは、cjsonとの違いであるJSON Schemaサポートに焦点を当てます。

JSON Schemaは、インターフェースのパラメータの形式とその検証方法を正確に記述できる一般的な標準です。以下は簡単な例です:

"stringArray": {
    "type": "array",
    "items": { "type": "string" },
    "minItems": 1,
    "uniqueItems": true
}

このJSONは、stringArrayパラメータが文字列配列の型であり、配列が空であってはならず、配列要素が重複してはならないことを正確に記述しています。

lua-rapidjsonを使用すると、OpenRestyでJSON Schemaを使用でき、インターフェース検証に大きな便利さをもたらします。例えば、前述の制限カウントインターフェースを以下のスキーマで記述できます:

local schema = {
    type = "object",
    properties = {
        count = {type = "integer", minimum = 0},
        time_window = {type = "integer",  minimum = 0},
        key = {type = "string", enum = {"remote_addr", "server_addr"}},
        rejected_code = {type = "integer", minimum = 200, maximum = 600},
    },
    additionalProperties = false,
    required = {"count", "time_window", "key", "rejected_code"},
}

これにより、2つの非常に明らかな利点が得られます:

  1. フロントエンドにとって、フロントエンドはこのスキーマ記述を直接再利用してフロントエンドページの開発とパラメータ検証を行うことができ、バックエンドを気にする必要がありません。
  2. バックエンドにとって、バックエンドはlua-rapidjsonのスキーマ検証機能SchemaValidatorを直接使用してインターフェースの正当性を判断し、余分なコードを書く必要がありません。

Worker間通信

最後に、OpenRestyでワーカー間通信を可能にするlua-restyライブラリについて話します。OpenRestyにはワーカープロセス間の直接通信メカニズムがないため、多くの問題が発生します。次のシナリオを想像してみてください:

OpenRestyサービスには24のワーカープロセスがあり、管理者がREST HTTP APIを通じてシステムの設定を更新すると、1つのWorkerだけが管理者からの更新を受け取り、データベースに結果を書き込み、自身のWorker内のshared dictlru cacheを更新します。では、他の23のワーカーにこの設定を更新するように通知するにはどうすればよいでしょうか?

複数のWorker間の通知メカニズムが必要です。OpenRestyがそれをサポートしていない場合、shared dictデータを介してワーカー間でデータを保存する必要があります。

lua-resty-worker-eventsはこのアイデアの具体的な実装です。shared dictにバージョン番号を維持し、新しいメッセージが公開されると、バージョン番号に1を加え、メッセージの内容をバージョン番号をkeyとして辞書に格納します。

event_id, err = _dict:incr(KEY_LAST_ID, 1)
success, err = _dict:add(KEY_DATA .. tostring(event_id), json)

また、デフォルトで1秒間隔のpollingループをngx.timerを使用してバックグラウンドで作成し、バージョン番号の変更を常にチェックします:

local event_id, err = get_event_id()
if event_id == _last_event then
    return "done"
end

このようにして、新しいイベント通知が処理されることがわかると、バージョン番号に基づいてshared dictからメッセージ内容を取得します:

while _last_event < event_id do
    count = count + 1
    _last_event = _last_event + 1
    data, err = _dict:get(KEY_DATA..tostring(_last_event))
end

全体として、lua-resty-worker-eventsには1秒の遅延がありますが、それでもワーカー間のイベント通知メカニズムを実装しています。

ただし、メッセージプッシュなどのリアルタイムシナリオでは、OpenRestyのWorkerプロセス間の直接通信の欠如が問題を引き起こす可能性があります。これに対するより良い解決策はありませんが、良いアイデアがあればGithubで自由に議論してください。OpenRestyの多くの機能は、コミュニティによって駆動され、健全な生態系を構築しています。

まとめ

今日紹介した3つのライブラリはユニークで、OpenRestyアプリケーションにさらなる可能性をもたらします。最後に、インタラクティブなトピックとして、OpenResty周辺で面白いライブラリを見つけましたか?または、今日紹介したライブラリについて何か発見や疑問がありますか?この記事を周りのOpenRestyユーザーに送って、一緒に交流し、進歩してください。