OpenResty का मूल: cosocket

API7.ai

October 28, 2022

OpenResty (NGINX + Lua)

आज हम OpenResty की मुख्य तकनीक, cosocket के बारे में सीखेंगे।

हमने पिछले लेखों में इसे कई बार उल्लेख किया है, cosocket विभिन्न lua-resty-* नॉन-ब्लॉकिंग लाइब्रेरीज़ का आधार है। cosocket के बिना, डेवलपर्स Lua का उपयोग करके बाहरी वेब सेवाओं से जल्दी से कनेक्ट नहीं कर सकते।

OpenResty के पुराने संस्करणों में, यदि आप Redis और memcached जैसी सेवाओं के साथ इंटरैक्ट करना चाहते थे, तो आपको redis2-nginx-module, redis-nginx-module और memc-nginx-module C-मॉड्यूल का उपयोग करना पड़ता था। ये मॉड्यूल अभी भी OpenResty वितरण में उपलब्ध हैं।

हालांकि, cosocket सुविधा के जोड़े जाने के बाद, C मॉड्यूल को lua-resty-redis और lua-resty-memcached द्वारा प्रतिस्थापित कर दिया गया है। अब कोई भी बाहरी सेवाओं से कनेक्ट करने के लिए C मॉड्यूल का उपयोग नहीं करता।

cosocket क्या है?

तो cosocket वास्तव में क्या है? cosocket OpenResty में एक विशेष शब्द है। cosocket का नाम coroutine + socket से मिलकर बना है।

cosocket को Lua की समवर्ती सुविधा समर्थन और NGINX में मूलभूत इवेंट मैकेनिज्म की आवश्यकता होती है, जो मिलकर नॉन-ब्लॉकिंग नेटवर्क I/O को सक्षम करते हैं। cosocket TCP, UDP, और Unix Domain Socket का भी समर्थन करता है।

यदि हम OpenResty में cosocket से संबंधित फ़ंक्शन को कॉल करते हैं, तो आंतरिक कार्यान्वयन निम्नलिखित चित्र की तरह दिखता है।

call cosocket-related function

मैंने इस चित्र का उपयोग पिछले लेख में OpenResty सिद्धांत और मूल अवधारणाएं में भी किया था। जैसा कि आप चित्र से देख सकते हैं, उपयोगकर्ता के Lua स्क्रिप्ट द्वारा ट्रिगर किए गए प्रत्येक नेटवर्क ऑपरेशन के लिए, दोनों में कोरोटीन का yield और resume होगा।

जब नेटवर्क I/O का सामना होता है, तो यह नेटवर्क इवेंट को NGINX लिस्टनर सूची में पंजीकृत करता है और नियंत्रण (yield) को NGINX को स्थानांतरित करता है। जब एक NGINX इवेंट ट्रिगर स्थिति तक पहुंचता है, तो यह कोरोटीन को जागृत करता है ताकि वह प्रसंस्करण जारी रख सके (resume)।

उपरोक्त प्रक्रिया वह नक्शा है जिसका उपयोग OpenResty आज हमारे सामने आने वाले cosocket APIs को संपर्क, भेजने, प्राप्त करने आदि ऑपरेशनों को संपादित करने के लिए करता है। मैं TCP को संभालने के लिए API का उदाहरण दूंगा। UDP और Unix Domain सॉकेट को नियंत्रित करने के लिए इंटरफ़ेस TCP के समान है।

cosocket APIs और कमांड्स का परिचय

TCP से संबंधित cosocket APIs को निम्नलिखित श्रेणियों में विभाजित किया जा सकता है।

  • ऑब्जेक्ट बनाएं: ngx.socket.tcp
  • टाइमआउट सेट करें: tcpsock:settimeout और tcpsock:settimeouts
  • कनेक्शन स्थापित करें: tcpsock:connect
  • डेटा भेजें: tcpsock:send
  • डेटा प्राप्त करें: tcpsock:receive, tcpsock:receiveany, और tcpsock:receiveuntil
  • कनेक्शन पूलिंग: tcpsock:setkeepalive
  • कनेक्शन बंद करें: tcpsock:close

हमें इन APIs का उपयोग करने के संदर्भों पर भी विशेष ध्यान देना चाहिए।

rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*_

एक और बिंदु जिस पर मैं जोर देना चाहता हूं, वह यह है कि NGINX कर्नेल में विभिन्न सीमाओं के कारण कई अनुपलब्ध वातावरण हैं। उदाहरण के लिए, cosocket API set_by_lua*, log_by_lua*, header_filter_by_lua*, और body_filter_by_lua* में अनुपलब्ध है। यह अभी init_by_lua* और init_worker_by_lua* में अनुपलब्ध है, लेकिन NGINX कर्नेल इन दो चरणों को प्रतिबंधित नहीं करता है, इनके लिए समर्थन बाद में जोड़ा जा सकता है।

इन APIs से संबंधित lua_socket_ से शुरू होने वाले आठ NGINX कमांड हैं। आइए संक्षेप में देखें।

  • lua_socket_connect_timeout: कनेक्शन टाइमआउट, डिफ़ॉल्ट 60 सेकंड।
  • lua_socket_send_timeout: भेजने का टाइमआउट, डिफ़ॉल्ट 60 सेकंड।
  • lua_socket_send_lowat: भेजने का थ्रेशोल्ड (लो वॉटर), डिफ़ॉल्ट 0 है।
  • lua_socket_read_timeout: पढ़ने का टाइमआउट, डिफ़ॉल्ट 60 सेकंड।
  • lua_socket_buffer_size: डेटा पढ़ने के लिए बफर आकार, डिफ़ॉल्ट 4k/8k।
  • lua_socket_pool_size: कनेक्शन पूल आकार, डिफ़ॉल्ट 30।
  • lua_socket_keepalive_timeout: कनेक्शन पूल cosocket ऑब्जेक्ट का निष्क्रिय समय, डिफ़ॉल्ट 60 सेकंड।
  • lua_socket_log_errors: क्या cosocket त्रुटियों को लॉग करना है जब वे होती हैं, डिफ़ॉल्ट on है।

यहां आप यह भी देख सकते हैं कि कुछ कमांड्स का कार्य API के समान है, जैसे टाइमआउट और कनेक्शन पूल आकार सेट करना। हालांकि, यदि दोनों के बीच कोई संघर्ष होता है, तो API का प्राथमिकता कमांड से अधिक होता है और यह आदेश द्वारा सेट किए गए मान को ओवरराइड कर देगा। इसलिए, आम तौर पर, हम सेटिंग्स करने के लिए APIs का उपयोग करने की सलाह देते हैं, जो अधिक लचीला भी है।

आगे, आइए एक ठोस उदाहरण देखें ताकि हम समझ सकें कि इन cosocket APIs का उपयोग कैसे करें। निम्नलिखित कोड का कार्य सरल है, जो एक वेबसाइट को TCP अनुरोध भेजता है और लौटाए गए सामग्री को प्रिंट करता है:

$ resty -e 'local sock = ngx.socket.tcp() sock:settimeout(1000) -- एक सेकंड टाइमआउट local ok, err = sock:connect("api7.ai", 80) if not ok then ngx.say("failed to connect: ", err) return end local req_data = "GET / HTTP/1.1\r\nHost: api7.ai\r\n\r\n" local bytes, err = sock:send(req_data) if err then ngx.say("failed to send: ", err) return end local data, err, partial = sock:receive() if err then ngx.say("failed to receive: ", err) return end sock:close() ngx.say("response is: ", data)'

आइए इस कोड का विस्तार से विश्लेषण करें।

  • सबसे पहले, ngx.socket.tcp() का उपयोग करके sock नाम के साथ एक TCP cosocket ऑब्जेक्ट बनाएं।
  • फिर, settimeout() का उपयोग करके टाइमआउट को 1 सेकंड पर सेट करें। ध्यान दें कि यहां टाइमआउट कनेक्ट और प्राप्त करने के बीच अंतर नहीं करता है; यह एक समान सेटिंग है।
  • अगला, connect() API का उपयोग करके निर्दिष्ट वेबसाइट के पोर्ट 80 से कनेक्ट करें और यदि यह विफल होता है तो बाहर निकलें।
  • यदि कनेक्शन सफल होता है, तो send() का उपयोग करके निर्मित डेटा भेजें और यदि यह विफल होता है तो बाहर निकलें।
  • यदि डेटा सफलतापूर्वक भेजा जाता है, तो receive() का उपयोग करके वेबसाइट से डेटा प्राप्त करें। यहां, receive() का डिफ़ॉल्ट पैरामीटर *l है, जिसका अर्थ है कि केवल डेटा की पहली पंक्ति लौटाई जाएगी। यदि पैरामीटर *a पर सेट किया जाता है, तो यह कनेक्शन बंद होने तक डेटा प्राप्त करता है।
  • अंत में, close() को कॉल करके सॉकेट कनेक्शन को सक्रिय रूप से बंद करें।

जैसा कि आप देख सकते हैं, नेटवर्क संचार करने के लिए cosocket APIs का उपयोग करना कुछ ही चरणों में सरल है। आइए उदाहरण को गहराई से समझने के लिए कुछ समायोजन करें।

1. तीन क्रियाओं के लिए टाइमआउट समय सेट करें: सॉकेट कनेक्ट, भेजें और पढ़ें।

हमने settimeout() का उपयोग करके टाइमआउट समय को एकल मान पर सेट किया था। टाइमआउट समय को अलग-अलग सेट करने के लिए, आपको settimeouts() फ़ंक्शन का उपयोग करना होगा, जैसे निम्नलिखित।

sock:settimeouts(1000, 2000, 3000)

settimeouts के पैरामीटर मिलीसेकंड में हैं। यह कोड लाइन 1 सेकंड का कनेक्शन टाइमआउट, 2 सेकंड का भेजने का टाइमआउट, और 3 सेकंड का पढ़ने का टाइमआउट इंगित करती है।

OpenResty और lua-resty लाइब्रेरीज़ में, समय से संबंधित APIs के अधिकांश पैरामीटर मिलीसेकंड में होते हैं। लेकिन कुछ अपवाद हैं जिन पर आपको कॉल करते समय विशेष ध्यान देना चाहिए।

2. निर्दिष्ट आकार की सामग्री प्राप्त करें।

जैसा कि मैंने अभी कहा, receive() API एक पंक्ति डेटा प्राप्त कर सकता है या डेटा को लगातार प्राप्त कर सकता है। हालांकि, यदि आप केवल 10K आकार का डेटा प्राप्त करना चाहते हैं, तो आपको इसे कैसे सेट करना चाहिए?

यहीं पर receiveany() काम आता है। यह इस आवश्यकता को पूरा करने के लिए डिज़ाइन किया गया है, इसलिए निम्नलिखित कोड लाइन देखें।

local data, err, partial = sock:receiveany(10240)

इस कोड का अर्थ है कि केवल 10K तक का डेटा प्राप्त किया जाएगा।

बेशक, receive() के लिए एक और सामान्य उपयोगकर्ता आवश्यकता यह है कि डेटा को तब तक प्राप्त करें जब तक कि निर्दिष्ट स्ट्रिंग का सामना न हो।

receiveuntil() इस तरह की समस्या को हल करने के लिए डिज़ाइन किया गया है। यह receive() और receiveany() की तरह एक स्ट्रिंग लौटाने के बजाय, एक इटरेटर लौटाएगा। इस तरह, आप इसे लूप में कॉल करके मिलान किए गए डेटा को खंडों में पढ़ सकते हैं और जब पढ़ना पूरा हो जाए तो nil लौटा सकते हैं। यहां एक उदाहरण है।

local reader = sock:receiveuntil("\r\n") while true do local data, err, partial = reader(4) if not data then if err then ngx.say("failed to read the data stream: ", err) break end ngx.say("read done") break end ngx.say("read chunk: [", data, "]") end

receiveuntil \r\n से पहले डेटा लौटाता है और इटरेटर के माध्यम से इसे एक बार में चार बाइट्स पढ़ता है।

3. सॉकेट को सीधे बंद करने के बजाय, इसे कनेक्शन पूल में रखें।

जैसा कि हम जानते हैं, कनेक्शन पूलिंग के बिना, एक नया कनेक्शन बनाना पड़ता है, जिससे cosocket ऑब्जेक्ट हर बार एक अनुरोध आने पर बनाए जाते हैं और बार-बार नष्ट होते हैं, जिससे अनावश्यक प्रदर्शन हानि होती है।

इस समस्या से बचने के लिए, जब आप एक cosocket का उपयोग समाप्त कर लेते हैं, तो आप इसे कनेक्शन पूल में रखने के लिए setkeepalive() को कॉल कर सकते हैं, जैसे निम्नलिखित।

local ok, err = sock:setkeepalive(2 * 1000, 100) if not ok then ngx.say("failed to set reusable: ", err) end

इस कोड ने कनेक्शन निष्क्रिय समय को 2 सेकंड और कनेक्शन पूल आकार को 100 पर सेट किया है, ताकि जब connect() फ़ंक्शन को कॉल किया जाए, तो cosocket ऑब्जेक्ट को पहले कनेक्शन पूल से प्राप्त किया जाएगा।

हालांकि, कनेक्शन पूलिंग का उपयोग करते समय हमें दो बातों का ध्यान रखना चाहिए।

  • पहला, आप एक त्रुटिपूर्ण कनेक्शन को कनेक्शन पूल में नहीं डाल सकते। अन्यथा, अगली बार जब आप इसका उपयोग करेंगे, तो यह डेटा भेजने और प्राप्त करने में विफल हो जाएगा। यही कारण है कि हमें यह निर्धारित करना चाहिए कि प्रत्येक API कॉल सफल है या नहीं।
  • दूसरा, हमें कनेक्शन की संख्या का पता लगाना चाहिए। कनेक्शन पूलिंग Worker-स्तर की है, और प्रत्येक Worker का अपना कनेक्शन पूल होता है। यदि आपके पास 10 Worker हैं और कनेक्शन पूल आकार 30 पर सेट है, तो बैकएंड सेवा के लिए 300 कनेक्शन होंगे।

सारांश

संक्षेप में, हमने cosocket की मूल अवधारणाओं, संबंधित कमांड्स और APIs के बारे में सीखा। एक व्यावहारिक उदाहरण ने हमें TCP से संबंधित APIs का उपयोग करने के तरीके से परिचित कराया। UDP और Unix Domain Socket का उपयोग TCP के समान है। आज हमने जो सीखा है, उसे समझने के बाद आप इन सभी प्रश्नों को आसानी से संभाल सकते हैं।

हम जानते हैं कि cosocket का उपयोग करना अपेक्षाकृत आसान है, और इसे अच्छी तरह से उपयोग करके हम विभिन्न बाहरी सेवाओं से कनेक्ट कर सकते हैं।

अंत में, हम दो प्रश्नों के बारे में सोच सकते हैं।

पहला प्रश्न, आज के उदाहरण में, tcpsock:send एक स्ट्रिंग भेजता है; यदि हमें स्ट्रिंग से बनी एक टेबल भेजनी हो तो क्या करें?

दूसरा प्रश्न, जैसा कि आप देख सकते हैं, cosocket का उपयोग कई चरणों में नहीं किया जा सकता है, तो क्या आप इसे बायपास करने के कुछ तरीके सोच सकते हैं?

कृपया मुझे टिप्पणी करने और इसे मेरे साथ साझा करने के लिए स्वतंत्र महसूस करें। इस लेख को अपने सहयोगियों और दोस्तों के साथ साझा करने के लिए आपका स्वागत है ताकि हम एक साथ संवाद और प्रगति कर सकें।