हम इसे लागू करने के लिए Bucket4J लाइब्रेरी का उपयोग करेंगे और हम वितरित कैश के रूप में Redis का उपयोग करेंगे।
दर सीमित का उपयोग क्यों करें?
<पी> आइए कुछ बुनियादी बातों से शुरुआत करें ताकि यह सुनिश्चित हो सके कि हम दर सीमित करने की आवश्यकता को समझते हैं और उन उपकरणों का परिचय देते हैं जिनका उपयोग हम इस ट्यूटोरियल में करेंगे।असीमित दरों के साथ समस्या
<पी> यदि ट्विटर एपीआई जैसी कोई सार्वजनिक एपीआई अपने उपयोगकर्ताओं को प्रति घंटे असीमित संख्या में अनुरोध करने की अनुमति देती है, तो इसका परिणाम यह हो सकता है:- संसाधन की कमी
- सेवा की गुणवत्ता में कमी
- सेवा हमलों से इनकार
दर सीमित करने से कैसे मदद मिलती है
<पी> सबसे पहले, दर-सीमित करने से सेवा हमलों से इनकार को रोका जा सकता है। जब एक डिडुप्लीकेशन तंत्र या एपीआई कुंजियों के साथ जोड़ा जाता है, तो दर सीमित करने से सेवा हमलों के वितरित इनकार को रोकने में भी मदद मिल सकती है। <पी> दूसरे, इससे ट्रैफिक का अनुमान लगाने में मदद मिलती है. सार्वजनिक एपीआई के लिए यह बहुत महत्वपूर्ण है। सेवा की निगरानी और पैमाने के लिए इसे स्वचालित स्क्रिप्ट के साथ भी जोड़ा जा सकता है। <पी> और तीसरा, आप इसका उपयोग टियर-आधारित मूल्य निर्धारण को लागू करने के लिए कर सकते हैं। इस प्रकार के मूल्य निर्धारण मॉडल का अर्थ है कि उपयोगकर्ता अनुरोधों की उच्च दर के लिए भुगतान कर सकते हैं। ट्विटर एपीआई इसका एक उदाहरण है।टोकन बकेट एल्गोरिथम
<पी> टोकन बकेट एक एल्गोरिदम है जिसका उपयोग आप दर सीमित करने के लिए कर सकते हैं। संक्षेप में, यह इस प्रकार काम करता है:- एक बाल्टी एक निश्चित क्षमता (टोकन की संख्या) के साथ बनाई जाती है।
- जब कोई अनुरोध आता है, तो बकेट की जाँच की जाती है। यदि पर्याप्त क्षमता है, तो अनुरोध को आगे बढ़ने की अनुमति दी जाती है। अन्यथा, अनुरोध अस्वीकार कर दिया जाता है।
- जब किसी अनुरोध को अनुमति दी जाती है, तो क्षमता कम हो जाती है।
- एक निश्चित समय के बाद, क्षमता पुनः भर दी जाती है।
वितरित सिस्टम में टोकन बकेट कैसे लागू करें
<पी> एक वितरित सिस्टम में टोकन बकेट एल्गोरिदम को लागू करने के लिए, हमें वितरित कैश का उपयोग करने की आवश्यकता है . <पी> कैश एक की-वैल्यू स्टोर है बकेट जानकारी संग्रहीत करने के लिए. इसे लागू करने के लिए हम रेडिस कैश का उपयोग करेंगे। <पी> आंतरिक रूप से, Bucket4j हमें Java JCache API के किसी भी कार्यान्वयन में प्लग इन करने की अनुमति देता है। रेडिस का रेडिसन क्लाइंट वह कार्यान्वयन है जिसका हम उपयोग करेंगे।परियोजना कार्यान्वयन
<पी> हम अपनी सेवा बनाने के लिए स्प्रिंग बूट फ्रेमवर्क का उपयोग करेंगे। <पी> हमारी सेवा में निम्नलिखित घटक शामिल होंगे:- एक सरल REST API।
- रेडिसन क्लाइंट का उपयोग करके - सेवा से जुड़ा एक रेडिस कैश।
- Bucket4J लाइब्रेरी REST API के चारों ओर लिपटी हुई है।
- हम Bucket4J को JCache इंटरफ़ेस से कनेक्ट करेंगे जो पृष्ठभूमि में कार्यान्वयन के रूप में Redisson क्लाइंट का उपयोग करेगा।
निर्भरताएं स्थापित करें
<पी> आइए नीचे दी गई निर्भरताएँ हमारे pom.xml में जोड़ें (या build.gradle ) फ़ाइल.<dependencies>
<!-- To build the Rest API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redisson Starter = Spring Data Redis starter(excluding other clients) and Redisson client -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
</dependency>
<!-- Bucket4J starter = Bucket4J + JCache -->
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>bucket4j-spring-boot-starter</artifactId>
<version>0.5.2</version>
</dependency>
</dependencies>
कैश कॉन्फ़िगरेशन
<पी> सबसे पहले, हमें अपना रेडिस सर्वर शुरू करना होगा। मान लीजिए कि हमारी स्थानीय मशीन पर पोर्ट 6379 पर एक रेडिस सर्वर चल रहा है। <पी> हमें दो चरण पूरे करने होंगे:- हमारे एप्लिकेशन से इस सर्वर से एक कनेक्शन बनाएं।
- Redisson क्लाइंट को कार्यान्वयन के रूप में उपयोग करने के लिए JCache सेट करें।
@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"));
}
}
<पी> यह क्या करता है? पी> - एक कॉन्फ़िगरेशन ऑब्जेक्ट बनाता है जिसका उपयोग हम कनेक्शन बनाने के लिए कर सकते हैं।
- कॉन्फ़िगरेशन ऑब्जेक्ट का उपयोग करके कैश मैनेजर बनाता है। यह आंतरिक रूप से रेडिस इंस्टेंस से एक कनेक्शन बनाएगा और उस पर "कैश" नामक एक हैश बनाएगा।
- एक प्रॉक्सी मैनेजर बनाता है जिसका उपयोग कैश तक पहुंचने के लिए किया जाएगा। जो कुछ भी हमारा एप्लिकेशन JCache API का उपयोग करके कैश करने का प्रयास करता है, उसे "कैश" नामक हैश के अंदर रेडिस इंस्टेंस पर कैश किया जाएगा।
एपीआई बनाएं
<पी> आइए एक सरल REST API बनाएं।@RestController
public class RateLimitController {
@GetMapping("/user/{id}")
public String getInfo(@PathVariable("id") String id) {
return "Hello " + id;
}
}
<पी> यदि मैं यूआरएल http://localhost:8080/user/1 के साथ एपीआई को हिट करता हूं , मुझे प्रतिक्रिया मिलेगी Hello 1 . बकेट4J कॉन्फ़िगरेशन
<पी> दर सीमित करने को लागू करने के लिए, हमें Bucket4J को कॉन्फ़िगर करने की आवश्यकता है। शुक्र है, स्टार्टर लाइब्रेरी के कारण हमें कोई बॉयलरप्लेट कोड लिखने की आवश्यकता नहीं है। <पी> यह स्वचालित रूप से ProxyManager बीन का भी पता लगाता है हमने पिछले चरण में बनाया था और इसका उपयोग बकेट को कैश करने के लिए किया था। <पी> हमें जो करने की ज़रूरत है वह इस लाइब्रेरी को हमारे द्वारा बनाए गए एपीआई के आसपास कॉन्फ़िगर करना है।फिर ऐसा करने के कई तरीके हैं। <पी> हम संपत्ति-आधारित कॉन्फ़िगरेशन के लिए जा सकते हैं जिसे स्टार्टर लाइब्रेरी में परिभाषित किया गया है।
यह सभी उपयोगकर्ताओं या सभी अतिथि उपयोगकर्ताओं के लिए दर-सीमित करने जैसे सरल मामलों के लिए सबसे सुविधाजनक तरीका है। <पी> हालाँकि, यदि हम प्रत्येक उपयोगकर्ता के लिए दर सीमा जैसी कुछ अधिक जटिल चीज़ लागू करना चाहते हैं, तो इसके लिए कस्टम कोड लिखना बेहतर है। <पी> हम प्रति उपयोगकर्ता दर सीमित करने जा रहे हैं। आइए मान लें कि हमारे पास डेटाबेस में संग्रहीत प्रत्येक उपयोगकर्ता के लिए दर सीमा है, और हम उपयोगकर्ता आईडी का उपयोग करके इसे क्वेरी कर सकते हैं। <पी> चलिए इसके लिए चरण दर चरण कोड लिखते हैं।
एक बाल्टी बनाएं
<पी> शुरू करने से पहले, आइए देखें कि बाल्टी कैसे बनाई जाती है।Refill refill = Refill.intervally(10, Duration.ofMinutes(1));
Bandwidth limit = Bandwidth.classic(10, refill);
Bucket bucket = Bucket4j.builder()
.addLimit(limit)
.build();
- फिर से भरना – कितने समय बाद बाल्टी दोबारा भर जाएगी.
- बैंडविड्थ – बाल्टी में कितनी बैंडविड्थ है. मूलतः, प्रति रिफिल अवधि के लिए अनुरोध।
- बाल्टी - इन दो मापदंडों का उपयोग करके एक ऑब्जेक्ट कॉन्फ़िगर किया गया है। इसके अतिरिक्त, यह बाल्टी में कितने टोकन उपलब्ध हैं, इसका ट्रैक रखने के लिए एक टोकन काउंटर रखता है।
प्रॉक्सीमैनेजर का उपयोग करके बकेट बनाएं और कैश करें
<पी> हमने Redis पर बकेट संग्रहीत करने के उद्देश्य से प्रॉक्सी प्रबंधक बनाया। एक बार बकेट बन जाने के बाद, इसे Redis पर कैश करने की आवश्यकता होती है और इसे दोबारा बनाने की आवश्यकता नहीं होती है। <पी> ऐसा करने के लिए, हमBucket4j.builder() को प्रतिस्थापित करेंगे proxyManager.builder() के साथ . ProxyManager बकेट को कैशिंग करने और उन्हें दोबारा न बनाने का ध्यान रखेगा। <पी> ProxyManager का बिल्डर दो पैरामीटर लेता है - एक कुंजी जिसके विरुद्ध बकेट कैश किया जाएगा और एक कॉन्फ़िगरेशन ऑब्जेक्ट होगा इसका उपयोग यह बाल्टी बनाने के लिए करेगा। <पी> आइए देखें कि हम इसे कैसे लागू कर सकते हैं: @Service
public class RateLimiter {
//autowiring dependencies
public Bucket resolveBucket(String key) {
Supplier<BucketConfiguration> configSupplier = getConfigSupplierForUser(key);
// Does not always create a new bucket, but instead returns the existing one if it exists.
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) {
// gets the bucket for the user
Bucket bucket = rateLimiter.resolveBucket(id);
// tries to consume a token from the bucket
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() {
// calls the method 10 times for user 1
for (int i = 0; i < 10; i++) {
rateLimiter.getInfo(1));
rateLimiter.getInfo(2));
}
// verifies that the response is rate limited for user 1
assertEquals("Rate limit exceeded", rateLimiter.getInfo(1));
// verifies that the response is successful for user 2
assertEquals("Hello 2", rateLimiter.getInfo(2));
}
<पी> जब हम परीक्षण चलाते हैं, तो हम देखेंगे कि परीक्षण सफल हो गया है।