কম্পিউটার টিউটোরিয়াল

Bucket4J এবং Redis দিয়ে স্কেলেবল রেট লিমিটিং ইমপ্লিমেন্ট করুন – ধাপে ধাপে গাইড

এই টিউটোরিয়ালে আমরা শিখব কীভাবে একটি স্কেলড সার্ভিসে রেট লিমিটিং ইমপ্লিমেন্ট করতে হয়। আমরা Bucket4J লাইব্রেরি ব্যবহার করব এটি ইমপ্লিমেন্ট করতে এবং একটি ডিস্ট্রিবিউটেড ক্যাশ হিসেবে Redis ব্যবহার করব।

রেট লিমিটিং কেন ব্যবহার করবেন?

কিছু বেসিক্স দিয়ে শুরু করি যাতে আমরা রেট লিমিটিং-এর প্রয়োজনীয়তা বুঝতে পারি এবং এই টিউটোরিয়ালে আমরা যে টুলগুলো ব্যবহার করব সেগুলো পরিচিত হয়।

অনলিমিটেড রেটের সমস্যা

যদি একটি পাবলিক API মনে Twitter API তার ইউজারদের ঘন্টায় অনলিমিটেড সংখ্যক রিকোয়েস্ট করার অনুমতি দিত, তবে এতে লিড করতে পারে:

  • রিসোর্স এক্সহাস্টশান
  • সার্ভিসের কোয়ালিটি হ্রাস
  • ডিনায়াল অফ সার্ভিস আটাক

এটি ফলস্বরূপ এমন একটি পরিস্থিতি হতে পারে যেখানে সার্ভিস অনুপলব্ধ বা ধীর হয়। এটি অপ্রত্যাশিত খরচও বাড়াতে পারে সার্ভিসের জন্য।

রেট লিমিটিং কীভাবে সাহায্য করে

প্রথমে, রেট-লিমিটিং ডিনায়াল অফ সার্ভিস আটাক প্রতিরোধ করতে পারে। যখন ডিডুপ্লিকেশন মেকানিজম বা API কীসের সাথে কাপলড হয়, রেট লিমিটিং ডিস্ট্রিবিউটেড ডিনায়াল অফ সার্ভিস আটাক প্রতিরোধ করতে সাহায্য করতে পারে।

দ্বিতীয়, এটি ট্রাফিক অনুমানে সাহায্য করে। এটি পাবলিক API-দের জন্য খুব গুরুত্বপূর্ণ। এটি অটোমেটেড স্ক্রিপ্টসের সাথে কাপলড হয়ে সার্ভিস মনিটর এবং স্কেল করতে সাহায্য করতে পারে।

তৃতীয়, আপনি এটি ব্যবহার করতে পারেন টিয়ার-বেসড প্রাইসিং ইমপ্লিমেন্ট করতে। এই প্রাইসিং মডেল মানে ইউজাররা বেশি রিকোয়েস্ট রেটের জন্য পেমেন্ট করতে পারে। Twitter API এর একটি উদাহরণ।

টোকেন বাকেট অ্যালগরিদম

টোকেন বাকেট একটি অ্যালগরিদম যা আপনি রেট লিমিটিং ইমপ্লিমেন্ট করতে ব্যবহার করতে পারেন। সংক্ষেপে, এটি নিচের মতো কাজ করে:

  1. একটি বাকেট তৈরি হয় একটি নির্দিষ্ট ক্যাপাসিটির সাথে (টোকেনের সংখ্যা)।
  2. যখন একটি রিকোয়েস্ট আসে, বাকেট চেক করা হয়। যদি পর্যাপ্ত ক্যাপাসিটি থাকে, রিকোয়েস্টকে এগিয়ে যাওয়ার অনুমতি দিয়ে দেয়া হয়। অন্যথায়, রিকোয়েস্ট ডিনি করা হয়।
  3. যখন একটি রিকোয়েস্ট অনুমোদিত হয়, ক্যাপাসিটি কমে যায়।
  4. একটি নির্দিষ্ট পরিমাণ সময়ের পর, ক্যাপাসিটি পুনরুদ্ধার হয়।

ডিস্ট্রিবিউটেড সিস্টেমে টোকেন বাকেট কীভাবে ইমপ্লিমেন্ট করবেন

ডিস্ট্রিবিউটেড সিস্টেমে টোকেন বাকেট অ্যালগরিদম ইমপ্লিমেন্ট করতে, আমাদের একটি ডিস্ট্রিবিউটেড ক্যাশ ব্যবহার করতে হবে।

ক্যাশটি একটি কি-ভ্যালু স্টোর বাকেট ইনফরমেশন সংরক্ষণ করতে। আমরা এই ইমপ্লিমেন্ট করতে একটি Redis ক্যাশ ব্যবহার করব।

অভ্যন্তরინভাবে, Bucket4J আমাদের Java JCache API-এর যেকোনো ইমপ্লিমেন্টেশন প্লাগ-ইন করতে দেয়। Redis-র Redisson ক্লায়েন্ট হলো ইমপ্লিমেন্টেশন যা আমরা ব্যবহার করব।

প্রজেক্ট ইমপ্লিমেন্টেশন

আমরা Spring Boot ফ্রেমওয়ার্ক ব্যবহার করব আমাদের সার্ভিস বানাতে।

আমাদের সার্ভিসে নিচের কম্পোনেন্টগুলো থাকবে:

  1. একটি সিম্পল REST API।
  2. সার্ভিসের সাথে কানেক্টেড একটি Redis ক্যাশ – Redisson ক্লায়েন্ট ব্যবহার করে।
  3. REST API-এর চারদিকে Bucket4J লাইব্রেরি র‍্যাপ করা।
  4. 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 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>

ক্যাশ কনফিগারেশন

প্রথমে, আমাদের Redis সার্ভার শুরু করতে হবে। মনে করুন আমাদের লোকাল মেশিনে পোর্ট ৬৩৭৯-এ একটি Redis সার্ভার চলছে।

আমাদের দুটি ধাপ করতে হবে:

  1. আমাদের অ্যাপ্লিকেশন থেকে এই সার্ভারের সাথে একটি কানেকশন তৈরি করুন।
  2. 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"));
    }
}

এটি কী করে?

  1. একটি কনফিগারেশন অবজেক্ট তৈরি করে যেটি আমরা কানেকশন তৈরি করতে ব্যবহার করতে পারি।
  2. কনফিগারেশন অবজেক্ট ব্যবহার করে একটি ক্যাশ ম্যানেজার তৈরি করে। এটি অভ্যন্তরিনভাবে Redis ইন্সট্যান্সের সাথে একটি কানেকশন তৈরি করে এবং এতে "cache" নামে একটি হ্যাশ তৈরি করে।
  3. একটি প্রক্সি ম্যানেজার তৈরি করে যেটি ক্যাশ অ্যাক্সেস করতে ব্যবহার হবে। আমাদের অ্যাপ্লিকেশন JCache API ব্যবহার করে যা ক্যাশ করার চেষ্টা করবে, সেটি Redis ইন্সট্যান্সে "cache" নামে হ্যাশে ক্যাশ হবে।

API বানান

একটি সিম্পল REST API তৈরি করি।

@RestController
public class RateLimitController {
    @GetMapping("/user/{id}")
    public String getInfo(@PathVariable("id") String id) {
        return "Hello " + id;
    }
}

যদি আমি API-কে URL https://localhost:8080/user/1 দিয়ে হিট করি, আমি রেসপন্স পাব 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 {
    // dependencies autowiring
    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 এবং লিমিট ১০ রিকোয়েস্ট প্রতি মিনিট। আমাদের আরেকটি ইউজার আছে আইডি 2 এবং লিমিট ২০ রিকোয়েস্ট প্রতি মিনিট।

আমরা উভয় ইউজারের জন্য ১১টি রিকোয়েস্ট হিট করব এবং যাচাই করব যে আইডি 1 가진 ইউজারের জন্য রিকোয়েস্ট ফেল করে কিন্তু আইডি 2 가진 ইউজারের জন্য সাকসেস করে।

@Test
public void testGetInfo() {
    // মেথডটি ১০ বার কল করুন ইউজার ১-এর জন্য
    for (int i = 0; i < 10; i++) {
        rateLimiter.getInfo(1));
        rateLimiter.getInfo(2));
    }
    // যাচাই করুন যে রেসপন্স ইউজার ১-এর জন্য রেট লিমিটেড
    assertEquals("Rate limit exceeded", rateLimiter.getInfo(1));
    // যাচাই করুন যে রেসপন্স ইউজার ২-এর জন্য সাকসেসফুল
    assertEquals("Hello 2", rateLimiter.getInfo(2));
}

যখন আমরা টেস্টটি চালাব, আমরা দেখব টেস্টটি পাস করেছে।

উপসংহার

এই টিউটোরিয়ালে, আমরা কভার করেছি কীভাবে Bucket4j এবং Redis ব্যবহার করে একটি রেট লিমিটার তৈরি করতে হয় একটি Spring Boot অ্যাপ্লিকেশনে। আমরা দেখেছি কীভাবে একটি Redisson ক্লায়েন্ট JCache-র সাথে সেট আপ করতে হয় এবং কীভাবে এটি ব্যবহার করে বাকেট ক্যাশ করতে হয়।

শেষে, আমরা একটি সিম্পল রেট লিমিটার ইমপ্লিমেন্ট করেছি যা নির্দিষ্ট ইউজারদের জন্য রিকোয়েস্ট রেট লিমিট করতে ব্যবহার করা যাবে।

এই টিউটোরিয়ালটি পড়ার জন্য ধন্যবাদ!

  1. অ্যান্ড্রয়েডে ফ্র্যাগমেন্ট থেকে এক্টিভিটি মেথড কল করা

  2. শর্টেস্ট জব ফার্স্ট (SJF) শিডিউলিং (নন-প্রি-এম্পটিভ) C++ প্রোগ্রাম

  3. জাভাFX-এ জ্যামিতিক ২D শেপ আঁকার পদ্ধতি

  4. অ্যান্ড্রয়েডে Spinner ভ্যালু পাওয়ার পদ্ধতি