Bảo vệ Microservices - Giải pháp chống lại hiệu ứng Domino

Giới thiệu

Trong các hệ thống có lưu lượng truy cập cao, microservices có thể bị quá tải và sụp đổ. Bài viết này sẽ hướng dẫn các bạn cách giải quyết vấn đề sụp đổ dây chuyền (cascading failure) trong kiến trúc microservices.

1. Giới hạn tần suất yêu cầu (Rate Limiting)

Khi một service nhận quá nhiều yêu cầu mà không thể xử lý kịp, nó sẽ bị sụp đổ. Giải pháp là giới hạn số lượng yêu cầu để luôn nằm trong khả năng xử lý của service.

2. Cách ly luồng xử lý (Thread Isolation)

Giả sử có hai service A và B. Nếu service B bị lỗi, người dùng request đến B sẽ bị chờ vô thời hạn, gây tích tụ luồng và có thể cạn kiệt tài nguyên server.

Giải pháp: Giới hạn số luồng được cấp phát cho mỗi service. Ví dụ: service giỏ hàng chiếm 20 luồng, các service khác chiếm 10 luồng. Khi service B lỗi, chỉ tốn 10 luồng thay vì toàn bộ.

3. Ngắt mạch dịch vụ (Circuit Breaker)

Cách ly luồng giúp tiết kiệm tài nguyên nhưng vẫn gây tổn thất luồng. Giải pháp tốt hơn là phát hiện service bất thường và thực hiện fail nhanh, trả về kết quả dự phòng (fallback) cho người dùng.

Cần thực hiện hai việc:

  • Viết logic fallback: Xử lý khi service gọi thất bại - có thể ném exception hoặc trả về dữ liệu mặc định.
  • Thống kê lỗi và ngắt mạch: Khi tỷ lệ lỗi của một service quá cao, từ chối gọi service đó và chuyển sang logic fallback.

4. Sử dụng Sentinel để triển khai

Sentinel là một middleware giúp triển khai các giải pháp trên một cách dễ dàng.

Cài đặt Sentinel

Bước 1: Cài đặt image Sentinel trên Linux

Bước 2: Khởi động container Sentinel

# Xóa container cũ nếu có
docker rm -f [ten-container]

# Khởi động Sentinel với port 8090
docker run -d -p 8090:8080 --name [ten-container] [ten-image]

Bước 3: Cấu hình trong project

<!--Sentinel dependency-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
  cloud:
    sentinel:
      transport:
        dashboard: 192.168.150.129:8090
      http-method-specify: true

4.1. Giới hạn tần suất

Cấu hình quy tắc giới hạn: chỉ cho phép 6 request mỗi giây. Sử dụng JMeter để gửi số lượng lớn request và quan sát hiệu ứng giới hạn.

4.2. Cách ly luồng

Giả sử có nghiệp vụ: khi xem giỏ hàng cần gọi đến service sản phẩm, nhưng service sản phẩm bị chậm. Điều này ảnh hưởng đến các thao tác khác.

Giải pháp: Cách ly luồng - service chỉ được cấp 5 luồng. Nếu mỗi luồng xử lý 2 request/giây, QPS tối đa là 10.

Cấu hình Tomcat:

server:
  port: 8082
  tomcat:
    threads:
      max: 50
    accept-count: 50
    max-connections: 100

Như vậy tối đa 50 luồng nhưng service giỏ hàng chỉ dùng 5 luồng, các thao tác khác không bị ảnh hưởng.

4.3. Tối ưu với Fallback

Khi một service lỗi và không phản hồi, luồng bị block gây trải nghiệm người dùng kém. Fallback giúp giải quyết vấn đề này.

Ví dụ: Service giỏ hàng gọi service sản phẩm qua OpenFeign mà service sản phẩm chậm, ta tạo fallback cho interface Feign đó.

Bước 1: Bật hỗ trợ Sentinel cho Feign

feign:
  okhttp:
    enabled: true
  sentinel:
    enabled: true

Bước 2: Tạo factory class cho Feign interface

@Slf4j
public class ProductClientFallbackFactory implements FallbackFactory<ProductClient> {

    @Override
    public ProductClient create(Throwable cause) {
        return new ProductClient() {
            @Override
            public List<ProductDTO> getProductsByIds(Collection<Long> ids) {
                log.error("Lỗi lấy thông tin sản phẩm!", cause);
                return Collections.emptyList();
            }

            @Override
            public void reduceInventory(List<OrderItemDTO> items) {
                log.error("Lỗi giảm tồn kho!", cause);
                throw new RuntimeException(cause);
            }
        };
    }
}

Bước 3: Đăng ký Bean trong configuration class

@Bean
public ProductClientFallbackFactory productClientFallbackFactory() {
    return new ProductClientFallbackFactory();
}

Bước 4: Chỉ định factory class trong Feign client

@FeignClient(value = "product-service", fallbackFactory = ProductClientFallbackFactory.class)

4.4. Ngắt mạch dịch vụ (Circuit Breaker)

Trạng thái của Circuit Breaker:

  • Closed: Cho phép tất cả request đi qua, bắt đầu thống kê tỷ lệ lỗi và request chậm. Nếu vượt ngưỡng, chuyển sang trạng thái Open.
  • Open: Request bị ngắt, trả về lỗi ngay lập tức, thực hiện logic fallback. Sau một thời gian, chuyển sang trạng thái Half-Open.
  • Half-Open: Cho phép 1 request đi qua để kiểm tra:
    • Nếu thành công: Chuyển về Closed
    • Nếu thất bại: Chuyển về Open

Quy tắc ngắt mạch: Khi độ trễ tối đa > 200ms, trong 1 giây có 5 request, và tỷ lệ request chậm > 50%, thực hiện ngắt mạch trong 10 giây.

Khi xảy ra ngắt mạch, request fail ngay lập tức, thực hiện fallback mà không chiếm dụng tài nguyên luồng.

Thẻ: Microservices circuit-breaker rate-limiting sentinel Java

Đăng vào ngày 18 tháng 5 lúc 17:38