Bảo vệ API của bạn bằng Netflix Hystrix

Nam Vu
9 min readSep 18, 2019

--

Trong thiết kế phần mềm có một khái niệm đó là “fail fast — don’t be slow”, có nghĩa là khi hệ thống đã fail hãy cố gắng làm nó fail một cách nhanh nhất để tránh những hệ quả gây ra khi hệ thống fail quá nhiều lần và fail quá lâu. Điều này đảm bảo việc tài nguyên hệ thống của bạn như I/O hay Transaction của bạn phải được giải phóng một cách nhanh nhất để tránh hệ thống gặp phải một thảm hoạ thác (cascading failures or catastrophic cascade). Và kiến trúc Circuit Breaker được thế kế ra để đảm bảo việc đó.

Trong một bài viết trước đây tui đã đề cập với kiến trúc “Circuit Breaker” và đã trình bầy khá chi tiết về kiến trúc, ý tưởng cũng như cách implement một CB đơn giản bằng Java, bạn đọc nào muốn tham khảo thì lội lại link nè để đọc nhé :D

Các kỹ sư của Netflix cũng phát triển một bộ thư viện của riêng họ có tên là Hystrix dựa trên kiến trúc của Circuit Breaker để bảo vệ các API của họ, ở bài viết này tui sẽ đề cập với việc cách sử dụng Hystrix và giải thích các tham số của nó.

Với dự án sử dụng Spring Boot việc sử dụng cực kỳ đơn giản, bạn chỉ cần thêm thư viện Spring Cloud Hystrix là một wrapper của Netflix Hystrix

Maven:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>

Gradle:

compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-hystrix', version: '1.4.7.RELEASE'

Thời điểm viết bài này version mới nhất là ‘1.4.7.RELEASE’ bạn có thể tìm version mới nhất của Spring Cloud Hystrix bạn có thể tìm ở link này.

Bây giờ việc tiếp theo là thêm một annotation @EnableCircuitBreaker vào Spring Boot Application main class, @EnableCircuitBreaker dùng để bật tính năng Circuit Breaker của Hystrix ở Sprint Boot Application

Well giờ nếu ta muốn bảo vệ API method nào ta chỉ cần sử dụng annotation @HystrixCommand trước mỗi method ví dụ ta có một API như sau:

Giờ tôi sẽ giải thích các tham số trên annotation @HystrixCommand vừa sử dụng bên trên.

- fallbackMethod: Đây là chính là method khi Circuit Breaker đang ở trạng thái OPEN, khi đó thay bằng hàm 'hello' được gọi thì hàm 'fallback_hello' sẽ được gọi. Nó có tác dụng để thay thế main method trong trường hợp bị fail, ví dụ trong trường hợp lấy dữ liệu từ DB bị fail ta có thể lấy từ Cached hoặc ngược lại. Nó có tác dụng vô cùng lớn nếu ta biết sử dụng một cách khôn khéo tùy vào business logic của bài toán.

- ignoreExceptions: Trong bài viết về Circuit Breaker tui có đề cập về một vài chú ý khi sử dụng Circuit Breaker như việc sử lý Exception một cách hợp lý, không phải exception nào ta cũng kích hoạt CB như exception văng ra khi có lỗi về validation hay những lỗi bị văng ra vì lỗi logic của hệ thống, tốt nhất thì ta chỉ nên kích hoạt CB khi có một vài exception cụ thể nào đó , ví dụ như timeout (408) hay service unavailable (503) exception. Khi ta sử dụng ignoreExceptions thì Hystrix sẽ bỏ qua những exception này.

- commandProperties:
+ Đây là nơi khai báo những Hystrix's configuration dưới dạng key - value, như ví dụ bên trên tui sử dụng tham số execution.isolation.thread.timeoutInMilliseconds thông qua annotation @HystrixProperty với giá trị là 1000, nó có ý nghĩa là Circuit Breaker sẽ được khích hoạt khi method sử lý quá thời gian timeout tui config là 1000 milliseconds (1 giây).

+ Ở đây ta khai báo từng config cho từng method bằng cách sử dụng commandProperties nhưng ta cũng có thể khai báo chung các Hystrix config cho toàn bộ Project bằng cách khai báo qua Spring Application Properties (application.properties/application.yml)

+ Ví dụ với cách dùng file properties (application.properties)

hystrix.execution.isolation.thread.timeoutInMilliseconds=1000 

+ Với các dùng file yml (application.yml)

hystrix:
execution.isolation.thread.timeoutInMilliseconds: 1000

Với dự án sử dụng Spring Boot thì việc áp dụng Hystrix tương đối đơn giản, nhưng với dựa án không sử dụng Spring Boot thì sao?

Đầu tiên ta phải import thư viện hystrix-corerxjava-core vào dự án

Maven:

<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.18</version>
</dependency>

<dependency>
<groupId>com.netflix.rxjava</groupId>
<artifactId>rxjava-core</artifactId>
<version>0.20.7</version>
</dependency>

Gradle:

compile 'com.netflix.hystrix:hystrix-core:1.5.18'
compile 'com.netflix.rxjava:rxjava-core:0.20.7'

Latest version của Hystrix bạn có thể tìm ở đâyRXJava bạn có thể tìm ở đây

Để sử dụng Hystrix bạn phải tạo một wrapper class và extends tới lớp HystrixCommand và wrapper call tới run() method. Ví dụ

Và nếu muốn thực thi thì ta gọi tới execute() method của CommandHelloWorld class.

new CommandHelloWorld("Nam").execute()

Nếu muốn có phương thức fallback thì ta override lại getFallback() method, ví dụ

Lớp command trên kia chỉ tập trung luồng sử lý trong hàm run() nếu ta muốn gọi từ một lớp Service bên ngoài thì chỉ đơn giản là khai báo service đó trong Hystrix Command class, ví dụ.

Bạn có thể để ý tới HystrixCommand bên trên tôi có truyền vào constructor một biến Setter config , Setter này giúp chúng ta dễ dàng cấu hình nhưng thông số cho Hystrix như setup các Command Properties hay setup group key ...
Ví dụ bên dưới tui viết một Unit test để setup cho HystrixCommand với group key là "RemoteServiceGroupTest" và set command property "execution.isolation.thread.timeoutInMilliseconds" với giá trị là "10000" (timeout 10 giây):

Hoặc với Thread Pool Properties với maxQueueSize là 10, coreSize là 3 và queueSizeRejectionThreshold là 10 như sau:

Đó cách implement với Hystrix cũng tương đối đơn giản, nhưng đòi hỏi bạn phải refactor code lại đôi chút thay vì gọi trực tiếp Service thì giờ bạn phải gọi qua các executor của HystrixCommand, nhưng nếu project của bạn đang sử dụng Spring Boot thì việc sử dụng Spring Cloud Hystrix thì ứng dụng hầu như không phải thay đổi gì về code, đơn giản chỉ cần sử dụng @anotation khỏe re :D

Tìm hiểu về các HystrixCommand Properties:

Hystrix cung cấp cho chúng ta 5 Command Properties là Execution, Fallback, Circuit Breaker, Metrics Request và Context, dưới đây tui xin giải thích về 3 command properties được sử dụng nhiều nhất là:

Execution: Những properties này sẽ control việc HystrixComand chạy ra sao.

- execution.isolation.strategy: Tham số này để quyết định việc HystrixComand này sẽ được chạy dưới "chiến thuật" (strategy) nào. Property này chấp nhận hai giá trị là THREAD và SEMAPHORE.
+ THREAD: HystrixCommand sẽ được thực thi với từng thread riêng biệt và các concurrent requests sẽ được giới hạn bởi số lượng của thread pool, về cơ bản các thread sẽ được quản lý bởi các cấu trúc dữ liệu kiểu thread pool và đây là option mặc định cũng như được recommended bởi Hystrix.
+ SEMAPHORE: HystrixCommand sẽ được thực thi với với từng lời gọi thread và các concurrent requests sẽ được giới hạn bởi số lượng semaphore (semaphore count) , về cơ bản các thread sẽ được quản lý với cấu trúc dữ liệu kiểu semaphore. Theo Netflix thì ta chi nên sử dụng option này khi lượng request rất lớn (hàm trăm request / giây) vì do việc tạo ra quá nhiều thread sẽ rất tốn tài nguyên, và chỉ áp dụng khi các cuộc gọi không thông qua network (internal thread processing).

- execution.isolation.thread.timeoutInMilliseconds: Tham số này để giới hạn timeout của lời gọi, tình bằng milliseconds, có nghĩa là khi quá thời gian này lời gọi chưa có kết quả trả về thì Hystrix sẽ tính rằng lời gọi trên bị fail và thực thi fallback logic. Giá trị default là 1000 (1 giây).

- execution.timeout.enabled: Tham số này để tắt hoặc bật tính năng của tham số execution.isolation.thread.timeoutInMillisecond. Giá trị mặc định là true (luôn bật), nhận hai giá trị là "true" và "false"

- execution.isolation.thread.interruptOnTimeout: Tham số này để khi xảy ra timeout HystrixCommand sẽ lập tực bị ngắt (interrupt). Giá trị mặc định là true, nhận hai giá trị là “true” và “false”

- execution.isolation.thread.interruptOnCancel: Tham số này để khi xảy ra timeout HystrixCommand bị hủy bỏ (Cancel). Giá trị mặc định là false, nhận hai giá trị là “true” và “false”

- execution.isolation.semaphore.maxConcurrentRequests: Tham số này được sử dụng khi bạn sử dụng tùy chọn SEMAPHORE trong tham số " execution.isolation.strategy" nó quyết định tổng số request tối đa đồng thời được gọi đến từ HystrixCommand, nếu số lượng concurrent request lớn hơn tham số này thì request sẽ bị từ chối (reject). Thực ra nó chính semaphore count trong CTDL semaphore. Giá trị mặc định của nó là 10.

Fallback: Những properties này sẽ quyết định việc HystrixCommand fallback ra sao.

- fallback.isolation.semaphore.maxConcurrentRequests: Tham số này quyết định số request fall back tối đa được sử lý đồng thời, nếu lượng concurrent request vượt quá giới hạn exception của method mà HystrixCommand đang "bảo kê" à nhầm bảo vệ sẽ được throw ra mà không có action fall back nào được thực hiện. Giá trị mặc định của nó là 10.

- fallback.enabled: Tham số này để bật hoặc tắt HystrixCommand fallback. Giá trị mặc định là true (luôn bật)

Circuit Breaker: Những properties này sẽ điều khiển Hystrix Circuit Breaker cho cả hệ thống.

  • circuitBreaker.enabled: Dùng để bật tắt Circuit Breaker, giá trị mặc định là true (luôn bật), chấp nhận hai giá trị "true" - "false".
    - circuitBreaker.requestVolumeThreshold: Tham số này để định nghĩa số lượng tối thiểu request bị fail liên tục để kích hoạt ciruit breaker. Giá trị mặc định là 20, có nghĩa là nếu có 20 request bị fail liên tục thì ciruit breaker sẽ OPEN.
    - circuitBreaker.sleepWindowInMilliseconds: Tham số này quyết định khoảng thời gian khi CB đang OPEN sau khoảng thời gian này sẽ chuyển sang HALF-OPEN. Giá trị mặc định là 5000 (5 giây)
    - circuitBreaker.errorThresholdPercentage: Tham số này để xác định số phần trăm tỷ lệ request bị lỗi để quyết định có OPEN circuit breaker hay không. Giá trị mặc định là 50, có nghĩa là khoảng 50% số lượng request bị fail thì CB sẽ OPEN ngay lập tức.
    - circuitBreaker.forceOpen: Với tham số này nếu giá trị là true thì CB ngay lập tức sẽ OPEN, tất cả các request sẽ bị từ chối. Tham số mặc định là false và chập nhận hai giá trị truefalse
    - circuitBreaker.forceClosed: Với tham số này nếu giá trị là true thì CB ngay lập tức sẽ CLOSE, tất cả các request sẽ được cho phép truy cập. Tham số mặc định là false và chập nhận hai giá trị truefalse.

Tìm hiểu về các Thread Pool Properties:

Ở phần Hystrix Command Properties ta có vài cấu hình về Thread Pool ở phần "execution.isolation.thread.*" ngoài ra ta cũng có thể sử dụng các Thread Pool Properties để cấu hình nâng cao các Thread Pool chung cho toàn bộ các HystrixCommand như sau:

hystrix.threadpool.default.coreSize: Cấu hình số lượng core thread-pool. Giá trị default là 10.

hystrix.threadpool.default.maximumSize: Cấu hình số lượng tối đa của thread-pool. Giá trị default là 10.

hystrix.threadpool.default.maxQueueSize: Cấu hình này quyết định việc Hystrix sẽ sử dụng CTDL nào để quán lý Thread Pool, Hystrix hiện đang sử dụng LinkedBlockingQueue làm mặc định nếu giá trị được set bằng "-1" và nếu giá khác thì Hystrix Thread Pool sẽ sử dụng SynchronousQueue. Chú ý rằngviệc thay đổi này yêu cầu phải restart lại hệ thống.

hystrix.threadpool.default.allowMaximumSizeToDivergeFromCoreSize: Trước phiên bản Hystrix 1.5.9 thì số coreSize mà maximumSize luôn luôn là một giá trị bằng nhau, sau phiên bản 1.5.9 Hystrix bắt đầu hỗ trợ việc hai giá trị đó có thể bằng nhau, lớn hơn hoặc nhỏ hơn nhau bằng việc set value lại allowMaximumSizeToDivergeFromCoreSize bằng "true" hoặc "false" . Nếu coreSize < maximumSize thì Thread Pool sẽ sử dụng số lượng maximumSize để tạo số lượng Pool tương ứng trong Thread Pool, nhưng vẫn sẽ trả lại (release) các thread nhàn rỗi cho hệ thống dựa trên tham số keepAliveTimeMinutes được config bên dưới.
Giá trị mặc định là false tức là giá trị coreSize mà maxQueueSize có thể khác nhau.

hystrix.threadpool.default.keepAliveTimeMinutes: Khi coreSize < maximumSize thì giá trị này sẽ control việc 1 thread sẽ được giải phóng sau 1 khoảng thời gian không được sử dụng đến (được tính bằng phút), giá trị default là 1 (1 phút).

Hết!

P/S: Hiện tại Hystrix đã ngừng nhận contribution hay phát triển thêm, do đó bạn có thể cân nhắc một thư viện khác là Resilience4j giải quyết bài toán về Circuit Breaker nhé.

--

--

Nam Vu
Nam Vu

No responses yet