---
title: "OpenResty 中的动态速率限制 - API7.ai"
description: "`lua-resty-limit-traffic` の `limit-req`、`limit-count`、`limit-conn`、および `limit.traffic` の実装方法を学び、それらを使用してレート制限を行う方法を見ていきましょう。"
url: "https://api7.ai/ja/learning-center/openresty/flexible-dynamic-rate-limiting"
---

# OpenResty 中的动态速率限制

API7.ai

January 6, 2023

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

前回の記事では、バーストトラフィックを扱うための一般的なアルゴリズムである`leaky bucket`と`token bucket`を紹介し、NGINX設定を使用してリクエストのレートリミットを行う方法を学びました。しかし、NGINX設定を使用するのはあくまで使用可能なレベルであり、実用的になるまでにはまだ長い道のりがあります。

最初の問題は、レートリミットのキーがNGINX変数の範囲に限定されており、柔軟に設定できないことです。例えば、異なる地域や異なるクライアントチャネルに対して異なる速度制限の閾値を設定する方法はありませんが、これはNGINXでは一般的な要件です。

もう一つの大きな問題は、レートを動的に調整できないことであり、変更のたびにNGINXサービスを再読み込みする必要があります。その結果、異なる期間に基づいて速度を制限することは、外部スクリプトを通じて不格好に実装するしかありません。

技術はビジネスに奉仕し、同時にビジネスが技術を推進することを理解することが重要です。NGINXが誕生した当時、設定を動的に調整する必要性はほとんどなく、リバースプロキシ、ロードバランシング、低メモリ使用量などのニーズがNGINXの成長を牽引していました。技術アーキテクチャと実装の観点から、モバイルインターネット、IoT、マイクロサービスなどのシナリオで動的かつ細かい制御の需要が爆発的に増加することを誰も予測できませんでした。

OpenRestyのLuaスクリプトの使用は、NGINXのこの分野での不足を補い、効果的な補完となります。これが、OpenRestyがNGINXの代替として広く使用されている理由です。次のいくつかの記事では、OpenRestyでのより動的なシナリオと例を紹介し続けます。まず、OpenRestyを使用して動的なレートリミットを実装する方法を見てみましょう。

OpenRestyでは、[lua-resty-limit-traffic](https://github.com/openresty/lua-resty-limit-traffic)を使用してトラフィックを制限することを推奨しています。これには`limit-req`（リクエストレートの制限）、`limit-count`（リクエスト数の制限）、`limit-conn`（同時接続数の制限）が含まれており、`limit.traffic`を提供してこれら3つの方法を集約します。

## リクエストレートの制限

まず、`limit-req`を見てみましょう。これは、リクエストのレートを制限するためにリーキーバケットアルゴリズムを使用します。

前回のセクションでは、このrestyライブラリのリーキーバケットアルゴリズムの主要な実装コードを簡単に紹介しましたが、今回はこのライブラリの使用方法を学びます。まず、以下のサンプルコードを見てみましょう。

```
resty --shdict='my_limit_req_store 100m' -e 'local limit_req = require "resty.limit.req"
local lim, err = limit_req.new("my_limit_req_store", 200, 100)
local delay, err = lim:incoming("key", true)
if not delay then
    if err == "rejected" then
        return ngx.exit(503)
    end
    return ngx.exit(500)
end

 if delay >= 0.001 then
    ngx.sleep(delay)
end'
```

`lua-resty-limit-traffic`は`shared dict`を使用してキーを保存およびカウントするため、`limit-req`を使用する前に`my_limit_req_store`の`100m`スペースを宣言する必要があります。これは`limit-conn`と`limit-count`でも同様で、それぞれ別の`shared dict`スペースを区別する必要があります。

```
limit_req.new("my_limit_req_store", 200, 100)
```

上記のコード行は最も重要なコード行の1つです。これは、`my_limit_req_store`という`shared dict`を使用して統計を保存し、1秒あたりのレートを`200`に設定することを意味します。これにより、`200`を超えるが`300`未満（この値は`200 + 100`から計算されます）の場合、キューに入れられ、`300`を超える場合は拒否されます。

設定が完了したら、クライアントからのリクエストを処理する必要があります。`lim: incoming("key", true)`はこれを行うためのものです。`incoming`には2つのパラメータがあり、詳細に読む必要があります。

最初のパラメータは、レートリミットのためのユーザー指定のキーで、上記の例では文字列定数であり、すべてのクライアントに対して均一にレートリミットを行うことを意味します。異なる地域やチャネルに応じてレートを制限したい場合は、両方の情報をキーとして使用するだけで簡単に実現できます。以下は、この要件を実現するための疑似コードです。

```
local  province = get_ province(ngx.var.binary_remote_addr)
local channel = ngx.req.get_headers()["channel"]
local key = province .. channel
lim:incoming(key, true)
```

もちろん、キーの意味や`incoming`を呼び出す条件をカスタマイズすることもできるため、非常に柔軟なレートリミット効果を得ることができます。

`incoming`関数の2番目のパラメータを見てみましょう。これはブール値で、デフォルトは`false`であり、リクエストが`shared dict`に記録されず、統計に含まれないことを意味します。これは単なる演習です。`true`に設定すると、実際の効果があります。したがって、ほとんどの場合、明示的に`true`に設定する必要があります。

このパラメータが存在する理由について疑問に思うかもしれません。ホスト名をキーとする1つの`limit-req`インスタンスと、クライアントのIPアドレスをキーとする別の`limit-req`インスタンスを設定するシナリオを考えてみましょう。その後、クライアントリクエストが処理されると、これらの2つのインスタンスの`incoming`メソッドが順番に呼び出されます。以下の疑似コードで示されています。

```
local limiter_one, err = limit_req.new("my_limit_req_store", 200, 100)
local limiter_two, err = limit_req.new("my_limit_req_store", 20, 10)

limiter_one :incoming(ngx.var.host, true)
limiter_two:incoming(ngx.var.binary_remote_addr, true)
```

ユーザーのリクエストが`limiter_one`の閾値検出を通過しても、`limiter_two`の検出で拒否された場合、`limiter_one:incoming`関数呼び出しは演習と見なすべきであり、カウントする必要はありません。

この場合、上記のコードロジックは十分に厳密ではありません。すべてのリミッターを事前に演習する必要があり、クライアントリクエストを拒否できるリミッターの閾値がトリガーされた場合、直接返すことができます。

```
for i = 1, n do
    local lim = limiters[i]
    local delay, err = lim:incoming(keys[i], i == n)
    if not delay then
        return nil, err
    end
end
```

これが`incoming`関数の2番目の引数の目的です。このコードは`limit.traffic`モジュールのコアコードであり、複数のレートリミッターを組み合わせるために使用されます。

## リクエスト数の制限

次に、リクエスト数を制限する`limit.count`ライブラリを見てみましょう。これはGitHub API Rate Limitingのように、固定時間ウィンドウ内でのユーザーリクエスト数を制限します。いつものように、サンプルコードから始めましょう。

```
local limit_count = require "resty.limit.count"

local lim, err = limit_count.new("my_limit_count_store", 5000, 3600)

local key = ngx.req.get_headers()["Authorization"]
local delay, remaining = lim:incoming(key, true)
```

`limit.count`と`limit.req`は同様に使用されます。まず、`nginx.conf`で`shared dict`を定義します。

```
lua_shared_dict my_limit_count_store 100m;
```

次に、リミッターオブジェクトを`new`し、最後に`incoming`関数を使用して判断および処理します。

ただし、`limit-count`の`incoming`関数の2番目の戻り値は残りの呼び出し回数を表し、これに応じてレスポンスヘッダーにフィールドを追加して、クライアントに適切な指示を与えることができます。

```
ngx.header["X-RateLimit-Limit"] = "5000"
ngx.header["X-RateLimit-Remaining"] = remaining
```

## 同時接続数の制限

`limit.conn`は、同時接続数を制限するためのライブラリです。前述の2つのライブラリとは異なり、特別な`leaving API`を持っています。ここで簡単に説明します。

リクエストレートとリクエスト数の制限は、前述のように`access`フェーズで直接行うことができます。一方、同時接続数の制限は、`access`フェーズで閾値を超えているかどうかを判断するだけでなく、`log`フェーズで`leaving` APIを呼び出す必要があります。

```
log_by_lua_block {
    local latency = tonumber(ngx.var.request_time) - ctx.limit_conn_delay
    local key = ctx.limit_conn_key

    local conn, err = lim:leaving(key, latency)
}
```

ただし、このAPIのコアコードは非常にシンプルで、以下のコード行で接続数を1つ減らします。`log`フェーズでクリーンアップしないと、接続数が増え続け、すぐに同時接続数の閾値に達します。

```
local conn, err = dict:incr(key, -1)
```

## レートリミッターの組み合わせ

これで、これら3つの方法の紹介は終わりです。最後に、`limit.rate`、`limit.conn`、`limit.count`を組み合わせる方法を見てみましょう。ここでは、`limit.traffic`の`combine`関数を使用する必要があります。

```
local lim1, err = limit_req.new("my_req_store", 300, 200)
local lim2, err = limit_req.new("my_req_store", 200, 100)
local lim3, err = limit_conn.new("my_conn_store", 1000, 1000, 0.5)

local limiters = {lim1, lim2, lim3}
local host = ngx.var.host
local client = ngx.var.binary_remote_addr
local keys = {host, client, client}

local delay, err = limit_traffic.combine(limiters, keys, states)
```

このコードは、今までに得た知識で簡単に理解できるはずです。`combine`関数のコアコードは、`limit.rate`の分析で既に述べたように、主に`drill`関数と`uncommit`関数の助けを借りて実装されています。この組み合わせにより、複数のリミッターに対して異なる閾値とキーを設定し、より複雑なビジネス要件を実現できます。

## まとめ

`limit.traffic`は、今日紹介した3つのレートリミッターだけでなく、`incoming`と`uncommit` APIを持つレートリミッターであれば、`limit.traffic`の`combine`関数で管理できます。

最後に、宿題を出します。以前紹介したトークンとバケットのレートリミッターを組み合わせた例を書くことができますか？コメント欄に答えを書いて私と議論してください。また、この記事を同僚や友人と共有して、一緒に学び、交流することも歓迎します。

Share article link

Share

Related Posts

[![OpenResty FAQ | OpenRestyの実践的な使い方](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [OpenResty FAQ | OpenRestyの実践的な使い方](https://api7.ai/learning-center/openresty/faq-5)

[![第3部：OpenRestyを使用してマイクロサービスAPIゲートウェイを構築する方法](https://static.api7.ai/uploads/2023/02/07/3XJXY9Jy_45.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

## [第3部：OpenRestyを使用してマイクロサービスAPIゲートウェイを構築する方法](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [リクエストレートの制限](#%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%83%88%E3%81%AE%E5%88%B6%E9%99%90)
- [リクエスト数の制限](#%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E6%95%B0%E3%81%AE%E5%88%B6%E9%99%90)
- [同時接続数の制限](#%E5%90%8C%E6%99%82%E6%8E%A5%E7%B6%9A%E6%95%B0%E3%81%AE%E5%88%B6%E9%99%90)
- [レートリミッターの組み合わせ](#%E3%83%AC%E3%83%BC%E3%83%88%E3%83%AA%E3%83%9F%E3%83%83%E3%82%BF%E3%83%BC%E3%81%AE%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B)
- [まとめ](#%E3%81%BE%E3%81%A8%E3%82%81)
