OpenResty FAQ | डायनामिक लोड, NYI, और शेयर्ड डिक्ट की कैशिंग

API7.ai

January 19, 2023

OpenResty (NGINX + Lua)

Openresty लेख श्रृंखला अब तक अपडेट की गई है, और प्रदर्शन अनुकूलन के बारे में जो भाग है, वह सब हमने सीखा है। आपको बधाई हो कि आप पीछे नहीं रहे, अभी भी सक्रिय रूप से सीख और अभ्यास कर रहे हैं, और उत्साहपूर्वक अपने विचार साझा कर रहे हैं।

हमने बहुत सारे अधिक विशिष्ट और दिलचस्प प्रश्न एकत्र किए हैं, और यहां उनमें से पांच पर एक नज़र डालते हैं।

प्रश्न 1: Lua मॉड्यूल के डायनामिक लोडिंग को कैसे पूरा करें?

विवरण: मेरे पास OpenResty में लागू डायनामिक लोडिंग के बारे में एक प्रश्न है। फ़ाइल को बदलने के बाद नई फ़ाइल को लोड करने के लिए loadstring फ़ंक्शन का उपयोग कैसे कर सकता हूं? मैं समझता हूं कि loadstring केवल स्ट्रिंग्स को लोड कर सकता है, इसलिए अगर मैं एक lua फ़ाइल/मॉड्यूल को पुनः लोड करना चाहता हूं, तो OpenResty में इसे कैसे कर सकता हूं?

जैसा कि हम जानते हैं, loadstring का उपयोग एक स्ट्रिंग को लोड करने के लिए किया जाता है, जबकि loadfile एक निर्दिष्ट फ़ाइल को लोड कर सकता है, उदाहरण के लिए: loadfile("foo.lua")। ये दोनों कमांड एक ही परिणाम प्राप्त करते हैं। Lua मॉड्यूल को लोड करने के बारे में, यहां एक उदाहरण दिया गया है:

resty -e 'local s = [[ local ngx = ngx local _M = {} function _M.f() ngx.say("hello world") end return _M ]] local lua = loadstring(s) local ret, func = pcall(lua) func.f()'

स्ट्रिंग s की सामग्री एक पूर्ण Lua मॉड्यूल है। इसलिए, जब आप इस मॉड्यूल के कोड में कोई परिवर्तन पाते हैं, तो आप loadstring या loadfile के साथ पुनः लोड कर सकते हैं। इस तरह, इसमें मौजूद फ़ंक्शन और वेरिएबल्स इसके साथ अपडेट हो जाएंगे।

इसे एक कदम आगे बढ़ाते हुए, आप परिवर्तनों को प्राप्त करने और पुनः लोड करने को code_loader फ़ंक्शन के साथ लपेट सकते हैं।

local func = code_loader(name)

इससे कोड अपडेट बहुत अधिक संक्षिप्त हो जाता है। साथ ही, code_loader आमतौर पर s को कैश करने के लिए lru cache का उपयोग करता है ताकि हर बार loadstring को कॉल करने से बचा जा सके।

प्रश्न 2: OpenResty ब्लॉकिंग ऑपरेशन को प्रतिबंधित क्यों नहीं करता?

विवरण: इन वर्षों में, मैंने हमेशा सोचा है, क्योंकि ये ब्लॉकिंग कॉल आधिकारिक तौर पर हतोत्साहित की जाती हैं, तो क्यों नहीं इन्हें अक्षम कर दिया जाए? या एक फ्लैग जोड़ दिया जाए जो उपयोगकर्ता को इसे अक्षम करने का विकल्प दे?

यह मेरी व्यक्तिगत राय है। पहले, क्योंकि OpenResty के आसपास का इकोसिस्टम पूर्ण नहीं है, कभी-कभी हमें कुछ फ़ंक्शनलिटी को लागू करने के लिए ब्लॉकिंग लाइब्रेरीज़ को कॉल करना पड़ता है। उदाहरण के लिए, संस्करण 1.15.8 से पहले, आपको बाहरी कमांड को कॉल करने के लिए lua-resty-shell के बजाय Lua लाइब्रेरी os.execute का उपयोग करना पड़ता था। उदाहरण के लिए, OpenResty में, फ़ाइलों को पढ़ने और लिखने के लिए अभी भी केवल Lua I/O लाइब्रेरी का उपयोग किया जा सकता है, और कोई नॉन-ब्लॉकिंग विकल्प नहीं है।

दूसरा, OpenResty ऐसे अनुकूलन के प्रति बहुत सतर्क है। उदाहरण के लिए, lua-resty-core को लंबे समय से विकसित किया गया है, लेकिन इसे डिफ़ॉल्ट रूप से कभी भी चालू नहीं किया गया है, आपको मैन्युअल रूप से require 'resty.core' को कॉल करना पड़ता था। यह नवीनतम 1.15.8 रिलीज़ तक चालू नहीं किया गया था।

अंत में, OpenResty के रखरखावकर्ता ब्लॉकिंग कॉल को मानकीकृत करने के लिए कंपाइलर और DSL के माध्यम से अत्यधिक अनुकूलित Lua कोड को स्वचालित रूप से उत्पन्न करने को प्राथमिकता देते हैं। इसलिए, OpenResty प्लेटफ़ॉर्म पर ही फ्लैग विकल्प जैसा कुछ करने का कोई प्रयास नहीं किया गया है। बेशक, मुझे यकीन नहीं है कि यह दिशा समस्या को हल कर सकती है या नहीं।

एक बाहरी डेवलपर के दृष्टिकोण से, अधिक व्यावहारिक समस्या यह है कि ऐसे ब्लॉकिंग से कैसे बचा जाए। हम Lua कोड डिटेक्शन टूल्स, जैसे luacheck, का विस्तार कर सकते हैं ताकि सामान्य ब्लॉकिंग ऑपरेशन को ढूंढ और सतर्क किया जा सके, या हम _G को रीराइट करके कुछ फ़ंक्शन को सीधे अक्षम या रीराइट कर सकते हैं, उदाहरण के लिए:

resty -e '_G.ngx.print = function() ngx.say("hello") end ngx.print()' # hello

इस नमूना कोड के साथ, आप ngx.print फ़ंक्शन को सीधे रीराइट कर सकते हैं।

प्रश्न 3: LuaJIT के NYI के ऑपरेशन का प्रदर्शन पर क्या प्रभाव पड़ता है?

विवरण: loadstring LuaJIT के NYI सूची में never दिखाता है। क्या इसका प्रदर्शन पर बड़ा प्रभाव पड़ेगा?

LuaJIT के NYI के बारे में, हमें बहुत सख्त होने की आवश्यकता नहीं है। जिन ऑपरेशन को JIT किया जा सकता है, JIT दृष्टिकोण स्वाभाविक रूप से सबसे अच्छा है; लेकिन जिन ऑपरेशन को अभी तक JIT नहीं किया जा सकता है, हम इसे उपयोग करना जारी रख सकते हैं।

प्रदर्शन अनुकूलन के लिए, हमें एक सांख्यिकीय आधारित वैज्ञानिक दृष्टिकोण अपनाने की आवश्यकता है, जो कि फ्लेम ग्राफ सैंपलिंग का उद्देश्य है। समय से पहले अनुकूलन सभी बुराइयों की जड़ है। हमें केवल उन हॉट कोड के लिए अनुकूलन करने की आवश्यकता है जो बहुत सारे कॉल करते हैं और बहुत सारे CPU का उपयोग करते हैं।

loadstring पर वापस आते हुए, हम इसे केवल तब कॉल करेंगे जब कोड बदलता है, न कि अनुरोध पर, इसलिए यह एक लगातार ऑपरेशन नहीं है। इस बिंदु पर, हमें इसके सिस्टम के समग्र प्रदर्शन पर प्रभाव के बारे में चिंता करने की आवश्यकता नहीं है।

दूसरे ब्लॉकिंग मुद्दे के साथ, OpenResty में, हम कभी-कभी init और init worker चरणों के दौरान ब्लॉकिंग फ़ाइल I/O ऑपरेशन को भी कॉल करते हैं। यह ऑपरेशन NYI से अधिक प्रदर्शन-समझौता करने वाला है, लेकिन चूंकि यह सेवा शुरू होने पर केवल एक बार किया जाता है, इसलिए यह स्वीकार्य है।

हमेशा की तरह, प्रदर्शन अनुकूलन को एक व्यापक दृष्टिकोण से देखा जाना चाहिए, एक बिंदु जिस पर आपको विशेष ध्यान देने की आवश्यकता है। अन्यथा, किसी विशेष विवरण पर जुनूनी होकर, आप संभवतः लंबे समय तक अनुकूलन करेंगे लेकिन अच्छा प्रभाव नहीं होगा।

प्रश्न 4: क्या मैं डायनामिक अपस्ट्रीम को स्वयं लागू कर सकता हूं?

विवरण: डायनामिक अपस्ट्रीम के लिए, मेरा दृष्टिकोण यह है कि एक सेवा के लिए 2 अपस्ट्रीम सेट करें, रूटिंग स्थितियों के अनुसार अलग-अलग अपस्ट्रीम का चयन करें, और जब मशीन का IP बदलता है तो अपस्ट्रीम में IP को सीधे संशोधित करें। क्या इस दृष्टिकोण में balancer_by_lua का सीधे उपयोग करने की तुलना में कोई कमी या खामी है?

balancer_by_lua का लाभ यह है कि यह उपयोगकर्ता को लोड बैलेंसिंग एल्गोरिदम चुनने की अनुमति देता है, उदाहरण के लिए, क्या roundrobin या chash का उपयोग करना है, या उपयोगकर्ता द्वारा लागू किया गया कोई अन्य एल्गोरिदम, जो लचीला और उच्च प्रदर्शन वाला है।

यदि आप इसे रूटिंग नियमों के तरीके से करते हैं, तो परिणाम के मामले में यह समान है। लेकिन अपस्ट्रीम हेल्थ चेक को आपको लागू करना होगा, जो बहुत सारे अतिरिक्त काम को जोड़ता है।

हम इस प्रश्न को और विस्तारित कर सकते हैं यह पूछकर कि abtest के लिए इस परिदृश्य को कैसे लागू करना चाहिए, जिसके लिए एक अलग अपस्ट्रीम की आवश्यकता होती है।

आप balancer_by_lua चरण में uri, host, parameters आदि के आधार पर यह तय कर सकते हैं कि किस अपस्ट्रीम का उपयोग करना है। आप इन निर्णयों को रूटिंग नियमों में बदलने के लिए API गेटवे का भी उपयोग कर सकते हैं, प्रारंभिक access चरण में किस रूट का उपयोग करना है यह तय कर सकते हैं, और फिर रूट और अपस्ट्रीम के बीच बाइंडिंग संबंध के माध्यम से निर्दिष्ट अपस्ट्रीम को ढूंढ सकते हैं। यह API गेटवे का एक सामान्य दृष्टिकोण है, और हम इसे बाद में हाथों-हाथ अनुभाग में और विशेष रूप से चर्चा करेंगे।

प्रश्न 5: क्या shared dict का कैशिंग अनिवार्य है?

विवरण:

वास्तविक उत्पादन अनुप्रयोगों में, मुझे लगता है कि shared dict परत का कैश अनिवार्य है। ऐसा लगता है कि हर कोई केवल lru cache की अच्छाई को याद करता है, डेटा प्रारूप पर कोई प्रतिबंध नहीं, डीसेरियलाइज़ करने की आवश्यकता नहीं, k/v मात्रा के आधार पर मेमोरी स्पेस की गणना करने की आवश्यकता नहीं, वर्कर्स के बीच कोई प्रतिस्पर्धा नहीं, कोई रीड/राइट लॉक नहीं और उच्च प्रदर्शन।

हालांकि, इसकी सबसे घातक कमजोरी को नज़रअंदाज़ न करें कि lru cache का जीवन चक्र Worker का अनुसरण करता है। जब भी NGINX रीलोड होता है, तो कैश का यह हिस्सा पूरी तरह से खो जाएगा, और इस समय, यदि कोई shared dict नहीं है, तो L3 डेटा स्रोत मिनटों में लटक जाएगा।

बेशक, यह उच्च समवर्तीता का मामला है, लेकिन चूंकि कैशिंग का उपयोग किया जाता है, तो व्यापार मात्रा निश्चित रूप से छोटी नहीं होगी, जिसका अर्थ है कि जो विश्लेषण किया गया है वह अभी भी लागू होता है। अगर मैं इस दृष्टिकोण में सही हूं?

कुछ मामलों में, यह सच है कि, जैसा कि आपने कहा, shared dict रीलोड के दौरान खोता नहीं है, इसलिए यह आवश्यक है। लेकिन एक विशेष मामला है जहां केवल lru cache स्वीकार्य है यदि सभी डेटा L3, डेटा स्रोत से init चरण या init_worker चरण में सक्रिय रूप से उपलब्ध है।

उदाहरण के लिए, यदि ओपन सोर्स API गेटवे APISIX का डेटा स्रोत etcd में है, तो यह केवल etcd से डेटा प्राप्त करता है। यह init_worker चरण में डेटा को lru cache में कैश करता है, और बाद में कैश अपडेट etcd के watch मैकेनिज्म के माध्यम से सक्रिय रूप से प्राप्त किए जाते हैं। इस तरह, भले ही NGINX रीलोड हो, कैश स्टैम्पीड नहीं होगा।

इसलिए, हम तकनीक चुनने में प्राथमिकताएं रख सकते हैं लेकिन पूर्ण रूप से सामान्यीकरण न करें क्योंकि कोई एक सिल्वर बुलेट नहीं है जो सभी कैशिंग परिदृश्यों को फिट कर सके। यह वास्तविक परिदृश्य की आवश्यकताओं के अनुसार एक न्यूनतम उपलब्ध समाधान बनाने और फिर इसे धीरे-धीरे बढ़ाने का एक उत्कृष्ट तरीका है।