ما وراء خادم الويب: العمليات المميزة ومهام المؤقت
API7.ai
November 3, 2022
في المقال السابق، قدمنا واجهات برمجة 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
يعمل مرة واحدة فقط؟
لا تتردد في ترك تعليق بحلولك، ولا تتردد في مشاركة هذه المقالة مع زملائك وأصدقائك حتى نتمكن من التواصل والتحسين معًا.