OpenRestyのキラー機能:Dynamic
API7.ai
January 12, 2023
これまで、OpenRestyのパフォーマンス関連の内容についてほぼ完了しました。これらの最適化技術をマスターし、柔軟に適用することで、コードのパフォーマンスを大幅に向上させることができます。今日は、パフォーマンス最適化の最後の部分として、OpenRestyでしばしば過小評価されている「動的」機能について学びましょう。
まず、動的とは何か、そしてそれがパフォーマンスとどのように関連しているかを見てみましょう。ここでの動的とは、プログラムが実行時にパラメータ、設定、さらにはコード自体を変更できることを意味し、再読み込みを必要としません。具体的には、NGINXやOpenRestyでは、アップストリーム、SSL証明書、レートリミットの閾値をサービスを再起動せずに変更することができます。動的とパフォーマンスの関係は明らかで、これらの操作を動的に行えない場合、NGINXサービスの頻繁な再読み込みが自然とパフォーマンスの低下を招きます。
しかし、オープンソース版のNGINXは動的機能をサポートしていないため、アップストリームやSSL証明書を変更するには設定ファイルを修正し、サービスを再起動する必要があります。NGINX Plus(NGINXの商用版)はいくつかの動的機能を提供し、REST APIを使用して更新できますが、これは根本的な改善とは言えません。
OpenRestyでは、これらの制約は存在せず、動的機能はOpenRestyの強力な特徴です。なぜNGINXをベースにしたOpenRestyが動的機能をサポートできるのか不思議に思うかもしれませんが、その理由は単純です。NGINXのロジックはCモジュールで実装されているのに対し、OpenRestyはスクリプト言語であるLuaで実装されています。スクリプト言語の利点の一つは、実行時に動的に変更できることです。
コードを動的にロードする
OpenRestyでLuaコードを動的にロードする方法を見てみましょう。
resty -e 'local s = [[ngx.say("hello world")]]
local func, err = loadstring(s)
func()'
この数行のコードで、文字列をLua関数に変換し、実行することができます。これらのコードを詳しく見てみましょう:
- まず、
hello world
を出力するLuaコードを含む文字列を宣言します。 - 次に、Luaの
loadstring
関数を使用して、文字列オブジェクトを関数オブジェクトfunc
に変換します。 - 最後に、関数名に括弧を付けて
func
を実行し、hello world
を出力します。
もちろん、このコードを基に、より興味深く実用的な機能を拡張することもできます。次に、それを試してみましょう。
機能1: FaaS
まずはFaaS(Function-as-a-Service)です。これは最近非常に人気のある技術方向です。OpenRestyでどのように実装するかを見てみましょう。先ほどのコードでは、文字列はLuaコードでしたが、これをLua関数に変更することもできます:
local s = [[
return function()
ngx.say("hello world")
end
]]
前述の通り、Luaでは関数が第一級市民であり、このコードは匿名関数を返します。この匿名関数を実行する際には、pcall
を使用して保護層を提供します。pcall
は関数を保護モードで実行し、例外をキャッチします。正常であればtrue
と実行結果を返し、失敗した場合はfalse
とエラー情報を返します。以下のコードです:
local func1, err = loadstring(s)
local ret, func = pcall(func1)
当然、上記の2つの部分を組み合わせると、完全で操作可能な例が得られます:
resty -e 'local s = [[
return function()
ngx.say("hello world")
end
]]
local func1 = loadstring(s)
local ret, func = pcall(func1)
func()'
さらに一歩進めて、関数を含む文字列s
をユーザーが指定できる形式に変更し、その実行条件を追加することができます。これがFaaSの原型です。ここでは、完全な実装を提供しています。FaaSに興味があり、さらに研究を進めたい場合は、リンクを参照して詳細を学んでください。
機能2: エッジコンピューティング
OpenRestyの動的機能はFaaSに利用でき、スクリプト言語の動的特性を関数レベルにまで洗練させ、エッジコンピューティングで動的役割を果たすことができます。
これらの利点により、OpenRestyの触手をAPIゲートウェイ、WAF(Webアプリケーションファイアウォール)、ウェブサーバーなどのサーバー側から、ユーザーに最も近いエッジノード、例えばIoTデバイス、CDNエッジノード、ルーターなどに拡張することができます。
これは単なる幻想ではありません。OpenRestyはすでに上記の分野で広く使用されています。CDNエッジノードの例として、OpenRestyの最大のユーザーであるCloudflareは、長い間OpenRestyの動的特性を利用してCDNエッジノードの動的制御を実現してきました。
Cloudflareのアプローチは、上記の動的コードロードの原理と似ており、以下のステップに分けられます:
- まず、キーバリューデータベースクラスタから変更されたコードファイルを取得します。方法はバックグラウンドのタイマーポーリングまたは「パブリッシュ-サブスクライブ」モードで監視することができます。
- 次に、ローカルディスク上の古いファイルを更新されたコードファイルに置き換え、
loadstring
とpcall
メソッドを使用してメモリにロードされたキャッシュを更新します。
これにより、次に処理されるクライアントリクエストは更新されたコードロジックを通過します。もちろん、実際のアプリケーションでは、バージョン管理とロールバック、例外処理、ネットワーク中断、エッジノードの再起動など、上記のステップよりも多くの詳細を考慮する必要がありますが、全体的なプロセスは変わりません。
CloudflareのアプローチをCDNエッジノードから他のエッジシナリオに移すことで、多くの計算能力をエッジノードデバイスに動的に割り当てることができます。これにより、エッジノードの計算能力を十分に活用できるだけでなく、ユーザーはリクエストに対してより速い応答を得ることができます。なぜなら、エッジノードが元のデータを処理し、それをリモートサーバーに要約するため、データ転送量が大幅に削減されるからです。
ただし、FaaSとエッジコンピューティングで優れた仕事をするためには、OpenRestyの動的機能は良い基盤に過ぎません。周辺エコシステムの改善やメーカーの参加も考慮する必要があり、これは単なる技術的なカテゴリーではありません。
動的アップストリーム
さて、OpenRestyに戻って、動的アップストリームをどのように実現するかを見てみましょう。lua-resty-core
はngx.balancer
ライブラリを提供し、アップストリームを設定します。これはOpenRestyのbalancer
ステージで実行する必要があります:
balancer_by_lua_block {
local balancer = require "ngx.balancer"
local host = "127.0.0.2"
local port = 8080
local ok, err = balancer.set_current_peer(host, port)
if not ok then
ngx.log(ngx.ERR, "failed to set the current peer: ", err)
return ngx.exit(500)
end
}
set_current_peer
関数はアップストリームのIPアドレスとポートを設定します。ただし、ここでドメイン名はサポートされていないことに注意してください。ドメイン名とIPの解析にはlua-resty-dns
ライブラリを使用する必要があります。
しかし、ngx.balancer
は比較的低レベルです。アップストリームを設定するために使用できますが、動的アップストリームの実現は簡単ではありません。そのため、ngx.balancer
の前に2つの機能が必要です:
- まず、アップストリーム選択アルゴリズムが
consistent hash
かroundrobin
かを決定します。 - 次に、アップストリームのヘルスチェックメカニズムで、不健康なアップストリームを排除し、不健康なアップストリームが健康になったときに再び参加させる必要があります。
OpenRestyの公式ライブラリlua-resty-balancer
には、resty.chash
とresty.roundrobin
の2種類のアルゴリズムが含まれており、最初の機能を完了します。また、lua-resty-upstream-healthcheck
を使用して2番目の機能を試みます。
しかし、まだ2つの問題があります。
1つ目は、最後の1マイルの完全な実装が欠けていることです。ngx.balancer
、lua-resty-balancer
、lua-resty-upstream-healthcheck
を組み合わせて動的アップストリームの機能を実現するには、まだいくつかの作業が必要であり、これがほとんどの開発者を止めています。
2つ目は、lua-resty-upstream-healthcheck
の実装が完全ではないことです。パッシブヘルスチェックしかなく、アクティブヘルスチェックがありません。
クライアントのリクエストがパッシブヘルスチェックをトリガーし、アップストリームの戻り値を分析して健康状態を判断します。クライアントリクエストがない場合、アップストリームが健康かどうかはわかりません。アクティブヘルスチェックはこの欠陥を補うことができます。ngx.timer
を使用して、指定されたアップストリームインターフェースを定期的にポーリングし、健康状態を検出します。
そのため、実際の実践では、通常lua-resty-healthcheck
を使用してアップストリームのヘルスチェックを完了することを推奨します。その利点は、アクティブとパッシブの両方のヘルスチェックを含み、複数のプロジェクトで検証されており、信頼性が高いことです。
さらに、新興のマイクロサービスAPIゲートウェイApache APISIXは、lua-resty-upstream-healthcheck
に基づいて動的アップストリームの完全な実装を行っています。その実装を参照できます。合計400行のコードしかありません。簡単に剥がして自分のプロジェクトに使用できます。
まとめ
OpenRestyの動的機能について、どの分野やシナリオでそれを活用できるでしょうか?また、この章で紹介した各部分の内容をさらに詳細かつ深く分析することもできます。
この記事を共有し、より多くの人と学び、進歩してください。