OpenResty XRay がどのようにしてアプリケーションの問題特定と効率化を支援するかをご覧ください。

詳細はこちら LIVE DEMO

このチュートリアルでは、OpenResty アプリケーションサービスの異なる HTTP リクエスト間でデータを共有する方法を段階的に説明します。ここでは 2 つのアプローチを取り上げます。単一のワーカープロセス内で共有するための Lua モジュールレベル変数と、すべてのワーカープロセス間で共有するための lua_shared_dict です。本記事は OpenResty チュートリアルシリーズの一部です。OpenResty が初めての方は、まず Hello World HTTP の例から始めてください。

プロジェクトのセットアップ

cd ~/
mkdir data-share
cd data-share/
mkdir conf logs lua
tree

スクリーンショット 1

まず、テストアプリケーションのディレクトリ構造を準備します。

方法 1:Lua モジュールレベル変数

リクエスト間でデータを共有する最も簡単な方法は、lua/my-module.lua という名前のカスタム Lua モジュールを使用することです。

以下の編集を行います:

  1. このモジュールのトップレベルにグローバルカウンター変数を追加します。
  2. 次に、main という名前のモジュール関数内で常にこのカウンターをインクリメントします。
  3. そして、新しいカウンター値を返します。
local _M = {}

local counter = 0

function _M.main()
    counter = counter + 1
    return counter
end

return _M

スクリーンショット 6

Nginx の設定

次に、nginx 設定ファイル conf/nginx.conf を作成します。

以下の編集を行います:

  1. ここで、Lua モジュールを探す場所を指定します。
  2. 次に、ローカルの 8080 ポートでリッスンするサーバーを定義します。
  3. ルートロケーションには content_by_lua_block があります。
  4. Lua モジュールをロードし、その main 関数を呼び出します。
  5. 返されたカウンター値をレスポンスボディとして出力します。
worker_processes 1;

events {
    worker_connections 1024;
}

http {
    lua_package_path "$prefix/lua/?.lua;;";

    server {
        listen 8080;

        location / {
            default_type text/plain;
            content_by_lua_block {
                local mod = require "my-module"
                local cnt = mod.main()
                ngx.say("counter = ", cnt)
            }
        }
    }
}

スクリーンショット 13

ディレクトリ構造を確認しましょう。

tree .

スクリーンショット 15

問題なさそうですね。

起動とテスト

sudo を使用せずに この OpenResty アプリケーションを起動します。

openresty -p $PWD/

スクリーンショット 17

ここでは、1 つのワーカープロセスのみを有効にしています。

ps aux|grep nginx|grep -v tmp

スクリーンショット 18

curl を使用してサーバーに HTTP リクエストを送信します。

curl 'http://127.0.0.1:8080/'

スクリーンショット 19

カウンターの値は 1 です。

もう一度試してみましょう:

スクリーンショット 21

今度は 2 になりました。

これは継続的に増加していきます。

スクリーンショット 23

仕組み:アップバリューと LuaJIT VM

この counter Lua 変数が共有されているのは、Lua モジュール関数のアップバリュー(upvalue)として機能しているためです。

cat lua/my-module.lua

スクリーンショット 24

そして、Lua モジュールはグローバルな LuaJIT 仮想マシン内でキャッシュされ、共有されています。

resty -Ilua -e 'require "my-module" print(package.loaded["my-module"].main)'

スクリーンショット 25

ご覧のとおり、キャッシュされた Lua モジュールに直接アクセスすることもできます。

package.loaded グローバルテーブルには、ロードされたすべての Lua モジュールが保存されています。

resty -e 'for k, v in pairs(package.loaded) do print(k) end'

スクリーンショット 27

ここでは、ほとんどのモジュールが標準モジュールです。

制限:データはワーカープロセス間で共有されない

これらのデータは異なる nginx ワーカープロセス間で共有できません。

スクリーンショット 29

ここでは問題ありません。1 つのワーカープロセスのみが設定されているからです。

しかし、通常は複数の CPU コアを活用するために複数のワーカープロセスを有効にします。

worker_processes 4;

スクリーンショット 31

その場合、各ワーカープロセスが独自のカウンターを持つことになります。

設定をテストし、サーバーをリロードしてみましょう。

openresty -p $PWD/ -t
kill -HUP `cat logs/nginx.pid`

スクリーンショット 33

これで 4 つのワーカープロセスが動作しているはずです。

ps aux|grep nginx|grep -v tmp

スクリーンショット 34

適切なアプローチの選び方

2 つの共有メカニズムは、それぞれ異なるユースケースに適しています。

Lua モジュール変数lua_shared_dict
スコープ単一のワーカープロセスすべてのワーカープロセス
値の型任意の Lua 型文字列、数値、真偽値
典型的な用途設定キャッシュ、ワーカーごとの LRU キャッシュカウンター、レート制限、共有フラグ
HUP リロード後も保持されるかいいえはい
再起動後も保持されるかいいえいいえ
アトミック操作対象外可能(incraddset

Lua モジュールによる方法は、主に Lua レベルのデータキャッシングに使用されます。OpenResty に付属の resty.lrucache Lua モジュールは、このようなシナリオのために設計されています。

restydoc resty.lrucache

スクリーンショット 36

このモジュールについては、後のチュートリアルで説明します。

方法 2:lua_shared_dict を使ったワーカー間のデータ共有

すべてのワーカープロセス間でカウンターを共有するには、http ブロック内で lua_shared_dict を使って共有メモリゾーンを宣言し、ngx.shared.DICT 経由でアクセスします(これらのゾーンが内部でどのように RAM を消費するかを理解するには、OpenResty と Nginx の共有メモリゾーンはどのように RAM を消費するのかを参照してください):

http {
    lua_shared_dict my_counter 1m;

    server {
        listen 8080;

        location / {
            default_type text/plain;
            content_by_lua_block {
                local dict = ngx.shared.my_counter
                local newval, err = dict:incr("counter", 1)
                if not newval then
                    dict:add("counter", 0)
                    newval = dict:incr("counter", 1)
                end
                ngx.say("counter = ", newval)
            }
        }
    }
}

このカウンターは、稼働中のワーカープロセス数に関係なく正しくインクリメントされ、その値は HUP リロード後も保持されます。有効期限(ttl)、エビクション動作、リスト操作を含む完全な ngx.shared.DICT API については、専用のチュートリアルで取り上げます。

restydoc -s lua_shared_dict
restydoc -s ngx.shared.DICT

スクリーンショット 38

今回取り上げたいのは以上です。ハッピーハッキング!

このチュートリアルが気に入っていただけましたら、このブログサイトと私たちの YouTube チャンネルをご購読ください。ありがとうございます!

本記事は OpenResty チュートリアルシリーズの一部であり、インストールから本番運用に向けた技術までを段階的に解説するガイドです。続けて OpenResty での HTTP レスポンスのストリーミング出力をお読みください。

著者について

章亦春(Zhang Yichun)は、オープンソースの OpenResty® プロジェクトの創始者であり、OpenResty Inc. の CEO および創業者です。

章亦春(GitHub ID: agentzh)は中国江蘇省生まれで、現在は米国ベイエリアに在住しております。彼は中国における初期のオープンソース技術と文化の提唱者およびリーダーの一人であり、Cloudflare、Yahoo!、Alibaba など、国際的に有名なハイテク企業に勤務した経験があります。「エッジコンピューティング」、「動的トレーシング」、「機械プログラミング」 の先駆者であり、22 年以上のプログラミング経験と 16 年以上のオープンソース経験を持っております。世界中で 4000 万以上のドメイン名を持つユーザーを抱えるオープンソースプロジェクトのリーダーとして、彼は OpenResty® オープンソースプロジェクトをベースに、米国シリコンバレーの中心部にハイテク企業 OpenResty Inc. を設立いたしました。同社の主力製品である OpenResty XRay動的トレーシング技術を利用した非侵襲的な障害分析および排除ツール)と OpenResty Edge(マイクロサービスおよび分散トラフィックに最適化された多機能ゲートウェイソフトウェア)は、世界中の多くの上場企業および大企業から高い評価を得ております。OpenResty 以外にも、章亦春は Linux カーネル、Nginx、LuaJITGDBSystemTapLLVM、Perl など、複数のオープンソースプロジェクトに累計 100 万行以上のコードを寄与し、60 以上のオープンソースソフトウェアライブラリを執筆しております。

翻訳

英語版の原文と日本語訳版(本文)をご用意しております。読者の皆様による他の言語への翻訳版も歓迎いたします。全文翻訳で省略がなければ、採用を検討させていただきます。心より感謝申し上げます!