संक्षेप (टीएल;डीआर)

  • समस्या: रेट लिमिटर को हर अनुरोध पर अनुमति या इनकार का फैसला करना है, गेटवे के पूरे बेड़े में एक जैसा, एक मिलीसेकंड से काफी कम अतिरिक्त देरी में, और खुद वह गड़बड़ी बने बिना जिसे रोकने के लिए वह बना है।
  • डिजाइन: नियम एपीआई गेटवे पर लागू करें। अनुरोधों की कुंजी किरायेदार, उपयोगकर्ता, आईपी और रूट से बनाएं। टोकन बकेट को बंटे हुए रेडिस क्लस्टर पर एक परमाणु लुआ स्क्रिप्ट की तरह चलाएं, गर्म कुंजियों को स्थानीय रूप से कैश करें, और हर एंडपॉइंट के लिए तय करें कि लिमिटर की विफलता खुली विफल होगी या बंद। 429 के साथ Retry-After और मशीन से पढ़े जा सकने वाले सीमा हेडर लौटाएं।
  • नतीजा: हर अनुरोध पर रेडिस का एक चक्कर, कई गेटवे में न्यायपूर्ण सीमाएं, रेडिस धीमा होने पर संभला हुआ व्यवहार, और ऐसी सीमाएं जिन्हें ग्राहक समझ सकें।

हर सार्वजनिक एपीआई आखिरकार एक ही सबक सीखता है: एक ग्राहक का दोबारा कोशिश वाला लूप दूसरे ग्राहक की गड़बड़ी बन जाता है। रेट लिमिटर इसके खिलाफ सबसे सस्ता बीमा है, और ज्यादातर बीमों की तरह यह साल के सबसे बुरे दिन ही काम आता है।

यह लेख एक असली एपीआई मंच की तरह इसे डिजाइन करता है। एल्गोरिदम पर इसकी साफ राय है, रेडिस क्रियान्वयन पर यह सटीक है, और जो टूटता है उस पर ईमानदार।


रेट लिमिटर असल में किसलिए है

लोग "रेट लिमिटिंग" कहकर चार अलग चीजें समझते हैं। स्ट्राइप का अपने लिमिटरों पर जाना-माना लेख इन्हें साफ अलग करता है, और यह फर्क आज भी सही है:

लिमिटर का प्रकार किस सवाल का जवाब देता है आम नियम
अनुरोध दर लिमिटर क्या यह ग्राहक प्रति समय इकाई बहुत ज्यादा अनुरोध भेज रहा है? हर एपीआई कुंजी पर १०० अनुरोध प्रति सेकंड
समवर्ती अनुरोध लिमिटर क्या इस ग्राहक के बहुत ज्यादा अनुरोध एक साथ चल रहे हैं? हर उपयोगकर्ता की २० महंगी खोजें एक साथ
बेड़े का भार घटाना क्या पूरा सिस्टम अपनी क्षमता के करीब है? अहम ट्रैफिक के लिए २०% क्षमता आरक्षित
वर्कर उपयोग पर भार घटाना क्या यह खास वर्कर समूह भर गया है? समूह ९०% व्यस्त होने पर कम प्राथमिकता वाले काम छोड़ो

यह लेख पहले वाले पर केंद्रित है, जिसका मतलब इंटरव्यू लेने वाले और ज्यादातर उत्पाद टीमें निकालती हैं। बाकी तीन का नाम लेना जरूरी है, क्योंकि अकेला अनुरोध दर लिमिटर आपको उस एक ग्राहक से नहीं बचाएगा जो गिनी-चुनी पर बहुत महंगी क्वेरी चलाता है।


जरूरतें और बजट

  • गेटवे पर पी९९ देरी में १ मिलीसेकंड से कम जोड़कर फैसला।
  • कई गेटवे नोड में सीमाएं वैश्विक रूप से लागू हों, हर नोड पर अलग नहीं।
  • किरायेदार, उपयोगकर्ता, एपीआई कुंजी, आईपी और रूट के हिसाब से नियम, अलग-अलग स्तरों (मुफ्त, प्रो, एंटरप्राइज) के साथ।
  • जायज झटकों (एक पेज लोड होते ही १५ एपीआई कॉल) को सहना, पर लगातार दुरुपयोग को नहीं।
  • जब लिमिटर का अपना स्टोर धीमा या बंद हो, तो अनुमानित तरीके से कमजोर पड़ना।
  • ग्राहकों को बताना कि दोबारा कब कोशिश करें।

पूरे बेड़े में २ लाख अनुरोध प्रति सेकंड पर, हर अनुरोध के लिए स्टोर का एक चक्कर यानी २ लाख कार्य प्रति सेकंड। रेडिस का एक हिस्सा आराम से लगभग १ लाख सरल कार्य प्रति सेकंड संभालता है, इसलिए गुंजाइश के साथ बंटा हुआ क्लस्टर और सबसे गर्म कुंजियों के लिए स्थानीय कैश की योजना बनाएं।


एल्गोरिदम चुनना

एल्गोरिदम कैसे काम करता है झटकों पर व्यवहार प्रति कुंजी मेमोरी सटीकता कहां दिखता है
स्थिर विंडो हर कैलेंडर विंडो (जैसे हर मिनट) में अनुरोध गिनो विंडो की सीमा पर दोगुनी अनुमति एक काउंटर किनारों पर कम सरल कोटा, दैनिक सीमा
स्लाइडिंग लॉग हर अनुरोध का समय रखो, पिछली विंडो वाले गिनो सीमा पर झटका नहीं हर अनुरोध की एक प्रविष्टि पूरी सटीक कम मात्रा, ऊंचे मूल्य वाले एंडपॉइंट
स्लाइडिंग विंडो काउंटर मौजूदा और पिछली स्थिर विंडो का भारित मेल किनारे चिकने दो काउंटर अनुमानित, व्यवहार में बहुत करीब क्लाउडफ्लेयर ने अपनी किनारे वाली सीमा के लिए यह तरीका बताया
टोकन बकेट capacity टोकन की बाल्टी जो rate से भरती है; हर अनुरोध टोकन खर्च करता है क्षमता तक झटके, फिर स्थिर दर दो संख्याएं अपने मॉडल के लिए सटीक एपीआई गेटवे, क्लाउड प्रदाताओं के एपीआई
लीकी बकेट अनुरोध कतार में लगते हैं और तय दर से निकलते हैं आउटपुट चिकना, देरी बढ़ती है कतार सटीक ट्रैफिक को आकार देना
जीसीआरए एक "सैद्धांतिक आगमन समय" का हिसाब रखता है टोकन बकेट जैसा एक समय मुहर सटीक दूरसंचार की विरासत; रेडिस-सेल जैसी लाइब्रेरी

मेरा डिफॉल्ट टोकन बकेट है। यह वे दो संख्याएं व्यक्त करता है जिनकी उत्पाद टीमों को सच में परवाह है (स्थिर दर और झटके का आकार), प्रति कुंजी दो फील्ड लेता है, और ग्राहकों को समझाना आसान है: "१०० प्रति सेकंड, झटके २०० तक।"

इंटरव्यू में स्थिर विंडो की सीमा वाली समस्या बोलकर बताना फायदेमंद है: १०० प्रति मिनट की सीमा पर कोई ग्राहक १२:००:५९ पर १०० अनुरोध और १२:०१:०० पर १०० और भेज सकता है, यानी दो सेकंड में २०० अनुरोध।


लिमिटर कहां रहता है

रेट लिमिटर आर्किटेक्चर: मोबाइल और एपीआई ग्राहक सीमा फिल्टर वाले एपीआई गेटवे तक पहुंचते हैं, जो रेडिस क्लस्टर और स्थानीय टोकन कैश से पूछता है, कॉन्फिग सेवा से नियम लेता है, अनुमति वाला ट्रैफिक बैकएंड तक भेजता है और फैसले मेट्रिक्स धारा में डालता है

  • मोबाइल ऐप / एपीआई ग्राहक। अविश्वसनीय कॉल करने वाले। कुछ में बग हैं, कुछ शत्रुतापूर्ण हैं, ज्यादातर ठीक हैं।
  • एपीआई गेटवे और सीमा फिल्टर। लागू करने की जगह। यह पहले से प्रमाणीकरण और रूटिंग पढ़ रहा है, इसलिए कुंजी बनाने के लिए पहचान और रूट इसके पास है। यहां लागू करने से एक ही क्रियान्वयन से हर बैकएंड सुरक्षित होता है (एनवॉय की वैश्विक रेट लिमिट सेवा और ज्यादातर प्रबंधित गेटवे यही ढांचा अपनाते हैं)।
  • नियम कॉन्फिग। एक छोटी सेवा या कॉन्फिग भंडार जो हर स्तर, किरायेदार और रूट की सीमाएं तय करता है। गेटवे हर कुछ सेकंड में नियम खींचकर मेमोरी में रखते हैं, ताकि कॉन्फिग बंद होने पर ट्रैफिक कभी न रुके।
  • रेडिस क्लस्टर (काउंटर)। साझा स्थिति। हर फैसला पहले से लोड की गई लुआ स्क्रिप्ट का एक EVALSHA है, जिससे पढ़ना, भरना और घटाना परमाणु बन जाता है।
  • स्थानीय टोकन कैश। बेहद गर्म कुछ कुंजियों (एक बड़ा किरायेदार जो एक रूट पर हथौड़ा चला रहा है) के लिए गेटवे रेडिस से टोकन का छोटा बैच किराए पर लेकर स्थानीय रूप से खर्च कर सकते हैं, थोड़ी सटीकता के बदले बहुत कम चक्कर।
  • ऑर्डर एपीआई / खोज एपीआई। बैकएंड को सिर्फ अनुमति वाला ट्रैफिक दिखता है। महंगे रूट (खोज) की सीमाएं सस्ते रूट (ऑर्डर पढ़ना) से कड़ी होती हैं।
  • सीमा मेट्रिक्स। हर फैसला असिंक्रोनस रूप से भेजा जाता है, ताकि आप देख सकें किसे रोका जा रहा है, नियम सुधार सकें और गलत रोक पकड़ सकें। यह धारा कभी अनुरोध के रास्ते पर नहीं होनी चाहिए।

चित्र का लाल नोट इस सिस्टम का सबसे अहम नीति फैसला है, जिसकी बात नीचे है।


एक अनुरोध का फैसले वाला रास्ता

रेट लिमिटर का फैसले वाला रास्ता: अनुरोध आता है, उपयोगकर्ता, आईपी और रूट से कुंजी बनती है, नियम मिलाया जाता है, परमाणु टोकन बकेट चलता है, फिर बैकएंड तक भेजा जाता है या ४२९ और दोबारा कोशिश के समय के साथ ठुकराया जाता है

१. कुंजी बनाएं। जैसे rl:{tenant:acme}:route:search। कोष्ठक रेडिस क्लस्टर का हैश टैग हैं, जो किसी किरायेदार की सारी कुंजियां एक ही स्लॉट में रखते हैं, ताकि कभी कई कुंजियों वाला काम करना पड़े तो हो सके। २. नियम मिलाएं। सबसे खास नियम जीतता है: रूट के लिए किरायेदार का अपवाद, फिर किरायेदार का स्तर, फिर डिफॉल्ट स्तर। जिस अनुरोध पर कोई नियम न मिले, उसे भी डिफॉल्ट मिलता है, "असीमित" कभी नहीं। ३. बाल्टी को परमाणु रूप से चलाएं। पढ़ना, भरना, खर्च करना और लिखना एक ही काम होना चाहिए। दो गेटवे अगर साथ-साथ पढ़कर फिर लिखें, तो दोनों को टोकन दिखेगा और दोनों उसे खर्च कर देंगे। ४. अनुमति दें या ठुकराएं। अनुमति वाले अनुरोध बचे बजट के हेडर के साथ आगे जाते हैं। ठुकराए गए अनुरोधों को 429 Too Many Requests और Retry-After मिलता है।

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

-- KEYS[1]: bucket key   ARGV[1]: capacity   ARGV[2]: refill tokens/sec   ARGV[3]: cost
local cap, rate, cost = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
local t = redis.call('TIME')  -- Redis 5+ replicates script effects, so TIME is safe here
local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)

local b = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = tonumber(b[1]) or cap
local ts = tonumber(b[2]) or now

tokens = math.min(cap, tokens + (now - ts) / 1000 * rate)
local allowed = 0
if tokens >= cost then
  tokens = tokens - cost
  allowed = 1
end

redis.call('HSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', KEYS[1], math.ceil(cap / rate * 1000) + 1000)

local retry_ms = 0
if allowed == 0 then retry_ms = math.ceil((cost - tokens) / rate * 1000) end
return { allowed, math.floor(tokens), retry_ms }

PEXPIRE दिखने से ज्यादा अहम है: निष्क्रिय बाल्टियां तब खत्म हो जाती हैं जब वे पूरी भर चुकी होतीं, इसलिए मेमोरी सक्रिय ग्राहकों के हिसाब से बढ़ती है, हर उस ग्राहक के हिसाब से नहीं जो कभी आया था।


ग्राहकों को बताना कि क्या हुआ

बिना वजह बताए ठुकराने वाला लिमिटर सहायता टिकट बढ़ाता है और ग्राहकों की दोबारा कोशिशों को बदतर बनाता है।

  • स्थिति: 429 Too Many Requests (आरएफसी ६५८५ में परिभाषित)। प्रति ग्राहक सीमा के लिए 503 का इस्तेमाल न करें; वह बताता है कि पूरी सेवा बंद है।
  • Retry-After: कितने सेकंड बाद दोबारा कोशिश सफल हो सकती है (आरएफसी ९११०)। ऊपर की लुआ स्क्रिप्ट ठीक यही संख्या लौटाती है।
  • बजट हेडर: गिटहब का एपीआई हर जवाब में x-ratelimit-limit, x-ratelimit-remaining और x-ratelimit-reset लौटाता है, और आईईटीएफ का एचटीटीपीएपीआई कार्य समूह RateLimit और RateLimit-Policy हेडर का मानक बना रहा है। एक परंपरा चुनें और उसे दस्तावेज में लिखें।
  • ग्राहकों के लिए सलाह: प्रकाशित करें कि ग्राहक बढ़ते अंतराल और थोड़े बेतरतीब फर्क के साथ दोबारा कोशिश करें। बिना बेतरतीब फर्क के हर रोका गया ग्राहक एक ही पल में दोबारा कोशिश करता है और एक साथ भगदड़ मच जाती है।

खुला विफल हो या बंद?

जब रेडिस धीमा हो या न मिले, तो लिमिटर को अंदाजा लगाना पड़ता है। इसका कोई सार्वभौमिक सही जवाब नहीं है, इसलिए इसे वैश्विक नहीं, हर रूट के लिए तय करना चाहिए।

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

रेडिस कॉल पर कड़ी समय सीमा रखें (कुछ मिलीसेकंड)। जो लिमिटर रेडिस के लिए २ सेकंड इंतजार करता है, वह पहले ही विफल हो चुका है, बस धीरे-धीरे।


बड़े पैमाने पर दिखने वाली समस्याएं

गर्म कुंजियां। एक विशाल किरायेदार ट्रैफिक का बड़ा हिस्सा एक रेडिस कुंजी और इसलिए एक हिस्से पर केंद्रित कर सकता है। विकल्प: कुंजी बांटें (rl:{acme}:search:0..7, हर एक को दर का आठवां हिस्सा), या स्थानीय कैश के जरिए गेटवे को बैच में टोकन किराए पर दें। दोनों थोड़ी सटीकता के बदले क्षमता देते हैं।

कई क्षेत्र। क्षेत्रों के पार वैश्विक सीमा के लिए या तो वैश्विक स्टोर चाहिए (देरी), या बंटा हुआ बजट (हर क्षेत्र को एक हिस्सा, समय-समय पर संतुलित)। ज्यादातर सिस्टम क्षेत्रवार बजट चुनते हैं और मान लेते हैं कि दो क्षेत्रों पर जाने वाले ग्राहक को तय सीमा से थोड़ा ज्यादा मिलेगा।

घड़ियों का अंतर। बाल्टी के हिसाब में गेटवे की घड़ियां इस्तेमाल करने से दो नोड बीते समय पर असहमत होते हैं। ऊपर की तरह रेडिस का TIME इस्तेमाल करने से यह नहीं होता। अगर आपके स्टोर में सर्वर घड़ी नहीं है, तो एक ही अधिकारी पर लगातार बढ़ने वाला समय इस्तेमाल करें।

दोबारा कोशिशें भार बढ़ाती हैं। ग्राहक, एसडीके और सर्विस मेश सब दोबारा कोशिश करते हैं। अगर हर परत तीन बार कोशिश करे, तो एक उपयोगकर्ता अनुरोध दर्जनों बन सकता है। दोबारा कोशिशों को उसी बजट में गिनें, और पक्का करें कि अंदरूनी दोबारा कोशिशें भी Retry-After मानें।

सीमाएं उत्पाद हैं। सीमाएं आपकी कीमत और अनुबंध का हिस्सा हैं। उन्हें चुपचाप बदलना एकीकरण तोड़ता है। उन्हें एपीआई की तरह संस्करण दें और घोषित करें।


इंटरव्यू के आकार का जवाब

१. साफ करें कि किस पर सीमा है (उपयोगकर्ता, आईपी, कुंजी) और सीमाएं सख्त हैं या लचीली। २. लिमिटर गेटवे में रखें, साझा स्थिति रेडिस में। ३. टोकन बकेट चुनें, और वजह के तौर पर स्थिर विंडो की सीमा वाली समस्या बताएं। ४. लुआ स्क्रिप्ट से जांच को परमाणु बनाएं। ५. Retry-After के साथ 429 लौटाएं। ६. हर एंडपॉइंट के लिए खुला या बंद विफल होने और गर्म कुंजी की समस्या पर बात करें।

ज्यादातर इंटरव्यू लेने वाले यही सुनना चाहते हैं। बाकी गहराई समय हो तो दें।


उत्पादन जांच सूची

  • हर स्तर, किरायेदार और रूट के नियम तय, बाकी सबके लिए डिफॉल्ट स्तर
  • परमाणु जांच और खर्च (लुआ या सर्वर पर समान काम)
  • बाल्टी के हिसाब के लिए सर्वर का समय
  • निष्क्रिय होने पर बाल्टी की कुंजियां खत्म होती हैं
  • स्टोर कॉल पर कड़ी समय सीमा, हर रूट के लिए खुली या बंद विफलता नीति
  • ग्राहकों के लिए 429, Retry-After और बजट हेडर दस्तावेज में
  • गर्म कुंजी का इलाज (बंटी कुंजियां या स्थानीय टोकन किराया)
  • फैसले मेट्रिक्स में; किरायेदार और रूट के हिसाब से रोक दर के डैशबोर्ड
  • महंगे एंडपॉइंट के लिए अलग समवर्ती सीमाएं
  • भार परीक्षण जिसमें रेडिस का धीमा होना भी हो, सिर्फ बंद होना नहीं

जो बचा रह गया

रेट लिमिटर इकलौता हिस्सा है जिसका काम अपने ही ग्राहकों को ना कहना है। इसे थोड़ा भी गलत सेट करें, तो जिन्हें आप रोकते हैं वे आपके सबसे अच्छे ग्राहक होते हैं, जिनके एकीकरण आपके नियमों से तेज बढ़ गए।

इसलिए हर इनकार दर्ज कीजिए, और वह लॉग पढ़िए। उसमें कहीं एक ऐसा ग्राहक है जिसे रोकने के बजाय आपको फोन करना चाहिए था।


स्रोत