वेब सर्वर से परे: प्रिविलेज्ड प्रोसेस और टाइमर टास्क्स
API7.ai
November 3, 2022
पिछले लेख में, हमने OpenResty APIs, 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 API के आधार पर दो विकल्प प्रतीत होते हैं।
- आप कॉलबैक फ़ंक्शन में
while trueअनंत लूप का उपयोग करके स्वयं आवधिक कार्य को लागू कर सकते हैं जो कार्य को निष्पादित करने के बाद कुछ समय के लिएsleepकरता है। - आप कॉलबैक फ़ंक्शन के अंत में एक नया टाइमर भी बना सकते हैं।
हालांकि, चुनाव करने से पहले, हमें एक बात स्पष्ट करने की आवश्यकता है: टाइमर अनिवार्य रूप से एक अनुरोध है, हालांकि यह अनुरोध क्लाइंट द्वारा शुरू नहीं किया गया था। अनुरोध के लिए, इसे अपना कार्य पूरा करने के बाद बाहर निकलना होगा और इसे हमेशा के लिए रहने की अनुमति नहीं है। अन्यथा, यह विभिन्न प्रकार के संसाधन रिसाव का कारण बन सकता है।
इसलिए, while true का उपयोग करके आवधिक कार्य को लागू करने का पहला समाधान अविश्वसनीय है। दूसरा समाधान संभव है लेकिन यह पुनरावर्ती रूप से टाइमर बनाता है, जो समझने में आसान नहीं है।
तो, क्या कोई बेहतर समाधान है? OpenResty के पीछे नया ngx.timer.every API विशेष रूप से इस समस्या को हल करने के लिए डिज़ाइन किया गया है, और यह 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 प्रोसेस में विभाजित किया गया है, जहां वर्कर प्रोसेस उपयोगकर्ता अनुरोधों को संभालते हैं। हम lua-resty-core में प्रदान किए गए process.type API के माध्यम से प्रोसेस के प्रकार को प्राप्त कर सकते हैं। उदाहरण के लिए, आप निम्नलिखित फ़ंक्शन को चलाने के लिए 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उपयोगकर्ता के विशेषाधिकार होते हैं, जो इसे वर्कर प्रोसेस के लिए असंभव कई कार्य करने की अनुमति देता है। -
प्रिविलेज्ड प्रोसेस केवल
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 }
उपरोक्त कोड हर 5 सेकंड में मास्टर प्रोसेस को HUP सिग्नल भेजने की क्षमता को लागू करता है। स्वाभाविक रूप से, आप इस पर निर्माण करके और अधिक रोमांचक चीजें कर सकते हैं, जैसे डेटाबेस को पोल करना कि क्या प्रिविलेज्ड प्रोसेस के लिए कोई कार्य हैं और उन्हें निष्पादित करना। चूंकि प्रिविलेज्ड प्रोसेस में 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 कमांड का उपयोग करके आउटपुट को पूरा करना। इसी तरह, आप Lua में os.execute कॉल के विकल्प के रूप में resty.shell का उपयोग कर सकते हैं।
हम जानते हैं कि lua-resty-shell का अंतर्निहित कार्यान्वयन lua-resty-core में ngx.pipe API पर निर्भर करता है, इसलिए यह उदाहरण 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 तैनात करते हैं, रखरखाव लागत कम होती है।
अंत में, मैं आपको एक विचारोत्तेजक प्रश्न छोड़ता हूं। चूंकि कई NGINX Worker हो सकते हैं, timer प्रत्येक Worker के लिए एक बार चलेगा, जो अधिकांश परिदृश्यों में अस्वीकार्य है। हम कैसे सुनिश्चित कर सकते हैं कि timer केवल एक बार चले?
अपने समाधान के साथ एक टिप्पणी छोड़ने के लिए स्वतंत्र महसूस करें, और इस लेख को अपने सहयोगियों और दोस्तों के साथ साझा करने के लिए स्वतंत्र महसूस करें ताकि हम एक साथ संवाद और सुधार कर सकें।