Hướng Dẫn API

Mẫu ngắt mạch cho lệnh gọi API CAPTCHA

Pipeline của bạn gửi hàng trăm request giải CAPTCHA mỗi phút, rồi API bắt đầu trả lỗi liên tục — có thể do quá tải, do bảo trì đột xuất, hoặc do hết slot xử lý. Nếu code vẫn tiếp tục gửi request như bình thường, bạn vừa tốn phí cho các lần gọi thất bại, vừa làm chậm cả pipeline phía sau. Circuit breaker (mẫu ngắt mạch) giải quyết đúng vấn đề này: tự động dừng gọi một API đang lỗi, chờ một khoảng thời gian hồi phục, rồi mới thử lại có kiểm soát — thay vì để lỗi lan ra (cascading failure) toàn hệ thống.

Circuit breaker đáng cân nhắc khi:

  • Pipeline chạy đa luồng và gửi liên tục nhiều task giải CAPTCHA song song.
  • Một lần API lỗi kéo dài vài chục giây trở lên đủ để làm nghẽn cả hàng đợi phía sau.
  • Bạn muốn kiểm soát chi phí — không trả tiền cho hàng trăm lần gọi thất bại liên tiếp vào một API đang có sự cố.

Ba trạng thái của circuit breaker

Circuit breaker vận hành theo ba trạng thái, tự chuyển đổi dựa trên số lần lỗi liên tiếp:

Trạng thái Ý nghĩa
Đóng (Closed) Trạng thái mặc định. Request đi qua bình thường, số lần lỗi được đếm ở nền.
Mở (Open) Số lỗi đã vượt ngưỡng cho phép. Mọi request bị từ chối ngay lập tức, không gọi API thật, cho tới khi hết thời gian hồi phục (recovery_timeout).
Nửa mở (Half-Open) Sau thời gian hồi phục, đúng một request thử nghiệm được cho đi qua. Thành công thì mạch đóng lại; thất bại thì mạch mở lại và đếm lỗi lại từ đầu.

Ví dụ thực tế: một đội QA của công ty outsourcing tại TP.HCM chạy vài nghìn task giải reCAPTCHA v2 mỗi ngày cho khách hàng nước ngoài. Vào giờ cao điểm, API đôi khi trả về ERROR_NO_SLOT_AVAILABLE liên tục trong vài chục giây. Không có circuit breaker, hàng trăm request vẫn tiếp tục dồn vào một endpoint đang quá tải, kéo theo cả cụm worker phía sau bị nghẽn theo. Có circuit breaker, hệ thống tự "nghỉ" một nhịp rồi mới thử lại, giữ cho phần còn lại của pipeline vẫn chạy bình thường.


Chọn ngưỡng theo lưu lượng traffic

Không có một cặp failure_threshold / recovery_timeout đúng cho mọi hệ thống — con số phù hợp phụ thuộc vào lưu lượng request thực tế của bạn. Đặt ngưỡng lỗi đủ cao để bỏ qua các lỗi ngắt quãng (một lần timeout đơn lẻ không nên làm mạch mở ngay) nhưng đủ thấp để không tiếp tục dội request vào một API đang thực sự gặp sự cố:

Tham số Lưu lượng thấp (< 10/phút) Lưu lượng cao (> 100/phút)
failure_threshold 3 10
recovery_timeout 30 giây 60 giây

Hai ví dụ triển khai dưới đây dùng failure_threshold = 3recovery_timeout = 30 giây — phù hợp mức traffic thấp, bạn có thể tăng theo bảng trên khi quy mô lớn hơn.


Triển khai bằng Python

Bản triển khai dưới đây dùng threading.Lock để tránh race condition khi nhiều luồng cùng gọi circuit breaker — đúng với mô hình đa luồng (multi-thread) phổ biến khi chạy song song nhiều task giải CAPTCHA:

import time
import threading
import requests

SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
API_KEY = "YOUR_API_KEY"

class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=60):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.failure_count = 0
        self.last_failure_time = 0
        self.state = "closed"  # closed, open, half-open
        self._lock = threading.Lock()

    def call(self, func, *args, **kwargs):
        with self._lock:
            if self.state == "open":
                if time.time() - self.last_failure_time > self.recovery_timeout:
                    self.state = "half-open"
                    print("[circuit] State: half-open — testing one request")
                else:
                    remaining = self.recovery_timeout - (
                        time.time() - self.last_failure_time
                    )
                    raise CircuitOpenError(
                        f"Circuit open — retry in {remaining:.0f}s"
                    )

        try:
            result = func(*args, **kwargs)
            with self._lock:
                self.failure_count = 0
                if self.state == "half-open":
                    print("[circuit] State: closed — API recovered")
                self.state = "closed"
            return result
        except Exception as e:
            with self._lock:
                self.failure_count += 1
                self.last_failure_time = time.time()
                if self.failure_count >= self.failure_threshold:
                    self.state = "open"
                    print(
                        f"[circuit] State: open — "
                        f"{self.failure_count} failures"
                    )
            raise

class CircuitOpenError(Exception):
    pass

def solve_captcha(sitekey, page_url):
    resp = requests.post(SUBMIT_URL, data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": "1",
    }, timeout=15)
    data = resp.json()
    if data["status"] != 1:
        raise Exception(f"Submit error: {data['request']}")

    task_id = data["request"]
    for _ in range(24):
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": "1",
        }, timeout=15).json()
        if poll["status"] == 1:
            return poll["request"]
        if poll["request"] != "CAPCHA_NOT_READY":
            raise Exception(f"Poll error: {poll['request']}")
    raise TimeoutError(f"Task {task_id} timed out")

# Usage
breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=30)

for i in range(10):
    try:
        token = breaker.call(
            solve_captcha, "6Le-SITEKEY", "https://example.com"
        )
        print(f"[task-{i}] Solved: {token[:40]}...")
    except CircuitOpenError as e:
        print(f"[task-{i}] Skipped: {e}")
    except Exception as e:
        print(f"[task-{i}] Failed: {e}")

Chạy thử với 10 task liên tiếp, bạn sẽ thấy mạch tự mở sau 3 lỗi liên tiếp, từ chối các task tiếp theo trong 30 giây, rồi tự chuyển sang nửa mở để thử lại:

[task-0] Solved: 03AGdBq26ZfPxL...
[task-1] Solved: 03AGdBq27AbCdE...
[task-2] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[task-3] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[task-4] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[circuit] State: open — 3 failures
[task-5] Skipped: Circuit open — retry in 28s
[task-6] Skipped: Circuit open — retry in 25s
...
[circuit] State: half-open — testing one request
[task-8] Solved: 03AGdBq28FgHiJ...
[circuit] State: closed — API recovered

Triển khai bằng JavaScript

Logic tương tự áp dụng cho Node.js, nhưng dùng async/await thay vì lock thủ công — JavaScript đơn luồng (single-threaded) không có race condition giữa các lệnh gọi call():

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.recoveryTimeout = options.recoveryTimeout || 60000;
    this.failureCount = 0;
    this.lastFailureTime = 0;
    this.state = 'closed';
  }

  async call(fn, ...args) {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
        this.state = 'half-open';
        console.log('[circuit] State: half-open');
      } else {
        const remaining = this.recoveryTimeout - (Date.now() - this.lastFailureTime);
        throw new Error(`Circuit open — retry in ${Math.ceil(remaining / 1000)}s`);
      }
    }

    try {
      const result = await fn(...args);
      this.failureCount = 0;
      if (this.state === 'half-open') {
        console.log('[circuit] State: closed — recovered');
      }
      this.state = 'closed';
      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();
      if (this.failureCount >= this.failureThreshold) {
        this.state = 'open';
        console.log(`[circuit] State: open — ${this.failureCount} failures`);
      }
      throw error;
    }
  }
}

// Usage
const axios = require('axios');

const API_KEY = 'YOUR_API_KEY';
const breaker = new CircuitBreaker({ failureThreshold: 3, recoveryTimeout: 30000 });

async function solveCaptcha(sitekey, pageurl) {
  const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
    params: { key: API_KEY, method: 'userrecaptcha', googlekey: sitekey, pageurl, json: 1 }
  });

  if (submit.data.status !== 1) throw new Error(submit.data.request);
  const taskId = submit.data.request;

  for (let i = 0; i < 24; i++) {
    await new Promise(r => setTimeout(r, 5000));
    const poll = await axios.get('https://ocr.captchaai.com/res.php', {
      params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
    });
    if (poll.data.status === 1) return poll.data.request;
    if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
  }
  throw new Error('Timeout');
}

(async () => {
  for (let i = 0; i < 10; i++) {
    try {
      const token = await breaker.call(solveCaptcha, '6Le-SITEKEY', 'https://example.com');
      console.log(`[task-${i}] Solved: ${token.substring(0, 40)}...`);
    } catch (err) {
      console.log(`[task-${i}] ${err.message}`);
    }
  }
})();

Kết hợp circuit breaker với retry logic

Retry và circuit breaker không thay thế nhau: retry xử lý lỗi tạm thời ở cấp một request đơn lẻ, còn circuit breaker xử lý lỗi kéo dài ở cấp toàn hệ thống. Đặt logic retry bên trong circuit breaker — circuit breaker chỉ đếm lỗi cuối cùng, sau khi các lần thử lại đã dùng hết:

def solve_with_retry(sitekey, page_url, max_retries=2):
    for attempt in range(max_retries + 1):
        try:
            return solve_captcha(sitekey, page_url)
        except Exception:
            if attempt == max_retries:
                raise
            time.sleep(2 ** attempt)

# Circuit breaker wraps the retry function
token = breaker.call(solve_with_retry, "6Le-SITEKEY", "https://example.com")

Các lỗi thường gặp khi triển khai

  • Mạch mở quá nhanh — nguyên nhân thường là ngưỡng lỗi đặt quá thấp; tăng failure_threshold lên.
  • Mạch không bao giờ phục hồirecovery_timeout đang quá dài; giảm xuống còn 30–60 giây.
  • Race condition khi chạy đa luồng — trạng thái không được khóa; dùng threading.Lock (Python) hoặc thao tác nguyên tử.
  • Toàn bộ request bị chặn dù chỉ một phần API lỗi — đang dùng chung một circuit breaker cho mọi endpoint; tách riêng circuit breaker cho endpoint gửi task và endpoint polling.

Câu hỏi thường gặp

Circuit breaker khác gì so với việc chỉ thêm retry?

Retry lặp lại một request đơn lẻ khi gặp lỗi tạm thời; circuit breaker theo dõi lỗi trên diện rộng và tạm ngừng toàn bộ luồng gọi khi API thực sự có vấn đề. Dùng retry mà không có circuit breaker, bạn vẫn dội hàng trăm request vào một API đang gặp sự cố — kết hợp cả hai mới tránh được lỗi xếp tầng trong pipeline.

Nên đặt failure_thresholdrecovery_timeout bằng bao nhiêu?

Không có con số chuẩn cho mọi hệ thống, nhưng điểm khởi đầu hợp lý là failure_threshold = 3recovery_timeout = 30 giây cho traffic thấp, tăng lên 1060 giây cho traffic cao. Theo dõi log thực tế trong vài ngày đầu rồi tinh chỉnh theo tỷ lệ lỗi thật của API.

Có cần circuit breaker riêng cho endpoint gửi task và endpoint polling không?

Có, với hệ thống quy mô lớn. Endpoint gửi task (in.php) và endpoint polling (res.php) có thể lỗi độc lập với nhau — dùng chung một circuit breaker cho cả hai sẽ khiến cả pipeline dừng lại chỉ vì một endpoint gặp sự cố tạm thời.

Nên làm gì khi mạch đang ở trạng thái mở?

Đưa task CAPTCHA vào hàng đợi để xử lý lại sau, hiển thị giao diện dự phòng cho người dùng, hoặc bỏ qua bước đó nếu nghiệp vụ cho phép — đừng cố gọi lại API ngay lập tức. Xem thêm Xử lý khi giải CAPTCHA thất bại một cách linh hoạt.


Bảo vệ pipeline giải CAPTCHA với CaptchaAI

Lấy API key CaptchaAI và triển khai circuit breaker phía trên endpoint in.php / res.php ngay hôm nay tại captchaai.com.


Hướng dẫn liên quan

Os comentários estão desativados para este artigo.