Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

ใช้การจำกัดอัตราที่ปรับขนาดได้ด้วย Bucket4J และ Redis – คำแนะนำทีละขั้นตอน

ใช้การจำกัดอัตราที่ปรับขนาดได้ด้วย Bucket4J และ Redis – คำแนะนำทีละขั้นตอน

ในบทช่วยสอนนี้ เราจะได้เรียนรู้วิธีใช้งานการจำกัดอัตราในบริการที่ปรับขนาด
เราจะใช้ไลบรารี Bucket4J เพื่อใช้งาน และเราจะใช้ Redis เป็นแคชแบบกระจาย

เหตุใดจึงต้องใช้การจำกัดอัตรา

มาเริ่มต้นด้วยพื้นฐานบางอย่างเพื่อให้แน่ใจว่าเราเข้าใจถึงความจำเป็นในการจำกัดอัตราและแนะนำเครื่องมือที่เราจะใช้ในบทช่วยสอนนี้

ปัญหาเรื่องอัตราไม่จำกัด

หาก API สาธารณะ เช่น Twitter API อนุญาตให้ผู้ใช้ส่งคำขอได้ไม่จำกัดจำนวนต่อชั่วโมง อาจนำไปสู่:

  • การสิ้นเปลืองทรัพยากร
  • คุณภาพการบริการลดลง
  • การปฏิเสธการโจมตีบริการ

ซึ่งอาจส่งผลให้เกิดสถานการณ์ที่ บริการไม่พร้อมใช้งานหรือช้า . นอกจากนี้ยังอาจนำไปสู่ ค่าใช้จ่ายที่ไม่คาดคิด มากขึ้นอีกด้วย เกิดขึ้นจากบริการ

การจำกัดอัตราช่วยได้อย่างไร

ประการแรก การจำกัดอัตราสามารถป้องกันการโจมตีแบบปฏิเสธบริการได้ เมื่อใช้ร่วมกับกลไกการขจัดข้อมูลซ้ำซ้อนหรือคีย์ API การจำกัดอัตรายังสามารถช่วยป้องกันการโจมตีแบบปฏิเสธการให้บริการแบบกระจาย

ประการที่สอง ช่วยในการประมาณปริมาณการเข้าชม สิ่งนี้สำคัญมากสำหรับ API สาธารณะ นอกจากนี้ยังสามารถใช้ร่วมกับสคริปต์อัตโนมัติเพื่อตรวจสอบและปรับขนาดบริการ

และประการที่สาม คุณสามารถใช้เพื่อกำหนดราคาตามระดับได้ รูปแบบการกำหนดราคาประเภทนี้หมายความว่าผู้ใช้สามารถชำระเงินสำหรับคำขอในอัตราที่สูงกว่าได้ Twitter API คือตัวอย่างของสิ่งนี้

อัลกอริธึม Token Bucket

Token Bucket เป็นอัลกอริทึมที่คุณสามารถใช้เพื่อจำกัดอัตราได้ กล่าวโดยสรุป มันทำงานดังนี้:

  1. ที่เก็บข้อมูลถูกสร้างขึ้นด้วยความจุที่แน่นอน (จำนวนโทเค็น)
  2. เมื่อมีคำขอเข้ามา ที่เก็บข้อมูลจะถูกตรวจสอบ หากมีความจุเพียงพอก็อนุญาตให้ดำเนินการตามคำขอได้ มิฉะนั้นคำขอจะถูกปฏิเสธ
  3. เมื่อคำขอได้รับอนุญาต ความจุจะลดลง
  4. หลังจากผ่านไประยะหนึ่ง ความจุจะถูกเติมเต็ม

วิธีการใช้ Token Bucket ในระบบแบบกระจาย

หากต้องการใช้อัลกอริทึมที่เก็บข้อมูลโทเค็นในระบบแบบกระจาย เราจำเป็นต้องใช้ แคชแบบกระจาย .

แคชคือ ที่เก็บคีย์-ค่า เพื่อเก็บข้อมูลบัคเก็ต เราจะใช้แคช Redis เพื่อดำเนินการนี้

ภายใน Bucket4j ช่วยให้เราสามารถเสียบการใช้งาน Java JCache API ใดๆ ได้ ไคลเอ็นต์ Redisson ของ Redis คือการนำไปใช้ที่เราจะใช้

การดำเนินโครงการ

เราจะใช้เฟรมเวิร์ก Spring Boot เพื่อสร้างบริการของเรา

บริการของเราจะมีส่วนประกอบด้านล่าง:

  1. REST API แบบง่าย
  2. แคช Redis เชื่อมต่อกับบริการ – โดยใช้ไคลเอ็นต์ Redisson
  3. ไลบรารี Bucket4J ล้อมรอบ REST API
  4. เราจะเชื่อมต่อ Bucket4J กับอินเทอร์เฟซ JCache ซึ่งจะใช้ไคลเอนต์ Redisson เป็นการใช้งานในเบื้องหลัง

ขั้นแรก เราจะเรียนรู้การจำกัดอัตรา API สำหรับคำขอทั้งหมด จากนั้น เราจะเรียนรู้การใช้กลไกการจำกัดอัตราที่ซับซ้อนยิ่งขึ้นต่อผู้ใช้หรือต่อระดับราคา

เริ่มต้นด้วยการตั้งค่าโครงการ

ติดตั้งการอ้างอิง

มาเพิ่มการอ้างอิงด้านล่างให้กับ 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>

การกำหนดค่าแคช

ขั้นแรก เราต้องเริ่มต้นเซิร์ฟเวอร์ Redis ของเรา สมมติว่าเรามีเซิร์ฟเวอร์ Redis ที่ทำงานบนพอร์ต 6379 บนเครื่องของเรา

เราจำเป็นต้องดำเนินการสองขั้นตอน:

  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 ภายในและสร้างแฮชที่เรียกว่า "แคช" บนอินสแตนซ์นั้น
  3. สร้างตัวจัดการพร็อกซีที่จะใช้ในการเข้าถึงแคช ไม่ว่าแอปพลิเคชันของเราจะพยายามแคชโดยใช้ JCache API ก็ตาม แอปพลิเคชันนั้นจะถูกแคชบนอินสแตนซ์ Redis ภายในแฮชที่ชื่อ "แคช"

สร้าง API

มาสร้าง REST API แบบง่ายๆ กันดีกว่า

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

หากฉันเข้าถึง API ด้วย URL 00 ฉันจะได้รับคำตอบ 10 .

การกำหนดค่า Bucket4J

หากต้องการใช้การจำกัดอัตรา เราจำเป็นต้องกำหนดค่า Bucket4J โชคดีที่เราไม่จำเป็นต้องเขียนโค้ดสำเร็จรูปใดๆ เนื่องจากไลบรารีเริ่มต้น

นอกจากนี้ยังตรวจจับ ProxyManager bean โดยอัตโนมัติ เราสร้างขึ้นในขั้นตอนก่อนหน้าและใช้เพื่อแคชที่เก็บข้อมูล

สิ่งที่เราต้องทำคือกำหนดค่าไลบรารีนี้โดยใช้ API ที่เราสร้างขึ้น
มีหลายวิธีในการทำเช่นนี้อีกครั้ง

เราสามารถไปกำหนดค่าตามคุณสมบัติซึ่งกำหนดไว้ในไลบรารีเริ่มต้น
นี่เป็นวิธีที่สะดวกที่สุดสำหรับกรณีง่ายๆ เช่น การจำกัดอัตราสำหรับผู้ใช้ทั้งหมดหรือผู้ใช้ทั่วไปทั้งหมด

อย่างไรก็ตาม หากเราต้องการใช้สิ่งที่ซับซ้อนมากขึ้น เช่น การจำกัดอัตราสำหรับผู้ใช้แต่ละคน จะเป็นการดีกว่าถ้าเขียนโค้ดที่กำหนดเองให้

เราจะดำเนินการจำกัดอัตราต่อผู้ใช้ สมมติว่าเรามีขีดจำกัดอัตราสำหรับผู้ใช้แต่ละรายที่จัดเก็บไว้ในฐานข้อมูล และเราสามารถสืบค้นได้โดยใช้รหัสผู้ใช้

มาเขียนโค้ดของมันทีละขั้นตอนกัน

สร้างที่เก็บข้อมูล

ก่อนที่เราจะเริ่มต้น มาดูวิธีการสร้างที่เก็บข้อมูลกันก่อน

Refill refill = Refill.intervally(10, Duration.ofMinutes(1));
Bandwidth limit = Bandwidth.classic(10, refill);
Bucket bucket = Bucket4j.builder()
 .addLimit(limit)
 .build();
  • เติมเงิน – หลังจากระยะเวลาเท่าใดจึงจะเติมที่ฝากข้อมูล
  • แบนด์วิธ – บัคเก็ตมีแบนด์วิดท์เท่าใด โดยพื้นฐานแล้ว คำขอต่อระยะเวลาการเติม
  • ที่เก็บข้อมูล – วัตถุที่กำหนดค่าโดยใช้พารามิเตอร์ทั้งสองนี้ นอกจากนี้ ยังรักษาตัวนับโทเค็นเพื่อติดตามจำนวนโทเค็นที่มีอยู่ในที่เก็บข้อมูล

การใช้สิ่งนี้เป็นองค์ประกอบหลัก เราจะมาเปลี่ยนแปลงบางสิ่งเพื่อให้เหมาะสมกับกรณีการใช้งานของเรา

สร้างและแคชบัคเก็ตโดยใช้ ProxyManager

เราสร้างตัวจัดการพร็อกซีเพื่อวัตถุประสงค์ในการจัดเก็บบัคเก็ตบน Redis เมื่อสร้างที่เก็บข้อมูลแล้ว จะต้องแคชไว้บน Redis และไม่จำเป็นต้องสร้างอีกครั้ง

เพื่อให้สิ่งนี้เกิดขึ้น เราจะแทนที่ 27 ด้วย 34 . 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());
 }
}

เราได้สร้างวิธีการส่งคืนที่ฝากข้อมูลสำหรับคีย์ที่ให้ไว้ ในขั้นตอนถัดไปเราจะดูวิธีใช้งาน

วิธีใช้โทเค็นและตั้งค่าการจำกัดอัตรา

เมื่อมีคำขอเข้ามา เราจะพยายามใช้โทเค็นจากที่เก็บข้อมูลที่เกี่ยวข้อง
เราจะใช้ 40 วิธีการของที่ฝากข้อมูลเพื่อทำสิ่งนี้

@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";
 }
}

54 วิธีการส่งคืน 66 หากโทเค็นถูกใช้สำเร็จหรือ 79 หากโทเค็นไม่ถูกใช้

วิธีทดสอบบริการของเรา

เราสามารถทดสอบสิ่งนี้ได้โดยใช้เทคนิคการทดสอบอัตโนมัติ ตัวอย่างเช่น เราสามารถใช้ JUnit มาเขียนกรณีทดสอบที่เรียก 86 วิธีการหลายครั้งและยืนยันว่าการตอบสนองนั้นถูกต้อง

สมมติว่าเรามีผู้ใช้ที่มีรหัส 99 และขีดจำกัด 106 คำขอต่อนาที สมมติว่าเรามีผู้ใช้ที่มี id 117 ด้วย และจำกัดไว้ที่ 122 คำขอต่อนาที

เราจะตอบรับคำขอ 11 รายการสำหรับผู้ใช้ทั้งสองคน และตรวจสอบว่าคำขอล้มเหลวสำหรับผู้ใช้ที่มีรหัส 130 แต่สำเร็จสำหรับผู้ใช้ที่มี id 149 .

@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));
}

เมื่อเรารันการทดสอบ เราจะเห็นว่าการทดสอบผ่านไป

บทสรุป

ในบทช่วยสอนนี้ เราได้กล่าวถึงวิธีสร้างตัวจำกัดอัตราโดยใช้ Bucket4j และ Redis ในแอปพลิเคชัน Spring Boot นอกจากนี้เรายังดูวิธีตั้งค่าไคลเอ็นต์ Redisson ด้วย JCache และวิธีใช้เพื่อแคชบัคเก็ต

ในตอนท้าย เราได้ใช้ตัวจำกัดอัตราแบบง่ายๆ ซึ่งสามารถใช้เพื่อขอจำกัดอัตราสำหรับผู้ใช้บางรายได้

หวังว่าคุณจะสนุกกับการกวดวิชานี้ ขอบคุณสำหรับการอ่าน!

เรียนรู้การเขียนโค้ดฟรี หลักสูตรโอเพ่นซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานในตำแหน่งนักพัฒนา เริ่มต้น