+3

Nâng cao độ ổn định của hệ thống Microservice với mẫu Bulkhead

Nguồn: https://www.tuanh.net/blog/oop/enhance-microservice-system-stability-with-bulkhead-pattern

Trong môi trường microservice, duy trì sự ổn định và khả năng phục hồi của hệ thống là một thách thức lớn. Mô hình Bulkhead là một trong những giải pháp hiệu quả giúp bảo vệ hệ thống khỏi lỗi bằng cách cách ly các dịch vụ. Bài viết này sẽ giải thích chi tiết về mô hình Bulkhead, cách hoạt động của nó và cách bạn có thể áp dụng nó để nâng cao tính bền vững của hệ thống microservice của mình. Hãy cùng khám phá cách bảo vệ hệ thống của bạn khỏi các rủi ro tiềm ẩn và tối ưu hóa hiệu suất.

1. Ví dụ thực tế

Cha tôi là một nhà quy hoạch đô thị tài năng. Một ngày, ông được giao nhiệm vụ tìm giải pháp cho một thành phố đang gặp vấn đề tắc nghẽn giao thông nghiêm trọng. Thay vì đại tu toàn bộ hệ thống giao thông - một nỗ lực tốn thời gian và tài nguyên - ông quyết định chọn một cách tiếp cận thông minh hơn. Công việc của ông tập trung vào việc lắp đặt đèn giao thông tại các giao lộ đông đúc nhất.

Ông đã khéo léo phân tích các khu vực có nguy cơ tắc nghẽn cao, chẳng hạn như các giao lộ lớn và đường gần khu vực thương mại. Sau đó, ông thiết kế và lắp đặt hệ thống đèn giao thông với thời gian đèn đỏ và đèn xanh được điều chỉnh khoa học, giúp điều phối lưu lượng giao thông hiệu quả hơn. Ông cũng lắp đặt cảm biến giao thông để thu thập dữ liệu thời gian thực, từ đó tối ưu hóa thời gian chờ đợi tại đèn giao thông để tránh tắc nghẽn. image.png

  • Các khu vực màu đỏ chỉ ra tình trạng tắc nghẽn giao thông nghiêm trọng
  • Các khu vực màu cam chỉ ra lưu lượng giao thông cao
  • Các khu vực màu xanh chỉ ra tình trạng tắc nghẽn giao thông thấp

Bố tôi không chỉ lắp đặt đèn giao thông mà còn thiết lập "làn đường ưu tiên" cho giao thông công cộng và xe cứu thương. Điều này giúp giảm tắc nghẽn giao thông cho các phương tiện quan trọng và đảm bảo rằng ngay cả khi một khu vực gặp vấn đề, các khu vực khác của thành phố vẫn hoạt động bình thường.

2. Bulkhead là gì

2.1 Khái niệm

Câu chuyện của bố tôi là một ví dụ sống động về mô hình Bulkhead trong hệ thống microservice. Tương tự như cách giao thông được phân chia và điều tiết để ngăn ngừa tắc nghẽn, mô hình Bulkhead chia một hệ thống thành các phần độc lập, ngăn chặn lỗi ở một phần lan rộng ra toàn bộ hệ thống. Kỹ năng và chiến lược mà bố tôi thể hiện trong việc quản lý giao thông phản ánh cách mô hình Bulkhead giúp duy trì sự ổn định và hiệu quả cho các microservice. Mẫu Bulkhead (vách ngăn) là một mẫu thiết kế ứng dụng nhằm mục đích chịu lỗi hiệu quả. Trong kiến trúc Bulkhead, các thành phần của ứng dụng được phân tách riêng biệt thành các nhóm (pool) riêng lẻ. Nhờ đó, nếu một thành phần gặp sự cố, các thành phần khác vẫn có thể tiếp tục hoạt động. (Nguồn: learn.microsoft.com)

Để mô tả chính xác điều này, tôi có hình minh họa sau. image.png

Ban đầu, hệ thống chỉ có một yêu cầu tạo và một yêu cầu lấy thông tin từ dịch vụ A. Để đáp ứng yêu cầu này, dịch vụ A cần gửi một yêu cầu tạo và một yêu cầu lấy thông tin tới dịch vụ B. Mọi thứ diễn ra suôn sẻ và dịch vụ B có thể xử lý các yêu cầu này một cách liền mạch. Khi số lượng yêu cầu tăng lên, đặc biệt là các yêu cầu tạo mất nhiều thời gian xử lý hơn, chúng giữ luồng worker lâu hơn, khiến các yêu cầu khác phải chờ đợi trong hàng đợi và tăng thời gian chờ đợi. Mặc dù dịch vụ B vẫn có thể xử lý các yêu cầu nhưng phải làm việc vất vả hơn. Các yêu cầu lấy thông tin, không yêu cầu nhiều thời gian xử lý, giờ đây mất nhiều thời gian hơn vì các yêu cầu tạo đã tiêu tốn tài nguyên.

Khi khối lượng lưu lượng truy cập tiếp tục tăng, đặc biệt là nhu cầu về các yêu cầu tạo, dịch vụ B không thể đáp ứng tất cả các yêu cầu từ dịch vụ A. Số lượng yêu cầu trong hàng đợi đang tăng lên. Các yêu cầu vừa được hoàn thành bởi dịch vụ B và trả về dịch vụ A phải được xử lý tiếp, khiến bộ thu gom rác (Garbage Collector - GC) không thể giải phóng bộ nhớ kịp thời. Đôi khi, điều này dẫn đến việc pod chết do thiếu bộ nhớ và CPU vì phải xử lý quá tải.

2.2 Cơ chế hoạt động của Bulkhead

image.png

Để đảm bảo hiệu suất và ổn định của hệ thống, tôi đã chọn áp dụng mẫu Bulkhead cho các dịch vụ tiêu tốn tài nguyên nhiều. Trong trường hợp này, tôi đã giới hạn số lượng yêu cầu đồng thời tới dịch vụ B chỉ cho phép một yêu cầu đồng thời tại một thời điểm. Các yêu cầu vượt quá giới hạn này sẽ được trả về cho client để xử lý backpressure, như sẽ được thảo luận chi tiết trong bài viết tiếp theo.

3. Bulkhead trong Spring

Để áp dụng Bulkhead trong Spring3, chúng ta thực hiện các bước sau: Thêm các dependency cần thiết.

<!--	BulkHead dependency	-->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
	</dependency>
	<dependency>
		<groupId>io.github.resilience4j</groupId>
		<artifactId>resilience4j-spring-boot3</artifactId>
		<version>2.2.0</version>
	</dependency>
<!--	BulkHead dependency	-->

Tôi tạo một controller đơn giản.

@RestController
@RequestMapping
public class BulkHeadController {

    @Autowired
    private BulkHeadService bulkHeadService;

    @GetMapping("/test")
    public ResponseEntity test() throws InterruptedException {
        return bulkHeadService.doSomeThing();
    }
}

Và tạo một hàm fallback khi xảy ra lỗi BulkHeadException.

@Service
public class BulkHeadService {

    @Bulkhead(name = "courseBulkheadApi", fallbackMethod = "fallbackMethod")
    public ResponseEntity doSomeThing() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(2000);
        System.out.println("Success in " + ( System.currentTimeMillis() - start ) );
        return ResponseEntity.ok().body("Success");
    }

    public ResponseEntity fallbackMethod(int id, BulkheadFullException ex) {
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                .body("Too many request - No further calls are accepted");
    }
}

courseBulkheadApi là cấu hình được định nghĩa trong application.properties

resilience4j.bulkhead.instances.courseBulkheadApi.maxConcurrentCalls=5
resilience4j.bulkhead.instances.courseBulkheadApi.maxWaitDuration=100ms

Và cuối cùng, đừng quên tạo một ControllerAdvice để bắt ngoại lệ và trả về ngoại lệ mong muốn.

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({ BulkheadFullException .class })
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public void bandwidthExceeded() {
    }
}

Để xác minh xem Bulkhead hoạt động đúng như mong đợi, tôi đã sử dụng JMeter để kiểm thử. Bạn có thể sử dụng các công cụ khác như locust hoặc AB Benchmarking Tool,... Ban đầu, tôi đặt giới hạn số lượng yêu cầu đồng thời cho API là 5, vì vậy tôi sẽ kiểm thử với 5 yêu cầu đồng thời. image.png Tôi đã nhận được 5 yêu cầu thành công. Tôi sẽ tiếp tục với 6 yêu cầu. image.png

4. Kết luận

Qua bài viết này, tôi đã trình bày khái niệm, tính chất. Trên thực tế, việc áp dụng Bulkhead hiệu quả phức tạp hơn nhiều. Tuy nhiên, tôi hy vọng bạn đã có được một cái nhìn tổng quan về nó. Trong bài viết tiếp theo, tôi sẽ giới thiệu về back-presser để xử lý các yêu cầu thất bại khi sử dụng Bulkhead. Tôi rất mong nhận được phản hồi từ tất cả các bạn. Trân trọng.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí