रेल एक बड़ा ढांचा है जिसमें विशिष्ट परिस्थितियों के लिए बहुत सारे उपयोगी उपकरण अंतर्निहित हैं। इस श्रृंखला में, हम रेल के बड़े कोडबेस में छिपे कुछ कम ज्ञात टूल पर एक नज़र डालेंगे।
इस लेख में, हम increment
. के बारे में बताएंगे और decrement
Rails.cache
. में विधियां ।
रेल.कैश हेल्पर
Rails.cache
आपके आवेदन में कैश के साथ बातचीत करने का प्रवेश मार्ग है। यह एक अमूर्तता भी है, जो आपको हुड के तहत उपयोग किए जा रहे वास्तविक कैश "स्टोर" की परवाह किए बिना कॉल करने के लिए एक सामान्य एपीआई देता है। लीक से हटकर, रेल निम्नलिखित का समर्थन करती है:
- फाइलस्टोर
- मेमोरीस्टोर
- मेम कैशेस्टोर
- नलस्टोर
- RedisCacheStore
Rails.cache
का निरीक्षण करना दिखाएगा कि आप किसे चला रहे हैं:
> Rails.cache
=> <#ActiveSupport::Cache::RedisCacheStore options={:namespace=>nil, ...
हम उन सभी को विस्तार से नहीं देखेंगे, लेकिन एक त्वरित सारांश के रूप में:
- NullStore कुछ भी स्टोर नहीं करता है; इससे वापस पढ़ना हमेशा
nil
लौटाएगा . यह एक नए रेल ऐप के लिए डिफ़ॉल्ट है। - फाइलस्टोर कैश को हार्ड ड्राइव पर फाइलों के रूप में संग्रहीत करता है, इसलिए यदि आप अपने रेल सर्वर को पुनरारंभ करते हैं तो भी वे बने रहते हैं।
- मेमोरीस्टोर कैश को रैम में रखता है, इसलिए यदि आप अपना रेल सर्वर बंद कर देते हैं, तो आप कैश को भी मिटा देंगे।
- MemCacheStore और RedisCacheStore कैश को बनाए रखने के लिए बाहरी प्रोग्राम (क्रमशः MemCache और Redis) का उपयोग करते हैं।
विभिन्न कारणों से, यहां पहले तीन का उपयोग अक्सर विकास/परीक्षण के लिए किया जाता है। उत्पादन में, आप संभवतः Redis या MemCache का उपयोग करेंगे।
क्योंकि Rails.cache
इन सेवाओं के बीच अंतर को दूर करता है, कोड को बदले बिना विभिन्न वातावरणों में विभिन्न संस्करणों को चलाना आसान है; उदाहरण के लिए, आप NullStore
. का उपयोग कर सकते हैं विकास में, MemoryStore
परीक्षण में, और RedisCacheStore
उत्पादन में।
संचित डेटा
रेल के माध्यम से Rails.cache.read
, Rails.cache.write
, और Rails.cache.fetch
, हमारे पास कैश से किसी भी मनमाने डेटा को संग्रहीत और पुनर्प्राप्त करने का एक आसान तरीका है। पहले के एक लेख में इन्हें और अधिक विस्तार से कवर किया गया था; इस लेख के लिए ध्यान देने योग्य महत्वपूर्ण बात यह है कि इन विधियों पर कोई अंतर्निहित थ्रेड-सुरक्षा नहीं है। मान लें कि हम चल रहे काउंट को बनाए रखने के लिए कैश्ड डेटा को कई थ्रेड्स से अपडेट कर रहे हैं; दौड़ की स्थिति से बचने के लिए हमें किसी प्रकार के लॉक में पढ़ने/लिखने के संचालन को लपेटने की आवश्यकता होगी। इस उदाहरण पर विचार करें, मान लें कि हमने रेडिस कैश स्टोर का उपयोग करने के लिए चीजों को सेट अप किया है:
threads = []
# Set initial counter
Rails.cache.write(:test_counter, 0)
4.times do
threads << Thread.new do
100.times do
current_count = Rails.cache.read(:test_counter)
current_count += 1
Rails.cache.write(:test_counter, current_count)
end
end
end
threads.map(&:join)
puts Rails.cache.read(:test_counter)
यहां हमारे पास चार धागे हैं, प्रत्येक एक हमारे कैश्ड मान को सौ गुना बढ़ा रहा है। परिणाम 400 होना चाहिए, लेकिन ज्यादातर समय, यह बहुत कम होगा - मेरे टेस्ट रन पर 269। यहाँ जो हो रहा है वह एक दौड़ की स्थिति है। मैंने पिछले लेख में इन्हें और अधिक विस्तार से कवर किया था, लेकिन एक त्वरित सारांश के रूप में, क्योंकि सभी थ्रेड्स एक ही "साझा" डेटा पर काम कर रहे हैं, वे एक दूसरे के साथ सिंक से बाहर हो सकते हैं। उदाहरण के लिए, एक थ्रेड मान को पढ़ सकता है, फिर दूसरा थ्रेड मान लेता है और मान को भी पढ़ता है, इसे बढ़ाता है, और इसे संग्रहीत करता है। पहला थ्रेड फिर से शुरू हो जाता है, इसके पुराने मान का उपयोग करते हुए।
इस समस्या को हल करने का सामान्य तरीका कोड को पारस्परिक रूप से अनन्य लॉक (या म्यूटेक्स) के साथ घेरना है ताकि एक समय में केवल एक थ्रेड लॉक के भीतर कोड निष्पादित कर सके। हमारे मामले में, हालांकि, इस परिदृश्य को संभालने के लिए Rails.cache के पास कुछ तरीके हैं।
रेल कैश इंक्रीमेंट और डिक्रीमेंट
Rails.cache
ऑब्जेक्ट में increment
both दोनों हैं और decrement
हमारे काउंटर परिदृश्य जैसे कैश्ड डेटा पर सीधे कार्य करने के तरीके:
threads = []
# Set initial counter
Rails.cache.write(:test_counter, 0, raw: true)
4.times do
threads << Thread.new do
100.times do
Rails.cache.increment(:test_counter)
# repeating the increment just to highlight the thread safety
Rails.cache.decrement(:test_counter)
Rails.cache.increment(:test_counter)
end
end
end
threads.map(&:join)
puts Rails.cache.read(:test_counter, raw: true)
increment
का उपयोग करने के लिए और decrement
, हमें कैश स्टोर को बताना होगा कि यह एक 'कच्चा' मान है (raw: true
. के माध्यम से) ) मूल्य को वापस पढ़ते समय भी आपको ऐसा करना होगा; अन्यथा, आपको एक त्रुटि मिलेगी। मूल रूप से, हम कैश को बता रहे हैं कि हम चाहते हैं कि यह मान एक नंगे पूर्णांक के रूप में संग्रहीत हो ताकि हम उस पर वृद्धि/कमी कह सकें, लेकिन आप अभी भी expires_in
का उपयोग कर सकते हैं और अन्य कैश फ़्लैग एक ही समय में।
यहाँ कुंजी यह है कि increment
और decrement
परमाणु संचालन (कम से कम Redis और MemCache के लिए) का उपयोग करें, जिसका अर्थ है कि वे थ्रेड सुरक्षित हैं; परमाणु संचालन के दौरान धागे के रुकने का कोई तरीका नहीं है।
यह ध्यान देने योग्य है, हालाँकि मैंने यहाँ अपने उदाहरण में इसका उपयोग नहीं किया है, कि दोनों विधियाँ भी नया मान लौटाती हैं। इसलिए, यदि आपको नए काउंटर वैल्यू को केवल अपडेट करने के अलावा उपयोग करने की आवश्यकता है, तो आप अतिरिक्त read
के बिना भी ऐसा कर सकते हैं। कॉल करें।
रियल-वर्ल्ड ऐप्लिकेशन
इसके चेहरे पर, ये increment
और decrement
विधियां निम्न-स्तरीय सहायक विधियों की तरह प्रतीत होती हैं, जिस तरह से आप शायद केवल तभी परवाह करते हैं जब आप पृष्ठभूमि नौकरी प्रसंस्करण मणि की तरह कुछ कार्यान्वित या बनाए रखते हैं। हालाँकि, एक बार जब आप उनके बारे में जान जाते हैं, तो आपको आश्चर्य हो सकता है कि वे कहाँ काम आ सकते हैं।
डुप्लिकेट शेड्यूल्ड बैकग्राउंड जॉब्स एक साथ चलने से बचने के लिए मैंने इन्हें प्रोडक्शन एप्लिकेशन में इस्तेमाल किया है। हमारे मामले में, हमारे पास खोज सूचकांकों को अद्यतन करने, परित्यक्त गाड़ियों को चिह्नित करने आदि के लिए विभिन्न अनुसूचित कार्य हैं। आम तौर पर, यह ठीक काम करता है; पकड़ यह है कि कुछ नौकरियां (विशेष रूप से खोज सूचकांक) बहुत अधिक स्मृति का उपभोग करती हैं - पर्याप्त है कि, यदि दो एक साथ चल रहे हों, तो यह हमारे हेरोकू डिनो की सीमा से अधिक हो जाएगा, और कार्यकर्ता को मार दिया जाएगा। चूंकि हमारे पास इनमें से कुछ नौकरियां हैं, इसलिए उन्हें नो-रिट्रीज के रूप में चिह्नित करना या अद्वितीय नौकरियों के लिए मजबूर करना उतना आसान नहीं है; दो अलग-अलग (और इस प्रकार अद्वितीय) कार्य एक ही समय में चलाने और कार्यकर्ता को नीचे लाने का प्रयास कर सकते हैं।
इसे रोकने के लिए, हमने अनुसूचित नौकरियों के लिए एक आधार वर्ग बनाया है जो इस बात का काउंटर रखता है कि कितने वर्तमान में चल रहे हैं। यदि गिनती बहुत अधिक है, तो कार्य बस फिर से कतारबद्ध हो जाता है और प्रतीक्षा करता है।
एक अन्य उदाहरण मेरा एक साइड-प्रोजेक्ट था जहां उपयोगकर्ता को प्रतीक्षा करने के दौरान पृष्ठभूमि नौकरी (या एकाधिक नौकरियां) कुछ प्रसंस्करण करती है। यह पृष्ठभूमि कार्य के उपयोगकर्ता को वर्तमान प्रगति को संप्रेषित करने के सामान्य मुद्दे को सामने लाता है। हालांकि इसे कई तरीकों से हल किया जा सकता है, एक प्रयोग के रूप में, मैंने Rails.cache.increment
का उपयोग करने का प्रयास किया। विश्व स्तर पर उपलब्ध काउंटर को अद्यतन करने के लिए। संरचना इस प्रकार थी:
- सबसे पहले, मैंने
/app/models
में एक नया वर्ग जोड़ा है इस तथ्य को दूर करने के लिए कि काउंटर कैश में रहता था। यह वह जगह है जहां मूल्य तक सभी पहुंच प्रवाहित होगी। इसका एक हिस्सा कार्य से संबंधित अद्वितीय कैश कुंजी उत्पन्न कर रहा है। - कार्य तब इस मॉडल का एक उदाहरण बनाता है और आइटम संसाधित होते ही इसे अपडेट कर देता है।
- एक साधारण JSON एंडपॉइंट वर्तमान मान को हथियाने के लिए इस मॉडल का एक उदाहरण बनाता है।
- यूआई को अपडेट करने के लिए फ्रंट-एंड इस एंडपॉइंट को हर कुछ सेकंड में पोल करता है। बेशक, आप एक्शनकेबल जैसी किसी चीज़ के साथ इस प्रशंसक को बना सकते हैं और अपडेट को पुश आउट कर सकते हैं।
निष्कर्ष
ईमानदारी से, Rails.cache.increment
ऐसा उपकरण नहीं है जिसके लिए मैं अक्सर पहुंचूंगा, क्योंकि ऐसा अक्सर नहीं होता है कि मैं कैश में संग्रहीत डेटा को अपडेट करना चाहता हूं (जो स्वभाव से, कुछ हद तक अस्थायी है)। जैसा कि ऊपर उल्लेख किया गया है, मैं जिस समय तक पहुंचता हूं वह आम तौर पर पृष्ठभूमि की नौकरियों से संबंधित होता है क्योंकि नौकरियां पहले से ही रेडिस में अपना डेटा संग्रहीत कर रही हैं (कम से कम अधिकांश ऐप में मैंने काम किया है) और आम तौर पर अस्थायी भी हैं। ऐसे मामलों में, संबंधित डेटा (उदाहरण के लिए पूर्ण प्रतिशत) को समान स्तर की अल्पकालिक दृढ़ता के साथ एक ही स्थान पर संग्रहीत करना स्वाभाविक लगता है।
सभी चीजों के साथ "पीटा पथ से बाहर", आपको इस तरह की चीजों को अपने कोडबेस में पेश करने से सावधान रहना चाहिए। मैं भविष्य के डेवलपर्स को यह समझाने के लिए कम से कम कुछ टिप्पणियों को जोड़ने की सलाह दूंगा कि आप इस पद्धति का उपयोग क्यों कर रहे हैं जिससे वे शायद परिचित नहीं हैं।