---
title: "За пределами веб-сервера: Привилегированные процессы и задачи по таймеру - API7.ai"
description: "Привилегированные процессы (Privileged Process) и задачи таймера (Timer Tasks) — это важные функции, которые отличают OpenResty от NGINX и других веб-серверов."
url: "https://api7.ai/ru/learning-center/openresty/privileged-process-and-timer-tasks"
---

# За пределами веб-сервера: Привилегированные процессы и задачи по таймеру

API7.ai

November 3, 2022

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

В предыдущей статье мы представили API OpenResty, `shared dict` и `cosocket`, которые реализуют функциональность в рамках NGINX и веб-серверов, предоставляя программируемый веб-сервер с более низкой стоимостью и более простым в обслуживании реализацией.

Однако OpenResty может делать больше. Давайте рассмотрим несколько функций OpenResty, которые выходят за рамки веб-сервера, и представим их сегодня. Это задачи по таймеру, привилегированный процесс и неблокирующий `ngx.pipe`.

## Задачи по таймеру

В OpenResty иногда нам нужно регулярно выполнять определенные задачи в фоновом режиме, такие как синхронизация данных, очистка логов и т.д. Если бы вы проектировали это, как бы вы это сделали? Самый простой способ, который приходит на ум, — это предоставить внешний API-интерфейс для выполнения этих задач, затем использовать системный `crontab` для вызова `curl` через определенные промежутки времени для доступа к этому интерфейсу и таким образом косвенно реализовать это требование.

Однако это не только будет фрагментированным, но и принесет более высокую сложность в эксплуатации и обслуживании. Поэтому OpenResty предоставляет `ngx.timer` для решения такого рода задач. Вы можете рассматривать `ngx.timer` как клиентский запрос, симулированный OpenResty, чтобы вызвать соответствующую функцию обратного вызова.

Задачи по таймеру в OpenResty можно разделить на следующие два типа.

- `ngx.timer.at` используется для выполнения одноразовых задач по таймеру.
- `ngx.timer.every` используется для выполнения задач по таймеру с фиксированным периодом.

Помните вопрос, который я оставил в конце предыдущей статьи? Вопрос был о том, как обойти ограничение, что `cosocket` нельзя использовать в `init_worker_by_lua`, и ответ — это `ngx.timer`.

Следующий код запускает задачу по таймеру с задержкой `0`. Он запускает функцию обратного вызова `handler`, и в этой функции используется `cosocket` для доступа к веб-сайту.

```
init_worker_by_lua_block {
    local function handler()
        local sock = ngx.socket.tcp()
        local ok, err = sock:connect(“api7.ai", 80)
    end

    local ok, err = ngx.timer.at(0, handler)
}
```

Таким образом, мы обходим ограничение, что `cosocket` нельзя использовать на этом этапе.

Возвращаясь к пользовательскому требованию, которое мы упомянули в начале этого раздела, `ngx.timer.at` не решает задачу периодического выполнения; в приведенном выше примере кода это одноразовая задача.

Итак, как мы можем сделать это периодически? У вас, кажется, есть два варианта на основе API `ngx.timer.at`.

- Вы можете реализовать периодическую задачу самостоятельно, используя бесконечный цикл `while true` в функции обратного вызова, который будет `sleep` на некоторое время после выполнения задачи.
- Вы также можете создать новый таймер в конце функции обратного вызова.

Однако, прежде чем сделать выбор, нам нужно уточнить одну вещь: таймер по сути является запросом, хотя запрос был инициирован не клиентом. Для запроса он должен завершиться после выполнения своей задачи и не может оставаться резидентным. В противном случае это легко приведет к различным утечкам ресурсов.

Поэтому первый вариант использования `while true` для реализации периодических задач ненадежен. Второй вариант возможен, но рекурсивное создание таймеров нелегко понять.

Итак, есть ли лучшее решение? Новый API `ngx.timer.every` в OpenResty специально разработан для решения этой проблемы, и это решение ближе к `crontab`.

Недостаток в том, что у вас никогда не будет возможности отменить задачу по таймеру после ее запуска. В конце концов, `ngx.timer.cancel` все еще является функцией, которую нужно реализовать.

На этом этапе вы столкнетесь с проблемой: таймер работает в фоновом режиме и не может быть отменен; если таймеров много, легко исчерпать системные ресурсы.

Поэтому OpenResty предоставляет две директивы, `lua_max_pending_timers` и `lua_max_running_timers`, чтобы ограничить их. Первая представляет максимальное количество таймеров, ожидающих выполнения, а вторая — максимальное количество текущих выполняющихся таймеров.

Вы также можете использовать Lua API для получения значений текущих ожидающих и выполняющихся задач по таймеру, как показано в следующих двух примерах.

```
content_by_lua_block {
    ngx.timer.at(3, function() end)
    ngx.say(ngx.timer.pending_count())
}
```

Этот код выведет `1`, что означает, что есть одна запланированная задача, ожидающая выполнения.

```
content_by_lua_block {
    ngx.timer.at(0.1, function() ngx.sleep(0.3) end)
    ngx.sleep(0.2)
    ngx.say(ngx.timer.running_count())
}
```

Этот код выведет `1`, что означает, что есть одна запланированная задача, выполняющаяся.

## Привилегированный процесс

Далее рассмотрим привилегированный процесс. Как мы все знаем, NGINX разделен на процесс `Master` и процессы `Worker`, где процессы worker обрабатывают пользовательские запросы. Мы можем получить тип процесса через API `process.type`, предоставленный в `lua-resty-core`. Например, вы можете использовать `resty` для выполнения следующей функции.

```
$ resty -e 'local process = require "ngx.process"
ngx.say("process type:", process.type())'
```

Вы увидите, что он возвращает результат `single` вместо `worker`, что означает, что `resty` запускает NGINX с процессом `Worker`, а не с процессом `Master`. Это правда. В реализации `resty` можно увидеть, что процесс `Master` отключен строкой, подобной этой.

```
master_process off;
```

OpenResty расширяет NGINX, добавляя `privileged agent`, Привилегированный процесс имеет следующие особенности.

- Он не мониторит никакие порты, что означает, что он не предоставляет услуги внешнему миру.
- Он имеет те же привилегии, что и процесс `Master`, что обычно означает привилегии пользователя `root`, позволяя ему выполнять множество задач, которые невозможны для процесса `Worker`.
- Привилегированный процесс может быть открыт только в контексте `init_by_lua`.
- Кроме того, привилегированный процесс имеет смысл только в контексте `init_worker_by_lua`, потому что никакие запросы не инициируются, и они не переходят в контексты `content`, `access` и т.д.

Давайте рассмотрим пример включенного привилегированного процесса.

```
init_by_lua_block {
    local process = require "ngx.process"

    local ok, err = process.enable_privileged_agent()
    if not ok then
        ngx.log(ngx.ERR, "enables privileged agent failed error:", err)
    end
}
```

После включения привилегированного процесса с этим кодом и запуска службы OpenResty мы можем увидеть, что привилегированный процесс теперь является частью процесса NGINX.

```
nginx: master process
nginx: worker process
nginx: privileged agent process
```

Однако, если привилегии выполняются только один раз во время фазы `init_worker_by_lua`, что не очень хорошая идея, как мы можем инициировать привилегированный процесс?

Да, ответ скрыт в только что изученных знаниях. Поскольку он не слушает порты, т.е. не может быть инициирован терминальными запросами, единственный способ инициировать его периодически — использовать `ngx.timer`, который мы только что представили:

```
init_worker_by_lua_block {
    local process = require "ngx.process"

    local function reload(premature)
        local f, err = io.open(ngx.config.prefix() .. "/logs/nginx.pid", "r")
        if not f then
            return
        end
        local pid = f:read()
        f:close()
        os.execute("kill -HUP " .. pid)
    end

    if process.type() == "privileged agent" then
         local ok, err = ngx.timer.every(5, reload)
        if not ok then
            ngx.log(ngx.ERR, err)
        end
    end
}
```

Приведенный выше код реализует возможность отправки сигналов `HUP` мастер-процессу каждые 5 секунд. Естественно, вы можете развить это, чтобы делать более интересные вещи, такие как опрос базы данных для проверки наличия задач для привилегированного процесса и их выполнение. Поскольку привилегированный процесс имеет привилегии `root`, это, очевидно, немного "бэкдорная" программа.

## Неблокирующий `ngx.pipe`

Наконец, рассмотрим неблокирующий `ngx.pipe`, который использует стандартную библиотеку Lua для выполнения внешней командной строки, отправляющей сигнал мастер-процессу в примере кода, который мы только что описали.

```
os.execute("kill -HUP " .. pid)
```

Естественно, эта операция будет блокирующей. Итак, есть ли неблокирующий способ вызова внешних программ в OpenResty? В конце концов, вы знаете, что если вы используете OpenResty как полноценную платформу разработки, а не как веб-сервер, это то, что вам нужно. Для этой цели была создана библиотека `lua-resty-shell`, и использование ее для вызова командной строки является неблокирующим:

```
$ resty -e 'local shell = require "resty.shell"
local ok, stdout, stderr, reason, status =
    shell.run([[echo "hello, world"]])
    ngx.say(stdout)
```

Этот код представляет собой другой способ написания `hello world`, вызывая системную команду `echo` для завершения вывода. Аналогично, вы можете использовать `resty.shell` как альтернативу вызову `os.execute` в Lua.

Мы знаем, что базовая реализация `lua-resty-shell` опирается на API `ngx.pipe` в `lua-resty-core`, поэтому этот пример использует `lua-resty-shell` для вывода `hello world`, используя `ngx.pipe` вместо этого, это будет выглядеть так.

```
$ resty -e 'local ngx_pipe = require "ngx.pipe"
local proc = ngx_pipe.spawn({"echo", "hello world"})
local data, err = proc:stdout_read_line()
ngx.say(data)'
```

Выше приведен базовый код реализации `lua-resty-shell`. Вы можете ознакомиться с документацией `ngx.pipe` и тестовыми примерами для получения дополнительной информации о том, как его использовать. Поэтому я не буду углубляться в это здесь.

## Итог

Вот и все. Мы завершили основное содержание на сегодня. Из вышеперечисленных функций видно, что OpenResty также пытается двигаться в направлении универсальной платформы, одновременно улучшая NGINX, надеясь, что разработчики смогут попытаться унифицировать технологический стек и использовать OpenResty для решения своих задач разработки. Это довольно дружелюбно для эксплуатации и обслуживания, поскольку затраты на обслуживание ниже, если вы развернете OpenResty на нем.

Наконец, я оставлю вам вопрос для размышления. Поскольку может быть несколько процессов `Worker` в NGINX, `timer` будет запускаться один раз для каждого `Worker`, что неприемлемо в большинстве сценариев. Как мы можем гарантировать, что `timer` будет запущен только один раз?

Не стесняйтесь оставить комментарий с вашим решением и поделиться этой статьей с вашими коллегами и друзьями, чтобы мы могли общаться и улучшаться вместе.

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: Как создать API-шлюз для микросервисов с использованием OpenResty](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: Как создать API-шлюз для микросервисов с использованием OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [Задачи по таймеру](#%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B8-%D0%BF%D0%BE-%D1%82%D0%B0%D0%B9%D0%BC%D0%B5%D1%80%D1%83)
- [Привилегированный процесс](#%D0%BF%D1%80%D0%B8%D0%B2%D0%B8%D0%BB%D0%B5%D0%B3%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9-%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81)
- [Неблокирующий `ngx.pipe`](#%D0%BD%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B8%D0%B9-ngxpipe)
- [Итог](#%D0%B8%D1%82%D0%BE%D0%B3)
