ट्रैफ़िक के Layer 4 को संभालना और OpenResty द्वारा Memcached सर्वर को लागू करना
API7.ai
November 10, 2022
पिछले कुछ लेखों में, हमने अनुरोधों को संभालने के लिए कुछ Lua APIs का परिचय दिया था, जो सभी Layer 7 से संबंधित हैं। इसके अलावा, OpenResty stream-lua-nginx-module मॉड्यूल प्रदान करता है जो Layer 4 से ट्रैफिक को संभालता है। यह निर्देश और APIs प्रदान करता है जो मूल रूप से lua-nginx-module के समान हैं।
आज, हम OpenResty के साथ एक Memcached सर्वर को लागू करने के बारे में बात करेंगे, जिसमें केवल लगभग 100 लाइन कोड की आवश्यकता होती है। इस छोटे से हाथों-हाथ प्रयोग में, हम पहले से सीखे गए बहुत कुछ का उपयोग करेंगे, और हम बाद में परीक्षण और प्रदर्शन अनुकूलन अध्यायों से कुछ सामग्री भी लाएंगे।
और हमें स्पष्ट होना चाहिए कि इस लेख का मुख्य बिंदु हर लाइन कोड के कार्यों को समझना नहीं है, बल्कि OpenResty द्वारा एक प्रोजेक्ट को शुरू से विकसित करने के पूर्ण दृश्य को समझना है, जिसमें आवश्यकताएं, परीक्षण, विकास आदि के दृष्टिकोण से शामिल हैं।
मूल आवश्यकताएं और तकनीकी समाधान
हम जानते हैं कि HTTPS ट्रैफिक मुख्यधारा बन रहा है, लेकिन कुछ पुराने ब्राउज़र session tickets का समर्थन नहीं करते हैं, इसलिए हमें सत्र ID को सर्वर साइड पर संग्रहीत करने की आवश्यकता है। यदि स्थानीय संग्रहण स्थान अपर्याप्त है, तो हमें संग्रहण के लिए एक क्लस्टर की आवश्यकता है, और डेटा को छोड़ा जा सकता है, इसलिए Memcached अधिक उपयुक्त है।
इस बिंदु पर, Memcached को सीधे शुरू करना सबसे सीधा समाधान होना चाहिए। हालांकि, इस लेख में, हम निम्नलिखित कारणों से OpenResty का उपयोग करके एक पहिया बनाने का विकल्प चुनेंगे।
- पहला, Memcached को सीधे शुरू करने से एक अतिरिक्त प्रक्रिया शुरू होगी, जो तैनाती और रखरखाव लागत को बढ़ाएगी।
- दूसरा, आवश्यकता काफी सरल है, केवल
getऔरsetऑपरेशन की आवश्यकता है, और समाप्ति का समर्थन करती है। - तीसरा, OpenResty में एक
streamमॉड्यूल है, जो इस आवश्यकता को जल्दी से लागू कर सकता है।
चूंकि हम एक Memcached सर्वर को लागू करना चाहते हैं, इसलिए हमें पहले इसके प्रोटोकॉल को समझने की आवश्यकता है। Memcached प्रोटोकॉल TCP और UDP का समर्थन कर सकता है। यहां हम TCP का उपयोग करते हैं। नीचे get और set कमांड का विशिष्ट प्रोटोकॉल है।
Get get value with key Telnet command: get <key>*\r\n उदाहरण: get key VALUE key 0 4 data END
Set Save key-value to memcached Telnet command:set <key> <flags> <exptime> <bytes> [noreply]\r\n<value>\r\n उदाहरण: set key 0 900 4 data STORED
हमें यह भी जानना होगा कि Memcached प्रोटोकॉल का "त्रुटि प्रबंधन" get और set के अलावा कैसे लागू किया जाता है। "त्रुटि प्रबंधन" सर्वर-साइड प्रोग्राम के लिए बहुत महत्वपूर्ण है, और हमें ऐसे प्रोग्राम लिखने की आवश्यकता है जो न केवल सामान्य अनुरोधों को संभालते हैं बल्कि अपवादों को भी संभालते हैं। उदाहरण के लिए, निम्नलिखित परिदृश्य में:
- Memcached
getयाsetके अलावा एक अनुरोध भेजता है, मैं इसे कैसे संभालूं? - सर्वर साइड पर त्रुटि होने पर मैं Memcached क्लाइंट को किस प्रकार की प्रतिक्रिया दूं?
इसके अलावा, हम एक Memcached-संगत क्लाइंट एप्लिकेशन लिखना चाहते हैं। इस तरह, उपयोगकर्ताओं को आधिकारिक Memcached संस्करण और OpenResty कार्यान्वयन के बीच अंतर करने की आवश्यकता नहीं है।
Memcached दस्तावेज़ीकरण से निम्नलिखित चित्र त्रुटि के मामले में क्या वापस किया जाना चाहिए और सटीक प्रारूप का वर्णन करता है, जिसे आप संदर्भ के रूप में उपयोग कर सकते हैं।

अब, आइए तकनीकी समाधान को परिभाषित करें। हम जानते हैं कि OpenResty का shared dict workers के बीच उपयोग किया जा सकता है और shared dict में डेटा डालना Memcached में डेटा डालने के समान है। वे दोनों get और set ऑपरेशन का समर्थन करते हैं, और प्रक्रिया को पुनः आरंभ करने पर डेटा खो जाता है। इसलिए, shared dict का उपयोग करके Memcached का अनुकरण करना उचित है, क्योंकि उनके सिद्धांत और व्यवहार समान हैं।
परीक्षण-संचालित विकास
अगला कदम इसे शुरू करना है। हालांकि, परीक्षण-संचालित विकास के विचार के आधार पर, आइए विशिष्ट कोड लिखने से पहले सबसे सरल परीक्षण मामले का निर्माण करें। test::nginx फ्रेमवर्क का उपयोग करने के बजाय, जो शुरू करने में कुख्यात रूप से कठिन है, आइए resty का उपयोग करके एक मैनुअल परीक्षण से शुरू करें।
$ resty -e 'local memcached = require "resty.memcached" local memc, err = memcached:new() memc:set_timeout(1000) -- 1 सेकंड local ok, err = memc:connect("127.0.0.1", 11212) local ok, err = memc:set("dog", 32) if not ok then ngx.say("failed to set dog: ", err) return end local res, flags, err = memc:get("dog") ngx.say("dog: ", res)'
यह परीक्षण कोड lua-rety-memcached क्लाइंट लाइब्रेरी का उपयोग करके connect और set ऑपरेशन शुरू करता है और मानता है कि Memcached सर्वर स्थानीय मशीन पर पोर्ट 11212 पर सुन रहा है।
ऐसा लगता है कि यह ठीक काम करना चाहिए। आप इस कोड को अपनी मशीन पर चला सकते हैं, और, आश्चर्यजनक रूप से, यह failed to set dog: closed जैसी त्रुटि वापस करेगा, क्योंकि इस बिंदु पर सेवा शुरू नहीं की गई है।
इस बिंदु पर, आपका तकनीकी समाधान स्पष्ट है: डेटा प्राप्त करने और भेजने के लिए stream मॉड्यूल का उपयोग करें और इसे संग्रहीत करने के लिए shared dict का उपयोग करें।
आवश्यकता को पूरा करने का मापदंड स्पष्ट है: उपरोक्त कोड चलाएं और dog का वास्तविक मूल्य प्रिंट करें।
फ्रेमवर्क का निर्माण
तो आप किस बात का इंतज़ार कर रहे हैं? कोड लिखना शुरू करें!
मेरी आदत है कि पहले एक न्यूनतम चलने योग्य कोड फ्रेमवर्क बनाएं और फिर धीरे-धीरे कोड को भरें। इसका लाभ यह है कि आप कोडिंग प्रक्रिया के दौरान कई छोटे लक्ष्य निर्धारित कर सकते हैं, और जब आप एक छोटे लक्ष्य को पूरा करते हैं तो परीक्षण मामले आपको सकारात्मक प्रतिक्रिया देंगे।
आइए पहले NGINX कॉन्फ़िगरेशन फ़ाइल सेट करें क्योंकि stream और shared dict को इसमें पहले से सेट किया जाना चाहिए। यहां मैंने सेट की गई कॉन्फ़िगरेशन फ़ाइल है।
stream { lua_shared_dict memcached 100m; lua_package_path 'lib/?.lua;;'; server { listen 11212; content_by_lua_block { local m = require("resty.memcached.server") m.run() } } }
जैसा कि आप देख सकते हैं, इस कॉन्फ़िगरेशन फ़ाइल में कई महत्वपूर्ण जानकारी हैं।
- पहला, कोड NGINX के
streamसंदर्भ में चलता है,HTTPसंदर्भ में नहीं, और पोर्ट11212पर सुन रहा है। - दूसरा,
shared dictका नामmemcachedहै, और आकार100Mहै, जिसे रनटाइम में बदला नहीं जा सकता है। - इसके अलावा, कोड
lib/resty/memcachedनिर्देशिका में स्थित है, फ़ाइल का नामserver.luaहै, और प्रवेश फ़ंक्शनrun()है, जिसे आपlua_package_pathऔरcontent_by_lua_blockसे पा सकते हैं।
अगला, कोड फ्रेमवर्क का निर्माण करने का समय है। आप इसे स्वयं आज़मा सकते हैं, और फिर आइए मेरे फ्रेमवर्क कोड को एक साथ देखें।
local new_tab = require "table.new" local str_sub = string.sub local re_find = ngx.re.find local mc_shdict = ngx.shared.memcached local _M = { _VERSION = '0.01' } local function parse_args(s, start) end function _M.get(tcpsock, keys) end function _M.set(tcpsock, res) end function _M.run() local tcpsock = assert(ngx.req.socket(true)) while true do tcpsock:settimeout(60000) -- 60 सेकंड local data, err = tcpsock:receive("*l") local command, args if data then local from, to, err = re_find(data, [[(\S+)]], "jo") if from then command = str_sub(data, from, to) args = parse_args(data, to + 1) end end if args then local args_len = #args if command == 'get' and args_len > 0 then _M.get(tcpsock, args) elseif command == "set" and args_len == 4 then _M.set(tcpsock, args) end end end end return _M
यह कोड स्निपेट प्रवेश फ़ंक्शन run() की मुख्य तर्क को लागू करता है। हालांकि मैंने कोई अपवाद प्रबंधन नहीं किया है और निर्भरताएं parse_args, get, और set सभी खाली फ़ंक्शन हैं, यह फ्रेमवर्क पहले से ही Memcached सर्वर की तर्क को पूरी तरह से व्यक्त करता है।
कोड भरना
अगला, आइए इन खाली फ़ंक्शन को कोड के निष्पादन क्रम में लागू करें।
पहले, हम Memcached कमांड के पैरामीटर को Memcached प्रोटोकॉल दस्तावेज़ीकरण के अनुसार पार्स कर सकते हैं।
local function parse_args(s, start) local arr = {} while true do local from, to = re_find(s, [[\S+]], "jo", {pos = start}) if not from then break end table.insert(arr, str_sub(s, from, to)) start = to + 1 end return arr end
मेरी सलाह है कि पहले एक सबसे सहज संस्करण लागू करें, बिना किसी प्रदर्शन अनुकूलन के ध्यान में रखे। आखिरकार, पूर्णता हमेशा पूर्णता से अधिक महत्वपूर्ण है, और पूर्णता के आधार पर वृद्धिशील अनुकूलन ही पूर्णता के करीब पहुंचने का एकमात्र तरीका है।
अगला, आइए get फ़ंक्शन को लागू करें। यह एक साथ कई कुंजियों को क्वेरी कर सकता है, इसलिए मैं निम्नलिखित कोड में एक for लूप का उपयोग करता हूं।
function _M.get(tcpsock, keys) local reply = "" for i = 1, #keys do local key = keys[i] local value, flags = mc_shdict:get(key) if value then local flags = flags or 0 reply = reply .. "VALUE" .. key .. " " .. flags .. " " .. #value .. "\r\n" .. value .. "\r\n" end end reply = reply .. "END\r\n" tcpsock:settimeout(1000) -- एक सेकंड का टाइमआउट local bytes, err = tcpsock:send(reply) end
यहां केवल एक लाइन कोर कोड है: local value, flags = mc_shdict:get(key), यानी shared dict से डेटा क्वेरी करना; बाकी कोड Memcached प्रोटोकॉल का पालन करके स्ट्रिंग को जोड़ रहा है और अंत में इसे क्लाइंट को भेज रहा है।
अंत में, आइए set फ़ंक्शन को देखें। यह प्राप्त पैरामीटर को shared dict API प्रारूप में परिवर्तित करता है, डेटा को संग्रहीत करता है, और त्रुटियों के मामले में, Memcached के प्रोटोकॉल के अनुसार उन्हें संभालता है।
function _M.set(tcpsock, res) local reply = "" local key = res[1] local flags = res[2] local exptime = res[3] local bytes = res[4] local value, err = tcpsock:receive(tonumber(bytes) + 2) if str_sub(value, -2, -1) == "\r\n" then local succ, err, forcible = mc_shdict:set(key, str_sub(value, 1, bytes), exptime, flags) if succ then reply = reply .. “STORED\r\n" else reply = reply .. "SERVER_ERROR " .. err .. “\r\n” end else reply = reply .. "ERROR\r\n" end tcpsock:settimeout(1000) -- एक सेकंड का टाइमआउट local bytes, err = tcpsock:send(reply) end
इसके अलावा, आप उपरोक्त फ़ंक्शन को भरते समय ngx.log का उपयोग करके परीक्षण मामलों के साथ जांच और डीबग कर सकते हैं। दुर्भाग्य से, हम ngx.say और ngx.log का उपयोग करके डीबग कर रहे हैं क्योंकि OpenResty में कोई ब्रेक पॉइंट डीबगर नहीं है, जो अभी भी आगे की खोज के लिए प्रतीक्षा कर रहा है।
सारांश
यह हाथों-हाथ प्रोजेक्ट अब समाप्त हो रहा है, और अंत में, मैं एक प्रश्न छोड़ना चाहूंगा: क्या आप उपरोक्त Memcached सर्वर कार्यान्वयन कोड को ले सकते हैं, इसे पूरी तरह से चला सकते हैं, और परीक्षण मामले को पास कर सकते हैं?
आज का प्रश्न शायद बहुत प्रयास की आवश्यकता होगी, लेकिन यह अभी भी एक आदिम संस्करण है। इसमें कोई त्रुटि प्रबंधन, प्रदर्शन अनुकूलन, और स्वचालित परीक्षण नहीं है, जिसे बाद में सुधारा जाएगा।
यदि आज के स्पष्टीकरण या आपके अभ्यास के बारे में कोई संदेह है, तो आप टिप्पणी करने और हमारे साथ चर्चा करने के लिए स्वागत करते हैं। आप इस लेख को अपने सहयोगियों और दोस्तों के साथ साझा करने के लिए भी स्वागत करते हैं ताकि हम एक साथ अभ्यास और प्रगति कर सकें।