---
title: "ما وراء خادم الويب: العمليات المميزة ومهام المؤقت - API7.ai"
description: "المهام المميزة والمهام الموقتة هي ميزات مهمة تميز OpenResty عن NGINX وخوادم الويب الأخرى."
url: "https://api7.ai/ar/learning-center/openresty/privileged-process-and-timer-tasks"
---

# ما وراء خادم الويب: العمليات المميزة ومهام المؤقت

API7.ai

November 3, 2022

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

في المقال السابق، قدمنا واجهات برمجة 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` لا يعالج الحاجة إلى التشغيل الدوري؛ في مثال الكود أعلاه، إنها مهمة لمرة واحدة.

إذن، كيف نفعل ذلك بشكل دوري؟ يبدو أن لديك خيارين بناءً على واجهة برمجة التطبيقات `ngx.timer.at`.

- يمكنك تنفيذ المهمة الدورية بنفسك باستخدام حلقة لا نهائية `while true` في وظيفة رد الاتصال التي `sleep` لفترة بعد تنفيذ المهمة.
- يمكنك أيضًا إنشاء مؤقت جديد في نهاية وظيفة رد الاتصال.

ومع ذلك، قبل اتخاذ قرار، هناك شيء واحد نحتاج إلى توضيحه: المؤقت هو في الأساس طلب، على الرغم من أن الطلب لم يتم بدؤه من قبل العميل. بالنسبة للطلب، يجب أن يخرج بعد إكمال مهمته ولا يمكن أن يكون مقيمًا دائمًا. وإلا، فمن السهل أن يتسبب في تسرب موارد متنوعة.

لذلك، الحل الأول باستخدام `while true` لتنفيذ المهام الدورية غير موثوق. الحل الثاني ممكن ولكنه ينشئ مؤقتات بشكل متكرر، مما يجعل فهمه صعبًا.

إذن، هل هناك حل أفضل؟ واجهة برمجة التطبيقات الجديدة `ngx.timer.every` خلف OpenResty مصممة خصيصًا لحل هذه المشكلة، وهي حل أقرب إلى `crontab`.

الجانب السلبي هو أنه لا توجد فرصة لإلغاء مهمة المؤقت بعد بدئها. بعد كل شيء، `ngx.timer.cancel` لا تزال وظيفة قيد التنفيذ.

في هذه المرحلة، ستواجه مشكلة: المؤقت يعمل في الخلفية ولا يمكن إلغاؤه؛ إذا كان هناك العديد من المؤقتات، فمن السهل استنفاد موارد النظام.

لذلك، يوفر OpenResty توجيهين، `lua_max_pending_timers` و `lua_max_running_timers` للحد منها. الأول يمثل الحد الأقصى لعدد المؤقتات التي تنتظر التنفيذ، والثاني يمثل الحد الأقصى لعدد المؤقتات التي تعمل حاليًا.

يمكنك أيضًا استخدام واجهة برمجة التطبيقات Lua للحصول على قيم مهام المؤقت التي تنتظر وتعمل حاليًا، كما هو موضح في المثالين التاليين.

```
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`، حيث تعالج عمليات العامل طلبات المستخدم. يمكننا الحصول على نوع العملية من خلال واجهة برمجة التطبيقات `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` إلى عملية `master` كل 5 ثوانٍ. بشكل طبيعي، يمكنك البناء على هذا للقيام بأشياء أكثر إثارة، مثل استطلاع قاعدة البيانات لمعرفة ما إذا كانت هناك مهام للعملية المميزة وتنفيذها. نظرًا لأن العملية المميزة لديها صلاحيات `root`، فإن هذا يعتبر نوعًا من "برنامج الباب الخلفي".

## `ngx.pipe` غير المعوق

أخيرًا، دعونا نلقي نظرة على `ngx.pipe` غير المعوق، الذي يستخدم مكتبة Lua القياسية لتنفيذ أمر خارجي يرسل إشارة إلى عملية `Master` في مثال الكود الذي وصفناه للتو.

```
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` يعتمد على واجهة برمجة التطبيقات `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 | كيفية استخدام 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 | كيفية استخدام OpenResty في الممارسة العملية](https://api7.ai/learning-center/openresty/faq-5)

[![الجزء الثالث: كيفية بناء بوابة 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)

## [الجزء الثالث: كيفية بناء بوابة API للخدمات المصغرة باستخدام OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [مهام المؤقت](#%D9%85%D9%87%D8%A7%D9%85-%D8%A7%D9%84%D9%85%D8%A4%D9%82%D8%AA)
- [العملية المميزة](#%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D9%85%D9%85%D9%8A%D8%B2%D8%A9)
- [`ngx.pipe` غير المعوق](#ngxpipe-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%B9%D9%88%D9%82)
- [الخلاصة](#%D8%A7%D9%84%D8%AE%D9%84%D8%A7%D8%B5%D8%A9)
