Перейти к содержанию

limit-rate: Lua модуль для ограничения скорости запросов для nginx-module-lua, использующий метод "токенов в ведре"

Установка

Если вы еще не настроили подписку на репозиторий RPM, зарегистрируйтесь. После этого вы можете продолжить с следующими шагами.

CentOS/RHEL 7 или Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-limit-rate

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-limit-rate

Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что nginx-module-lua установлен.

Этот документ описывает lua-resty-limit-rate v0.1, выпущенный 25 октября 2018 года.


http {
    lua_shared_dict my_limit_rate_store 100m;
    lua_shared_dict my_locks 100k;

    server {
        location / {
            access_by_lua_block {
                local limit_rate = require "resty.limit.rate"

                local lim, err = limit_rate.new("my_limit_rate_store", 500, 10, 3, 200, {
                    lock_enable = true, -- использовать lua-resty-lock
                    locks_shdict_name = "my_locks",
                })

                if not lim then
                    ngx.log(ngx.ERR,
                            "не удалось создать объект resty.limit.rate: ", err)
                    return ngx.exit(500)
                end

                -- следующий вызов должен выполняться для каждого запроса.
                -- здесь мы используем удаленный (IP) адрес в качестве ключа ограничения
                local key = ngx.var.binary_remote_addr
                local delay, err = lim:incoming(key, true)
                -- local delay, err = lim:take(key, 1, true)
                if not delay then
                    if err == "rejected" then
                        return ngx.exit(503)
                    end
                    ngx.log(ngx.ERR, "не удалось взять токен: ", err)
                    return ngx.exit(500)
                end

                if delay >= 0.001 then
                    -- 2 возвращаемое значение содержит текущее количество доступных токенов
                    -- запросов для указанного ключа
                    local avail = err

                    ngx.sleep(delay)
                end
            }

            # обработчик контента здесь. если это content_by_lua, то вы можете
            # объединить код Lua выше в access_by_lua в ваш обработчик content_by_lua
            # чтобы сэкономить немного времени ЦП.
        }

        location /take_available {
            access_by_lua_block {
                local limit_rate = require "resty.limit.rate"

                -- глобально 20r/s 6000r/5m
                local lim_global = limit_rate.new("my_limit_rate_store", 100, 6000, 2, nil, {
                    lock_enable = true,
                    locks_shdict_name = "my_locks",
                })

                if not lim_global then
                    return ngx.exit(500)
                end

                -- индивидуально 2r/s 600r/5m
                local lim_single = limit_rate.new("my_limit_rate_store", 500, 600, 1, nil, {
                    locks_shdict_name = "my_locks",
                })

                if not lim_single then
                    return ngx.exit(500)
                end

                local t0, err = lim_global:take_available("__global__", 1)
                if not t0 then
                    ngx.log(ngx.ERR, "не удалось взять глобальный: ", err)
                    return ngx.exit(500)
                end

                -- здесь мы используем userid в качестве ключа ограничения
                local key = ngx.var.arg_userid or "__single__"

                local t1, err = lim_single:take_available(key, 1)
                if not t1 then
                    ngx.log(ngx.ERR, "не удалось взять индивидуальный: ", err)
                    return ngx.exit(500)
                end

                if t0 == 1 then
                    return -- глобальное ведро не голодное
                else
                    if t1 == 1 then
                        return -- индивидуальное ведро не голодное
                    else
                        return ngx.exit(503)
                    end
                end
            }
        }
    }
}

Описание

Этот модуль предоставляет API, чтобы помочь пользователям OpenResty/ngx_lua ограничивать скорость запросов, используя метод "токенов в ведре".

Если вы хотите использовать несколько различных экземпляров этого класса одновременно или использовать один экземпляр этого класса с экземплярами других классов (таких как resty.limit.conn), то вы должны использовать модуль resty.limit.traffic для их объединения.

Основное различие между этим модулем и resty.limit.req:

  • resty.limit.req ограничивает скорость запросов, используя метод "протекающего ведра", этот модуль использует метод "токенов в ведре".

Основное различие между этим модулем и resty.limit.count:

  • resty.limit.count предлагает простую ментальную модель, которая ограничивает скорость запросов фиксированным количеством запросов в заданном временном окне, но иногда может пропустить в два раза больше разрешенных запросов в минуту. Например, если наш лимит скорости составляет 10 запросов в минуту, и пользователь сделал 10 запросов в 10:00:59, он может сделать еще 10 запросов в 10:01:00, потому что новый счетчик начинается в начале каждой минуты. В этом случае этот модуль способен контролировать более точно и плавно.

Методы

new

синтаксис: obj, err = class.new(shdict_name, interval, capacity, quantum?, max_wait?, opts?)

Создает объект этого класса. Значение class возвращается вызовом require "resty.limit.rate".

Метод возвращает новое ведро токенов, которое заполняется со скоростью quantum токенов каждые interval, до заданной максимальной capacity. Ведро изначально полное.

Этот метод принимает следующие аргументы и необязательную таблицу опций opts:

  • shdict_name — это имя зоны shm lua_shared_dict.

    Рекомендуется использовать отдельные зоны shm для различных типов ограничителей.

  • interval — это время, проходящее между добавлением токенов, в миллисекундах.

  • capacity — максимальное количество токенов, которое может храниться в ведре.

  • quantum — количество токенов, добавляемых в ведро за один интервал, этот аргумент является необязательным, по умолчанию 1.

  • max_wait — максимальное время, которое мы будем ждать, чтобы добавить достаточное количество токенов, в миллисекундах, этот аргумент является необязательным, по умолчанию nil, что означает бесконечность.

Таблица опций принимает следующие параметры:

  • lock_enable Когда включено, обновление состояния shdict между несколькими процессами-рабочими nginx является атомарным; в противном случае будет иметь место (небольшое) окно гонки между поведением "чтение-затем-запись", по умолчанию false. См. lua-resty-lock для получения дополнительных сведений.

  • locks_shdict_name Указывает имя общего словаря (созданного с помощью lua_shared_dict) для блокировки, по умолчанию locks.

В случае ошибки этот метод возвращает nil и строку, описывающую ошибку (например, неправильное имя lua_shared_dict).

incoming

синтаксис: delay, err = obj:take(key, commit)

Запускает новое событие входящего запроса и вычисляет необходимую задержку (если такая имеется) для текущего запроса по указанному ключу или решает, следует ли немедленно отклонить его.

Похоже на метод take, но этот метод берет только один токен из ведра за раз.

Этот метод принимает следующие аргументы:

  • key — это ключ, указанный пользователем для ограничения скорости.

    Обратите внимание, что этот модуль не добавляет префиксы или суффиксы к ключу пользователя, поэтому ответственность за обеспечение уникальности ключа в зоне shm lua_shared_dict лежит на пользователе.

  • commit — это логическое значение. Если установлено в true, объект фактически запишет событие в shm-зону, поддерживающую текущий объект; в противном случае это будет просто "пробный запуск" (что является значением по умолчанию).

set_max_wait

синтаксис: obj:set_max_wait(max_wait?)

Перезаписывает порог max_wait, как указано в методе new.

take

синтаксис: delay, err = obj:take(key, count, commit)

Метод берет count токенов из ведра без блокировки.

Этот метод принимает следующие аргументы:

  • key — это ключ, указанный пользователем для ограничения скорости.

    Обратите внимание, что этот модуль не добавляет префиксы или суффиксы к ключу пользователя, поэтому ответственность за обеспечение уникальности ключа в зоне shm lua_shared_dict лежит на пользователе.

  • count — количество токенов для удаления.

  • commit — это логическое значение. Если установлено в true, объект фактически запишет событие в shm-зону, поддерживающую текущий объект; в противном случае это будет просто "пробный запуск" (что является значением по умолчанию).

Возвращаемые значения зависят от следующих случаев:

  1. Если значение max_wait, указанное в методе new или set_max_wait, метод будет брать токены из ведра только в том случае, если время ожидания токенов не превышает max_wait, и возвращает время, которое вызывающий должен ждать, пока токены фактически не станут доступны, в противном случае возвращает nil и строку ошибки "rejected".

  2. Если значение max_wait равно nil, возвращает время, которое вызывающий должен ждать, пока токены фактически не станут доступны.

Кроме того, этот метод также возвращает второе возвращаемое значение, указывающее количество текущих доступных токенов на данный момент.

Если произошла ошибка (например, сбой при доступе к зоне shm lua_shared_dict, поддерживающей текущий объект), то этот метод возвращает nil и строку, описывающую ошибку.

Этот метод никогда не спит сам по себе. Он просто возвращает задержку, если это необходимо, и требует от вызывающего позже вызвать метод ngx.sleep для ожидания.

take_available

синтаксис: count, err = obj:take_available(key, count)

Метод берет до count немедленно доступных токенов из ведра. Он возвращает количество удаленных токенов или ноль, если доступных токенов нет. Он не блокирует.

Этот метод принимает следующие аргументы:

  • key — это ключ, указанный пользователем для ограничения скорости.

    Обратите внимание, что этот модуль не добавляет префиксы или суффиксы к ключу пользователя, поэтому ответственность за обеспечение уникальности ключа в зоне shm lua_shared_dict лежит на пользователе.

  • count — количество токенов для удаления.

Если произошла ошибка (например, сбой при доступе к зоне shm lua_shared_dict, поддерживающей текущий объект), то этот метод возвращает nil и строку, описывающую ошибку.

uncommit

синтаксис: ok, err = obj:uncommit(key)

Этот метод пытается отменить фиксацию вызова incoming. Это просто приближение и должно использоваться с осторожностью. Этот метод в основном предназначен для использования в Lua модуле resty.limit.traffic при комбинировании нескольких ограничителей одновременно.

Гранулярность ограничения

Ограничение работает на уровне отдельного экземпляра сервера NGINX (включая все его рабочие процессы). Благодаря механизму shm мы можем дешево делиться состоянием между всеми рабочими процессами в одном экземпляре сервера NGINX.

См. также

GitHub

Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-limit-rate.