การทำ Rate Limiting และ Circuit Breaker (Resilience4J กับ WebFlux)

Sharing is caring!

การทำ Rate Limiting และ Circuit Breaker
(Resilience4J กับ WebFlux)

20-minute read • Updated 19 July 2025

สารบัญ

  1. เกริ่นนำ
  2. ทำความเข้าใจแนวคิด
  3. สแตกเทคโนโลยี & Blueprint
  4. เตรียมโปรเจค & Dependency
  5. ตั้งค่า Rate Limiter & Circuit Breaker (application.yml)
  6. ตัวอย่าง Service แบบ Reactive
  7. ผสานกับ Router Functions
  8. Monitoring & Dashboards
  9. Rate Limiter vs Circuit Breaker
  10. Best Practices
  11. Testing & Tuning
  12. สรุป

1. เกริ่นนำ

ระบบ Microservices ที่รับ ทราฟฟิกสูง ต้องเผชิญสองปัญหาหลักคือ การป้องกันการโจมตีแบบ DoS/Brute force และ การล่มพังแบบทอด Domino ของบริการ downstream เมื่อเกิด failure สะสม Rate Limiting และ Circuit Breaker เป็นคู่หูหลักในการเพิ่ม MTBF และลด MTTR ของระบบ Reactive API ที่เราเขียนด้วย Spring WebFlux

2. ทำความเข้าใจแนวคิด

Rate Limiter ปฏิเสธหรือยอมรับคำขอภายในกรอบเวลาที่กำหนด

2.1 Rate Limiting

กลไกที่ “เคาะประตู” ทุกครั้งก่อนเข้า API เพื่อเช็กว่าจำนวน token หรือ permit ใน bucket ยังเหลือหรือไม่ ถ้าเกิน ระบบตอบ HTTP 429 Too Many Requests
ประโยชน์คือ กันล้น (throttling) และ กันเจตนาร้าย เช่น Brute Force Login

2.2 Circuit Breaker

สถานะ Closed ➜ Open ➜ Half-Open ตามเงื่อนไข error rate และเวลาหน่วง

ทำหน้าที่เปรียบเสมือน “Fuse” ตัดวงจรเมื่อ downstream ล้มเหลวเกินเกณฑ์ ป้องกัน Thread Exhaustion และช่วยระบบฟื้นตัวได้เร็ว

3. สแตกเทคโนโลยี & Blueprint

  • Spring Boot 3.5.x + Spring WebFlux (Reactive stack)
  • Resilience4J 2.2.x (RateLimiter, CircuitBreaker, Retry, Bulkhead)
  • Micrometer + Prometheus + Grafana (Metrics & Dashboard)
  • ตัวอย่าง build ด้วย Maven แต่ปรับเป็น Gradle ได้เช่นกัน

4. เตรียมโปรเจค & Dependency

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
  <groupId>io.github.resilience4j</groupId>
  <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
  <groupId>io.github.resilience4j</groupId>
  <artifactId>resilience4j-reactor</artifactId>
</dependency>
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
  

5. ตั้งค่า Rate Limiter & Circuit Breaker (application.yml)

resilience4j:
  ratelimiter:
    instances:
      bookingService:
        limitForPeriod: 50
        limitRefreshPeriod: 1s
        timeoutDuration: 0
  circuitbreaker:
    instances:
      bookingService:
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 100
        failureRateThreshold: 50
        waitDurationInOpenState: 20s
        slowCallDurationThreshold: 2s
        slowCallRateThreshold: 50
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  

6. ตัวอย่าง Service แบบ Reactive

@Service
@RequiredArgsConstructor
public class BookingService {

  private final WebClient backendClient;
  private final RateLimiter bookRateLimiter =
      RateLimiter.ofDefaults("bookingService");
  private final CircuitBreaker bookCircuitBreaker =
      CircuitBreaker.ofDefaults("bookingService");

  public Mono<BookingDto> createBooking(Mono<RequestDto> req) {
    return req.flatMap(r ->
        backendClient.post()
            .uri("/backend/book")
            .bodyValue(r)
            .retrieve()
            .bodyToMono(BookingDto.class))
        .transformDeferred(RateLimiterOperator.of(bookRateLimiter))
        .transformDeferred(CircuitBreakerOperator.of(bookCircuitBreaker));
  }
}
  

7. ผสานกับ Router Functions

@Bean
RouterFunction<ServerResponse> routes(BookingHandler handler) {
  return RouterFunctions.route()
      .POST("/api/book", handler::create)
      .build();
}

@Component
@RequiredArgsConstructor
class BookingHandler {
  private final BookingService service;

  Mono<ServerResponse> create(ServerRequest r) {
    return service.createBooking(r.bodyToMono(RequestDto.class))
           .flatMap(b -> ServerResponse.ok().bodyValue(b))
           .onErrorResume(CallNotPermittedException.class,
               ex -> ServerResponse.status(503).build())
           .onErrorResume(RequestNotPermitted.class,
               ex -> ServerResponse.status(429).build());
  }
}
  

8. Monitoring & Dashboards

Resilience4J ส่งเมตริกผ่าน Micrometer: resilience4j_circuitbreaker_state, resilience4j_ratelimiter_available_permissions ฯลฯ
ต่อ Prometheus แล้วสร้าง Grafana Board ภาพรวมสุขภาพบริการได้ทันที

9. Rate Limiter vs Circuit Breaker

ทั้งสอง pattern ป้องกัน overload เหมือนกันแต่ใช้ “เวลาที่ต่างกัน”

  • Rate Limiter → ป้องกันก่อนเกิดปัญหา (Pre-Control)
  • Circuit Breaker → ตัดวงจรหลังพบ error (Post-Control)

10. Best Practices

  1. ใช้ different instance name ต่อ service เพื่อแยกสถิติแม่นยำ
  2. เก็บค่าใน Central Config แล้วรีโหลดผ่าน Spring Cloud Config
  3. ตั้ง timeoutDuration เป็น 0 สำหรับ reactive เพื่อไม่ block
  4. ใช้ Fallback แบบ cache/queue เพื่อรักษา UX

11. Testing & Tuning

@Test
void shouldOpenCircuitWhenFailure() {
  CircuitBreaker cb = CircuitBreaker.of("test",
      CircuitBreakerConfig.custom()
        .failureRateThreshold(50)
        .slidingWindowSize(2)
        .waitDurationInOpenState(Duration.ofSeconds(1))
        .build());

  Flux.error(new IllegalStateException())
      .transformDeferred(CircuitBreakerOperator.of(cb))
      .onErrorResume(e -> Mono.empty())
      .repeat(2)
      .blockLast();

  assertEquals(CircuitBreaker.State.OPEN, cb.getState());
}
  

12. สรุป

เพียงไม่กี่บรรทัด คุณก็เพิ่ม Rate Limiter และ Circuit Breaker ให้กับ API WebFlux ได้แล้ว Resilience4J ช่วยให้ปรับแต่งง่าย เห็นผลลัพธ์ผ่าน Metrics ทันที เมื่อเลือกค่า threshold เหมาะสม ระบบของคุณจะ เสถียร ปลอดภัย และ รองรับทราฟฟิกมหาศาล พร้อมลุย production!

พร้อมทดลอง? ลอง Fork โค้ดตัวอย่าง repo นี้ แล้วลุยเลย ✌️


© 2025 poolsawat.com • หากบทความนี้มีประโยชน์ ฝากกดแชร์ด้วยนะครับ

Leave a Reply

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *