2009-09-03 14 views
50

मेरी साइट के वैध उपयोगकर्ता कभी-कभी सर्वर अनुरोधों के साथ सर्वर को हथौड़ा देते हैं जो अवांछित परिणाम का कारण बनता है। मैं हर 5 सेकंड या एन कॉल प्रति मिनट एक एपीआई कॉल कहने के अलावा और अधिक की एक सीमा स्थापित करना चाहता हूं (अभी तक सटीक सीमा का पता नहीं लगाया है)। मैं स्पष्ट रूप से डीबी में प्रत्येक एपीआई कॉल लॉग कर सकता हूं और यह देखने के लिए प्रत्येक अनुरोध पर गणना कर सकता हूं कि वे सीमा से अधिक हैं, लेकिन हर अनुरोध पर यह अतिरिक्त ओवरहेड इस उद्देश्य को हरा देगा। एक सीमा स्थापित करने के लिए मैं अन्य कम संसाधन-गहन तरीकों का उपयोग कर सकता हूं? मैं PHP/अपाचे/लिनक्स का उपयोग कर रहा हूं, इसके लायक क्या है।मैं अपनी साइट के एपीआई उपयोगकर्ताओं को कैसे थ्रॉटल करूं?

+0

यह सिर्फ एक पट्टी, जबकि आप एपीआई tweak या अधिक सर्वर जोड़ने है: मुझे लगता है कि आप के लिए सभी को किया था? डेवलपर्स पर कुछ प्रतिबंध/प्रतिबंध लगाने से बहुत खतरनाक है ... –

+11

नहीं, मैं साइट को टिकाऊ बनाने के लिए उचित सीमाएं लगाने की कोशिश कर रहा हूं। कुछ अति उत्साही उपयोगकर्ताओं के लिए सर्वर क्षमता जोड़ना योजना का हिस्सा नहीं है। – scotts

उत्तर

48

ठीक है, मैंने के बिना किसी भी सर्वर से लिखने के लिए कोई रास्ता नहीं है, लेकिन मैं कम से कम प्रत्येक अनुरोध को लॉगिंग को समाप्त कर सकता हूं। एक तरीका है "लीकी बाल्टी" थ्रॉटलिंग विधि का उपयोग करके, जहां यह केवल अंतिम अनुरोध() का ट्रैक रखता है और समय सीमा ($minute_throttle) के लिए अनुरोध/सीमा की संख्या का अनुपात रखता है। लीकी बाल्टी कभी भी अपने काउंटर को रीसेट नहीं करती है (ट्विटर एपीआई के थ्रॉटल के विपरीत जो हर घंटे रीसेट करता है), लेकिन यदि बाल्टी भर जाती है (उपयोगकर्ता सीमा तक पहुंच जाता है), तो उन्हें n सेकेंड का इंतजार करना होगा ताकि बाल्टी को एक और अनुरोध करने से पहले थोड़ा खाली कर दिया जा सके। । दूसरे शब्दों में यह एक रोलिंग सीमा की तरह है: यदि समय सीमा के भीतर पिछले अनुरोध हैं, तो वे धीरे-धीरे बाल्टी से बाहर निकल रहे हैं; यदि आप बाल्टी भरते हैं तो यह केवल आपको प्रतिबंधित करता है।

यह कोड स्निपेट प्रत्येक अनुरोध पर एक नए $minute_throttle मान की गणना करेगा। मैंने मिनट$minute_throttle में निर्दिष्ट किया है क्योंकि आप किसी भी समय अवधि, जैसे कि प्रति घंटा, दैनिक, आदि के लिए थ्रॉटल जोड़ सकते हैं ... हालांकि एक से अधिक उपयोगकर्ता इसे उपयोगकर्ताओं के लिए भ्रमित करने लगेंगे।

$minute = 60; 
$minute_limit = 100; # users are limited to 100 requests/minute 
$last_api_request = $this->get_last_api_request(); # get from the DB; in epoch seconds 
$last_api_diff = time() - $last_api_request; # in seconds 
$minute_throttle = $this->get_throttle_minute(); # get from the DB 
if (is_null($minute_limit)) { 
    $new_minute_throttle = 0; 
} else { 
    $new_minute_throttle = $minute_throttle - $last_api_diff; 
    $new_minute_throttle = $new_minute_throttle < 0 ? 0 : $new_minute_throttle; 
    $new_minute_throttle += $minute/$minute_limit; 
    $minute_hits_remaining = floor(($minute - $new_minute_throttle) * $minute_limit/$minute ); 
    # can output this value with the request if desired: 
    $minute_hits_remaining = $minute_hits_remaining >= 0 ? $minute_hits_remaining : 0; 
} 

if ($new_minute_throttle > $minute) { 
    $wait = ceil($new_minute_throttle - $minute); 
    usleep(250000); 
    throw new My_Exception ('The one-minute API limit of ' . $minute_limit 
     . ' requests has been exceeded. Please wait ' . $wait . ' seconds before attempting again.'); 
} 
# Save the values back to the database. 
$this->save_last_api_request(time()); 
$this->save_throttle_minute($new_minute_throttle); 
+1

क्या आप समझा सकते हैं कि '$ minute_limit' शून्य क्यों होगा? – nrathaus

+0

मुझे लगता है कि यह '$ min_throttle' होना चाहिए, क्योंकि यह डीबी से आ रहा है। – Sljux

+1

रेटलिमीटर को अपनी कक्षा में निकालने के बारे में सोचा? – mblaettermann

1

आप कहते हैं कि "हर अनुरोध पर अतिरिक्त ओवरहेड सभी उद्देश्य को हरा देगा", लेकिन मुझे यकीन नहीं है कि यह सही है। क्या आपके सर्वर की हथौड़ा को रोकने का उद्देश्य नहीं है? यह शायद मैं इसे लागू करने का तरीका हूं, क्योंकि वास्तव में केवल एक त्वरित पढ़ने/लिखने की आवश्यकता होती है। यदि आप प्रदर्शन के बारे में चिंतित थे तो आप एपीआई सर्वर चेक को एक अलग डीबी/डिस्क पर भी खेत कर सकते हैं।

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

+0

हाँ, मुझे शायद डिस्क पर कुछ सहेजना होगा .. अधिमानतः हर एक लॉग अनुरोध नहीं। मैं केवल अंतिम सफल एपीआई अनुरोध को बचा सकता हूं और यह सुनिश्चित कर सकता हूं कि यह उसके बाद सेकंड सेकंड है। – scotts

3

सरल समाधान बस प्रत्येक API कुंजी 24 घंटे प्रति अनुरोध की एक सीमित संख्या देने के लिए हो सकता है, और उन्हें कुछ जाना जाता है, तय समय पर रीसेट हो जाएंगे।

तो वे अपने API अनुरोधों (यानी। काउंटर शून्य तक पहुंच, या सीमा, दिशा आप गिनती कर रहे हैं पर निर्भर करता है), जब तक आप अपने काउंटर रीसेट उन्हें डेटा की प्रस्तुति बंद निकास।

इस तरह, यह उनके सर्वोत्तम हित में अनुरोध के साथ आप हथौड़ा नहीं करने के लिए होगा।

1

खरोंच से कार्यान्वयन के अलावा आप आप भी जो दर अन्य सामग्री (एनालिटिक्स आदि) का एक समूह के रूप में भी सीमित करता है 3scale (http://www.3scale.net) की तरह एपीआई के बुनियादी ढांचे पर एक नज़र ले जा सकते हैं। इसके लिए एक PHP प्लगइन है: https://github.com/3scale/3scale_ws_api_for_php

तुम भी एपीआई के वार्निश सामने की तरह कुछ चिपक जाते हैं और इस तरह सीमित API दर कर सकते हैं।

4

मुझे नहीं पता कि यह धागा अभी भी जिंदा है या नहीं, लेकिन मैं इन आंकड़ों को मेमोरीड जैसे मेमोरी कैश में रखने का सुझाव दूंगा। यह डीबी को अनुरोध लॉगिंग करने के ऊपरी हिस्से को कम करेगा लेकिन फिर भी इस उद्देश्य को पूरा करेगा।

+0

मैं पूरी तरह से सहमत हूं और हम इस तरह के साथ-साथ इसके परमाणु को भी लागू करते हैं। आप उन्हें स्टोर करने के लिए एडब्ल्यूएस लोचदार दर्द जैसे कुछ का उपयोग कर सकते हैं और उसके बाद एक cronjob सिर्फ डेटाबेस में समेकित परिणाम लॉग इन करें। हमारे पास वास्तव में वृद्धि करने के लिए प्रति सर्वर एक छोटा सा memcached उदाहरण होता है और फिर एक मिनट में लोचदार होने के लिए इसे फ्लश/बढ़ाएं - इस तरह आप बाधा को लोचदारता में नहीं ले जाते हैं। – Ross

+0

@ केदार आप अभी भी विभिन्न प्रकार के विश्लेषण के लिए फ़ाइल में सभी कॉल लॉग कर सकते हैं, जो डिस्क डीफर पर लिखने के लिए बस आपके डीबी को परेशान नहीं करेगा। – kommradHomer

+0

क्या रेडिस बेहतर समाधान होगा? यह राम में है लेकिन गैर अस्थिर भी है? – BeardedGeek

7

आप token bucket algorithm के साथ दर को नियंत्रित कर सकते हैं, जो लीकी बाल्टी एल्गोरिदम से तुलनीय है। ध्यान दें कि आपको प्रक्रियाओं (या जो भी गुंजाइश आप नियंत्रित करना चाहते हैं) पर बाल्टी की स्थिति (यानी टोकन की मात्रा) साझा करना होगा। तो आप दौड़ की स्थिति से बचने के लिए लॉकिंग के बारे में सोचना चाह सकते हैं।

अच्छी खबर: bandwidth-throttle/token-bucket

use bandwidthThrottle\tokenBucket\Rate; 
use bandwidthThrottle\tokenBucket\TokenBucket; 
use bandwidthThrottle\tokenBucket\storage\FileStorage; 

$storage = new FileStorage(__DIR__ . "/api.bucket"); 
$rate = new Rate(10, Rate::SECOND); 
$bucket = new TokenBucket(10, $rate, $storage); 
$bucket->bootstrap(10); 

if (!$bucket->consume(1, $seconds)) { 
    http_response_code(429); 
    header(sprintf("Retry-After: %d", floor($seconds))); 
    exit(); 
} 
+0

टोकन बाल्टी एल्गोरिदम के लिंक के लिए धन्यवाद - इसके बिना मुझे एहसास नहीं होता कि यह और लीकी बाल्टी बोनफाइड एल्गोरिदम थे। – Colin

संबंधित मुद्दे