रेट लिमिटिंग का उपयोग क्यों करें?
आइए कुछ बुनियादी बातों से शुरू करें ताकि हम रेट लिमिटिंग की आवश्यकता को समझ सकें और उन टूल्स को पेश कर सकें जिनका हम इस ट्यूटोरियल में उपयोग करेंगे।
असीमित दरों के साथ समस्या
यदि Twitter API जैसे पब्लिक API ने अपने यूजर्स को प्रति घंटे असीमित संख्या में रिक्वेस्ट करने की अनुमति दी, तो इससे निम्न हो सकता है:
- संसाधन थकावट (resource exhaustion)
- सेवा की गुणवत्ता में कमी
- सेवा से इनकार (denial of service) हमले
इसके परिणामस्वरूप एक ऐसी स्थिति हो सकती है जहां सेवा अनुपलब्ध या धीमी हो। इससे सेवा द्वारा अप्रत्याशित लागत भी हो सकती है।
रेट लिमिटिंग कैसे मदद करता है
सबसे पहले, रेट-लिमिटिंग सेवा से इनकार हमलों को रोक सकता है। जब डिडुप्लिकेशन मैकेनिज्म या API कुंजी के साथ जोड़ा जाता है, तो रेट लिमिटिंग डिस्ट्रिब्यूटेड डिनायल ऑफ सर्विस हमलों को रोकने में भी मदद कर सकता है।
दूसरे, यह ट्रैफिक का अनुमान लगाने में मदद करता है। यह पब्लिक API के लिए बहुत महत्वपूर्ण है। इसे स्वचालित स्क्रिप्ट के साथ जोड़ा जा सकता है ताकि सेवा की निगरानी और स्केलिंग की जा सके।
और तीसरा, आप इसका उपयोग टियर-आधारित मूल्य निर्धारण लागू करने के लिए कर सकते हैं। इस प्रकार का मूल्य निर्धारण मॉडल يعني है कि यूजर्स रिक्वेस्ट की उच्च दर के लिए भुगतान कर सकते हैं। Twitter API इसका एक उदाहरण है।
टोकन बकेट एल्गोरिदम
टोकन बकेट एक एल्गोरिदम है जिसका उपयोग आप रेट लिमिटिंग लागू करने के लिए कर सकते हैं। संक्षेप में, यह इस प्रकार काम करता है:
- एक बकेट को एक निश्चित क्षमता (टोकन की संख्या) के साथ बनाया जाता है।
- जब एक रिक्वेस्ट आती है, तो बकेट की जांच की जाती है। यदि पर्याप्त क्षमता है, तो रिक्वेस्ट को आगे बढ़ने की अनुमति दी जाती है। अन्यथा, रिक्वेस्ट को अस्वीकार कर दिया जाता है।
- जब एक रिक्वेस्ट की अनुमति दी जाती है, तो क्षमता कम हो जाती है।
- एक निश्चित समय के बाद, क्षमता फिर से भर दी जाती है।
डिस्ट्रिब्यूटेड सिस्टम में टोकन बकेट कैसे लागू करें
डिस्ट्रिब्यूटेड सिस्टम में टोकन बकेट एल्गोरिदम लागू करने के लिए, हमें एक डिस्ट्रिब्यूटेड कैश का उपयोग करने की आवश्यकता है।
कैश एक की-वैल्यू स्टोर है जो बकेट जानकारी को स्टोर करता है। हम इसे लागू करने के लिए एक Redis कैश का उपयोग करेंगे।
आंतरिक रूप से, Bucket4j हमें Java JCache API के किसी भी कार्यान्वयन में प्लग इन करने की अनुमति देता है। Redis का Redisson क्लाइंट वह कार्यान्वयन है जिसका हम उपयोग करेंगे।
प्रोजेक्ट कार्यान्वयन
हम अपनी सर्विस बनाने के लिए Spring Boot फ्रेमवर्क का उपयोग करेंगे।
हमारी सर्विस में नीचे दिए गए घटक होंगे:
- एक साधारण REST API।
- सर्विस से जुड़ा एक Redis कैश - Redisson क्लाइंट का उपयोग करके।
- REST API के चारों ओर लपेटा गया Bucket4J लाइब्रेरी।
- हम Bucket4J को JCache इंटरफेस से जोड़ेंगे जो बैकग्राउंड में कार्यान्वयन के रूप में Redisson क्लाइंट का उपयोग करेगा।
सबसे पहले, हम सभी रिक्वेस्ट के लिए API को रेट लिमिट करना सीखेंगे। फिर हम प्रति यूजर या प्रति मूल्य निर्धारण टियर अधिक जटिल रेट लिमिटिंग तंत्र लागू करना सीखेंगे।
आइए प्रोजेक्ट सेटअप से शुरू करें।
डिपेंडेंसी इंस्टॉल करें
आइए नीचे दी गई डिपेंडेंसी को अपने pom.xml (या build.gradle) फाइल में जोड़ें।
<dependencies>
<!-- REST API बनाने के लिए -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redisson Starter = Spring Data Redis starter (अन्य क्लाइंट्स को छोड़कर) और Redisson क्लाइंट -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
</dependency>
<!-- Bucket4J स्टार्टर = Bucket4J + JCache -->
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>bucket4j-spring-boot-starter</artifactId>
<version>0.5.2</version>
</dependency>
</dependencies>
कैश कॉन्फ़िगरेशन
सबसे पहले, हमें अपना Redis सर्वर शुरू करने की आवश्यकता है। मान लें कि हमारे स्थानीय मशीन पर पोर्ट 6379 पर एक Redis सर्वर चल रहा है।
हमें दो चरण करने होंगे:
- हमारे एप्लिकेशन से इस सर्वर से एक कनेक्शन बनाएं।
- JCache को Redisson क्लाइंट का उपयोग करके कार्यान्वयन के रूप में सेट करें।
Redisson का डॉक्यूमेंटेशन एक नियमित Java एप्लिकेशन में इसे लागू करने के लिए संक्षिप्त चरण प्रदान करता है। हम उन्हीं चरणों को लागू करने जा रहे हैं, लेकिन Spring Boot में।
आइए पहले कोड को देखें। हमें आवश्यक बीन्स बनाने के लिए एक कॉन्फ़िगरेशन क्लास बनाने की आवश्यकता है।
@Configuration
public class RedisConfig {
@Bean
public Config config() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
return config;
}
@Bean
public CacheManager cacheManager(Config config) {
CacheManager manager = Caching.getCachingProvider().getCacheManager();
cacheManager.createCache("cache", RedissonConfiguration.fromConfig(config));
return cacheManager;
}
@Bean
ProxyManager<String> proxyManager(CacheManager cacheManager) {
return new JCacheProxyManager<>(cacheManager.getCache("cache"));
}
}
यह क्या करता है?
- एक कॉन्फ़िगरेशन ऑब्जेक्ट बनाता है जिसका उपयोग हम कनेक्शन बनाने के लिए कर सकते हैं।
- कॉन्फ़िगरेशन ऑब्जेक्ट का उपयोग करके एक कैश मैनेजर बनाता है। यह आंतरिक रूप से Redis इंस्टेंस से एक कनेक्शन बनाएगा और उस पर "cache" नामक एक हैश बनाएगा।
- एक प्रॉक्सी मैनेजर बनाता है जिसका उपयोग कैश तक पहुंचने के लिए किया जाएगा। हमारा एप्लिकेशन JCache API का उपयोग करके जो कुछ भी कैश करने का प्रयास करता है, उसे Redis इंस्टेंस के अंदर "cache" नामक हैश पर कैश किया जाएगा।
API बनाएं
आइए एक साधारण REST API बनाएं।
@RestController
public class RateLimitController {
@GetMapping("/user/{id}")
public String getInfo(@PathVariable("id") String id) {
return "Hello " + id;
}
}
यदि मैं URL https://localhost:8080/user/1 के साथ API को हिट करता हूं, तो मुझे प्रतिक्रिया Hello 1 मिलेगी।
Bucket4J कॉन्फ़िगरेशन
रेट लिमिटिंग लागू करने के लिए, हमें Bucket4J कॉन्फ़िगर करना होगा। शुक्र है, स्टार्टर लाइब्रेरी के कारण हमें कोई बॉयलरप्लेट कोड लिखने की आवश्यकता नहीं है।
यह हमारे द्वारा पिछले चरण में बनाए गए ProxyManager बीन का स्वचालित रूप से पता लगाता है और बकेट को कैश करने के लिए इसका उपयोग करता है।
हमें जो करने की आवश्यकता है वह है इस लाइब्रेरी को हमारे द्वारा बनाए गए API के आसपास कॉन्फ़िगर करना।
फिर से इसे करने के कई तरीके हैं।
हम प्रॉपर्टी-आधारित कॉन्फ़िगरेशन के लिए जा सकते हैं जो स्टार्टर लाइब्रेरी में परिभाषित है।
यह सभी यूजर्स या सभी गेस्ट यूजर्स के लिए रेट-लिमिटिंग जैसे सरल मामलों के लिए सबसे सुविधाजनक तरीका है।
हालांकि, यदि हम प्रति यूजर रेट लिमिट जैसी अधिक जटिल चीज लागू करना चाहते हैं, तो इसके लिए कस्टम कोड लिखना बेहतर है।
हम प्रति यूजर रेट लिमिटिंग लागू करने जा रहे हैं। मान लें कि हमारे पास प्रत्येक यूजर के लिए रेट लिमिट एक डेटाबेस में स्टोर है, और हम यूजर आईडी का उपयोग करके इसे क्वेरी कर सकते हैं।
आइए इसके लिए कोड चरण दर चरण लिखें।
एक बकेट बनाएं
शुरू करने से पहले, आइए देखें कि एक बकेट कैसे बनाया जाता है।
Refill refill = Refill.intervally(10, Duration.ofMinutes(1));
Bandwidth limit = Bandwidth.classic(10, refill);
Bucket bucket = Bucket4j.builder()
.addLimit(limit)
.build();
- Refill - कितने समय बाद बकेट फिर से भरा जाएगा।
- Bandwidth - बकेट में कितनी बैंडविड्थ है। मूल रूप से, प्रति रिफिल अवधि रिक्वेस्ट।
- Bucket - इन दो पैरामीटर्स का उपयोग करके कॉन्फ़िगर किया गया एक ऑब्जेक्ट। इसके अलावा, यह एक टोकन काउंटर रखता है ताकि यह ट्रैक कर सके कि बकेट में कितने टोकन उपलब्ध हैं।
इसे बिल्डिंग ब्लॉक के रूप में उपयोग करके, आइए कुछ चीजों को बदलें ताकि यह हमारे उपयोग के मामले के लिए उपयुक्त हो।
ProxyManager का उपयोग करके बकेट बनाएं और कैश करें
हमने Redis पर बकेट स्टोर करने के उद्देश्य से प्रॉक्सी मैनेजर बनाया था। एक बार बकेट बन जाने के बाद, इसे Redis पर कैश करने की आवश्यकता होती है और इसे फिर से बनाने की आवश्यकता नहीं होती।
इसे बनाने के लिए, हम Bucket4j.builder() को proxyManager.builder() से बदल देंगे। ProxyManager बकेट को कैश करने और उन्हें फिर से न बनाने का ख्याल रखेगा।
ProxyManager का बिल्डर दो पैरामीटर लेता है - एक कुंजी जिसके विरुद्ध बकेट कैश किया जाएगा और एक कॉन्फ़िगरेशन ऑब्जेक्ट जिसका उपयोग यह बकेट बनाने के लिए करेगा।
आइए देखें कि हम इसे कैसे लागू कर सकते हैं:
@Service
public class RateLimiter {
// डिपेंडेंसी ऑटोवायरिंग
public Bucket resolveBucket(String key) {
Supplier<BucketConfiguration> configSupplier = getConfigSupplierForUser(key);
// हमेशा एक नया बकेट नहीं बनाता, बल्कि यदि मौजूद है तो मौजूदा को लौटाता है।
return buckets.builder().build(key, configSupplier);
}
private Supplier<BucketConfiguration> getConfigSupplierForUser(String key) {
User user = userRepository.findById(userId);
Refill refill = Refill.intervally(user.getLimit(), Duration.ofMinutes(1));
Bandwidth limit = Bandwidth.classic(user.getLimit(), refill);
return () -> (BucketConfiguration.builder()
.addLimit(limit)
.build());
}
}
हमने एक मेथड बनाया है जो प्रदान की गई कुंजी के लिए एक बकेट लौटाता है। अगले चरण में, हम देखेंगे कि इसका उपयोग कैसे करना है।
टोकन कैसे उपभोग करें और रेट लिमिटिंग सेट करें
जब एक रिक्वेस्ट आती है, तो हम संबंधित बकेट से एक टोकन उपभोग करने का प्रयास करेंगे।
हम ऐसा करने के लिए बकेट के tryConsume() मेथड का उपयोग करेंगे।
@GetMapping("/user/{id}")
public String getInfo(@PathVariable("id") String id) {
// यूजर के लिए बकेट प्राप्त करें
Bucket bucket = rateLimiter.resolveBucket(id);
// बकेट से एक टोकन उपभोग करने का प्रयास करें
if (bucket.tryConsume(1)) {
return "Hello " + id;
} else {
return "Rate limit exceeded";
}
}
tryConsume() मेथड true लौटाता है यदि टोकन सफलतापूर्वक उपभोग किया गया था या false यदि टोकन उपभोग नहीं किया गया था।
अपनी सर्विस को कैसे टेस्ट करें
हम इसे किसी भी ऑटोमेटेड टेस्टिंग तकनीक का उपयोग करके टेस्ट कर सकते हैं। उदाहरण के लिए, हम JUnit का उपयोग कर सकते हैं। आइए एक टेस्ट केस लिखें जो getInfo() मेथड को कई बार कॉल करता है और सत्यापित करता है कि प्रतिक्रिया सही है।
मान लें कि हमारे पास आईडी 1 वाला एक यूजर है और प्रति मिनट 10 रिक्वेस्ट की सीमा है। मान लें कि हमारे पास आईडी 2 वाला एक यूजर भी है और प्रति मिनट 20 रिक्वेस्ट की सीमा है।
हम दोनों यूजर्स के लिए 11 रिक्वेस्ट करेंगे और सत्यापित करेंगे कि आईडी 1 वाले यूजर के लिए रिक्वेस्ट विफल होती है लेकिन आईडी 2 वाले यूजर के लिए सफल होती है।
@Test
public void testGetInfo() {
// यूजर 1 के लिए मेथड को 10 बार कॉल करें
for (int i = 0; i < 10; i++) {
rateLimiter.getInfo(1));
rateLimiter.getInfo(2));
}
// सत्यापित करें कि यूजर 1 के लिए प्रतिक्रिया रेट लिमिटेड है
assertEquals("Rate limit exceeded", rateLimiter.getInfo(1));
// सत्यापित करें कि यूजर 2 के लिए प्रतिक्रिया सफल है
assertEquals("Hello 2", rateLimiter.getInfo(2));
}
जब हम टेस्ट चलाते हैं, तो हम देखेंगे कि टेस्ट पास होता है।
निष्कर्ष
इस ट्यूटोरियल में, हमने कवर किया है कि Spring Boot एप्लिकेशन में Bucket4j और Redis का उपयोग करके रेट लिमिटर कैसे बनाया जाए। हमने यह भी देखा कि JCache के साथ Redisson क्लाइंट कैसे सेट करें और बकेट को कैश करने के लिए इसका उपयोग कैसे करें।
अंत में, हमने एक साधारण रेट लिमिटर लागू किया जिसका उपयोग विशिष्ट यूजर्स के लिए रिक्वेस्ट को रेट लिमिट करने के लिए किया जा सकता है।
उम्मीद है आपको यह ट्यूटोरियल पसंद आया। पढ़ने के लिए धन्यवाद!
मुफ़्त में कोड करना सीखें। freeCodeCamp का ओपन सोर्स पाठ्यक्रम 40,000 से अधिक लोगों को डेवलपर के रूप में नौकरी पाने में मदद कर चुका है। शुरू करें