Trong các hệ thống phân tán hiện đại, khi việc gọi API bên ngoài hoặc liên kết với các hệ thống khác trở nên thường xuyên, tầm quan trọng của mô hình Circuit Breaker nhằm ngăn chặn sự lan truyền lỗi (cascading failure) ngày càng trở nên cấp thiết. Đặc biệt trong môi trường vận hành nhiều instance (server) cùng lúc, một cấu trúc có khả năng chia sẻ trạng thái phân tán là điều bắt buộc.
Bài viết này tập trung vào việc giới thiệu cách triển khai Circuit Breaker dựa trên Redis và cách sử dụng an toàn trong môi trường Kotlin + Coroutine.

Circuit Breaker là gì?

Circuit Breaker (Bộ ngắt mạch) là một mô hình bảo vệ giúp ngăn chặn lãng phí tài nguyên và lan truyền lỗi bằng cách chặn các yêu cầu trong một khoảng thời gian nhất định khi hệ thống xảy ra lỗi lặp đi lặp lại.
Chuyển đổi trạng thái:
  • CLOSED: Trạng thái bình thường, cho phép mọi yêu cầu.
  • OPEN: Trạng thái ngắt mạch, từ chối mọi yêu cầu (Trạng thái hết hạn sau một khoảng TTL).
  • HALF_OPEN: Cho phép một phần yêu cầu → Đang trong quá trình thử nghiệm phục hồi.
CLOSED ──(Lỗi liên tục)──▶ OPEN ──(Hết TTL)──▶ HALF_OPEN ──(Thành công)──▶ CLOSED
                                                 └────(Thất bại)────┘

Triển khai dựa trên Redis

Cấu trúc Redis | Key Redis | Mô tả | | :--- | :--- | | cb:state (RMapCache) | Lưu trữ trạng thái theo từng key (CLOSED, OPEN, HALF_OPEN). | | cb:{key}:failures | Sử dụng để đếm số lần thất bại (RAtomicLong). | | cb:{key}:success | Sử dụng để đếm số lần thành công trong trạng thái HALF_OPEN (RAtomicLong). |

Thuật toán cơ bản

  1. Nếu số lần thất bại vượt ngưỡng, chuyển sang trạng thái OPEN và thiết lập TTL.
  2. Khi TTL hết hạn và key trạng thái biến mất, hệ thống sẽ coi là đang ở trạng thái HALF_OPEN.
  3. Cho phép một số yêu cầu trong trạng thái HALF_OPEN, nếu tích lũy đủ số lần thành công → Quay lại trạng thái CLOSED.
  4. Nếu thất bại lần nữa trong trạng thái HALF_OPEN → Chuyển ngay lập tức về OPEN.

Triển khai tích hợp Kotlin Coroutine + Redisson

Redisson cung cấp các API bất đồng bộ (.tryLockAsync(), .incrementAndGetAsync()), khi kết hợp với await() trong Coroutine, chúng ta có thể xử lý bất đồng bộ một cách an toàn.
suspend fun <T> execute(key: String, supplier: suspend () -> T): T {
    if (!isCallPermitted(key)) throw CircuitOpenException("Mạch đang ở trạng thái OPEN")

    return try {
        val result = supplier()
        onSuccess(key)
        result
    } catch (ex: Exception) {
        if (classifier.shouldTrip(ex)) {
            onFailure(key)
        }
        throw ex
    }
}
Sử dụng RMapCache để quản lý trạng thái và RAtomicLong để đếm số lần thất bại/thành công.

Chiến lược phân loại ngoại lệ: Ngăn chặn xâm nhập Domain

Thay vì gắn các interface chính sách kỹ thuật (ShouldTripCircuitBreaker) vào các ngoại lệ của Domain, chúng ta tách biệt trách nhiệm phân loại ngoại lệ ra bên ngoài.
interface ExceptionClassifier {
    fun shouldTrip(ex: Throwable): Boolean
}

class DefaultExceptionClassifier : ExceptionClassifier {
    override fun shouldTrip(ex: Throwable): Boolean = when (ex) {
        is ClientErrorException -> false // Lỗi phía client: Không ngắt mạch
        is TimeoutException, is UpstreamServerException -> true // Lỗi hệ thống: Ngắt mạch
        else -> true
    }
}
Cách tiếp cận này cho phép giữ sự thuần khiết cho Domain đồng thời linh hoạt trong việc tách biệt các chính sách.

Lưu ý khi triển khai

  • Việc chuyển sang trạng thái HALF_OPEN có thể coi là việc hết hạn TTL của trạng thái OPEN hoặc quản lý một cách tường minh.
  • onSuccess chỉ tham gia vào việc chuyển đổi trạng thái khi đang ở HALF_OPEN (bỏ qua khi đang ở CLOSED).
  • onFailure chỉ xử lý các ngoại lệ thuộc đối tượng ngắt mạch (loại trừ lỗi phía client).
  • Đảm bảo tính an toàn đa luồng (thread-safe) trong Coroutine bằng cách sử dụng các API bất đồng bộ của Redisson kết hợp với await().

Kết luận

Circuit Breaker dựa trên Redis giúp vượt qua giới hạn của cấu trúc instance đơn lẻ, cho phép chia sẻ trạng thái và áp dụng logic bảo vệ nhất quán trong môi trường phân tán. Sự kết hợp giữa Redisson + Kotlin Coroutine cho phép triển khai ổn định trong môi trường bất đồng bộ, và thông qua bộ phân loại ngoại lệ, chúng ta có thể tách biệt rạch ròi trách nhiệm của Domain và các chính sách kỹ thuật.
Trong tương lai, có thể xem xét các chiến lược mở rộng bao gồm giám sát trạng thái (Monitoring), hiển thị chỉ số (Metrics) và kết hợp với cơ chế thử lại (Retry).
Nguồn bài viết ryukato.github.io