ในบทช่วยสอนนี้ เราจะได้เรียนรู้วิธีใช้งานการจำกัดอัตราในบริการที่ปรับขนาด
เราจะใช้ไลบรารี Bucket4J เพื่อใช้งาน และเราจะใช้ Redis เป็นแคชแบบกระจาย
เหตุใดจึงต้องใช้การจำกัดอัตรา
มาเริ่มต้นด้วยพื้นฐานบางอย่างเพื่อให้แน่ใจว่าเราเข้าใจถึงความจำเป็นในการจำกัดอัตราและแนะนำเครื่องมือที่เราจะใช้ในบทช่วยสอนนี้
ปัญหาเรื่องอัตราไม่จำกัด
หาก API สาธารณะ เช่น Twitter API อนุญาตให้ผู้ใช้ส่งคำขอได้ไม่จำกัดจำนวนต่อชั่วโมง อาจนำไปสู่:
- การสิ้นเปลืองทรัพยากร
- คุณภาพการบริการลดลง
- การปฏิเสธการโจมตีบริการ
ซึ่งอาจส่งผลให้เกิดสถานการณ์ที่ บริการไม่พร้อมใช้งานหรือช้า . นอกจากนี้ยังอาจนำไปสู่ ค่าใช้จ่ายที่ไม่คาดคิด มากขึ้นอีกด้วย เกิดขึ้นจากบริการ
การจำกัดอัตราช่วยได้อย่างไร
ประการแรก การจำกัดอัตราสามารถป้องกันการโจมตีแบบปฏิเสธบริการได้ เมื่อใช้ร่วมกับกลไกการขจัดข้อมูลซ้ำซ้อนหรือคีย์ API การจำกัดอัตรายังสามารถช่วยป้องกันการโจมตีแบบปฏิเสธการให้บริการแบบกระจาย
ประการที่สอง ช่วยในการประมาณปริมาณการเข้าชม สิ่งนี้สำคัญมากสำหรับ API สาธารณะ นอกจากนี้ยังสามารถใช้ร่วมกับสคริปต์อัตโนมัติเพื่อตรวจสอบและปรับขนาดบริการ
และประการที่สาม คุณสามารถใช้เพื่อกำหนดราคาตามระดับได้ รูปแบบการกำหนดราคาประเภทนี้หมายความว่าผู้ใช้สามารถชำระเงินสำหรับคำขอในอัตราที่สูงกว่าได้ Twitter API คือตัวอย่างของสิ่งนี้
อัลกอริธึม Token Bucket
Token Bucket เป็นอัลกอริทึมที่คุณสามารถใช้เพื่อจำกัดอัตราได้ กล่าวโดยสรุป มันทำงานดังนี้:
- ที่เก็บข้อมูลถูกสร้างขึ้นด้วยความจุที่แน่นอน (จำนวนโทเค็น)
- เมื่อมีคำขอเข้ามา ที่เก็บข้อมูลจะถูกตรวจสอบ หากมีความจุเพียงพอก็อนุญาตให้ดำเนินการตามคำขอได้ มิฉะนั้นคำขอจะถูกปฏิเสธ
- เมื่อคำขอได้รับอนุญาต ความจุจะลดลง
- หลังจากผ่านไประยะหนึ่ง ความจุจะถูกเติมเต็ม
วิธีการใช้ Token Bucket ในระบบแบบกระจาย
หากต้องการใช้อัลกอริทึมที่เก็บข้อมูลโทเค็นในระบบแบบกระจาย เราจำเป็นต้องใช้ แคชแบบกระจาย .
แคชคือ ที่เก็บคีย์-ค่า เพื่อเก็บข้อมูลบัคเก็ต เราจะใช้แคช Redis เพื่อดำเนินการนี้
ภายใน Bucket4j ช่วยให้เราสามารถเสียบการใช้งาน Java JCache API ใดๆ ได้ ไคลเอ็นต์ Redisson ของ Redis คือการนำไปใช้ที่เราจะใช้
การดำเนินโครงการ
เราจะใช้เฟรมเวิร์ก Spring Boot เพื่อสร้างบริการของเรา
บริการของเราจะมีส่วนประกอบด้านล่าง:
- REST API แบบง่าย
- แคช Redis เชื่อมต่อกับบริการ – โดยใช้ไคลเอ็นต์ Redisson
- ไลบรารี Bucket4J ล้อมรอบ REST API
- เราจะเชื่อมต่อ 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 บนเครื่องของเรา
เราจำเป็นต้องดำเนินการสองขั้นตอน:
- สร้างการเชื่อมต่อกับเซิร์ฟเวอร์นี้จากแอปพลิเคชันของเรา
- ตั้งค่า 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 ภายในและสร้างแฮชที่เรียกว่า "แคช" บนอินสแตนซ์นั้น
- สร้างตัวจัดการพร็อกซีที่จะใช้ในการเข้าถึงแคช ไม่ว่าแอปพลิเคชันของเราจะพยายามแคชโดยใช้ 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 คนได้งานในตำแหน่งนักพัฒนา เริ่มต้น