Một worker giải CAPTCHA chỉ xử lý được vài request cùng lúc — vượt ngưỡng đó, hàng đợi phình ra và pipeline crawl nghẽn lại. Giải pháp chuẩn: đặt nhiều worker sau một load balancer để NGINX/HAProxy phân phối request /solve, tự loại worker lỗi, và cho phép thêm worker mới không cần sửa code gọi API.
Ví dụ thực tế: một đội automation tại TP.HCM theo dõi giá trên Shopee, Lazada và Tiki mỗi giờ phải chạy song song hàng chục worker gửi request tới
in.php/res.phpcủa CaptchaAI. Không có load balancer, một worker treo vì quá tải sẽ nghẽn cả đợt crawl kế tiếp; với least connections, request tự dồn sang worker còn rảnh.
Sơ đồ kiến trúc tổng quan
[Scraper 1] ──┐ ┌── [Worker 1] ──→ CaptchaAI API
[Scraper 2] ──┤── [Load Balancer] ──┤── [Worker 2] ──→ CaptchaAI API
[Scraper 3] ──┘ └── [Worker 3] ──→ CaptchaAI API
Scraper không gọi thẳng CaptchaAI — mọi request đi qua load balancer trước.
Cấu hình NGINX cho worker giải CAPTCHA
Ba biến thể upstream dưới đây khác nhau ở chiến lược định tuyến.
Round-robin (mặc định)
upstream captcha_workers {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080;
}
server {
listen 80;
server_name captcha.internal;
location /solve {
proxy_pass http://captcha_workers;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 10s;
proxy_read_timeout 300s; # CAPTCHA solving can take minutes
}
location /health {
proxy_pass http://captcha_workers;
proxy_connect_timeout 5s;
proxy_read_timeout 5s;
}
}
proxy_read_timeout 300s trên route /solve là bắt buộc — vượt xa timeout mặc định 60s của NGINX.
Least connections (phù hợp hơn cho việc giải CAPTCHA)
upstream captcha_workers {
least_conn; # Route to worker with fewest active connections
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080 weight=2; # Higher capacity worker
# Health checks
server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 max_fails=3 fail_timeout=30s;
}
weight=2 cho worker mạnh hơn nhận gấp đôi lưu lượng; max_fails/fail_timeout tự loại worker lỗi trong 30 giây.
Với worker dự phòng (backup)
upstream captcha_workers {
least_conn;
server 10.0.1.10:8080;
server 10.0.1.11:8080;
server 10.0.1.12:8080 backup; # Only used when others are down
}
Worker đánh dấu backup chỉ nhận request khi cả hai worker chính down — giữ dự phòng chi phí thấp thay vì chạy đủ 3 worker song song suốt ngày.
Chọn chiến lược định tuyến phù hợp
| Chiến lược | Cách hoạt động | Phù hợp nhất với |
|---|---|---|
| Round-robin | Xoay tuần tự qua từng worker | Worker có năng lực đồng đều |
| Least connections | Định tuyến tới worker đang ít kết nối nhất | Giải CAPTCHA (thời lượng tác vụ dao động mạnh) |
| Weighted | Phân phối tỷ lệ thuận với trọng số cấu hình | Cụm worker có năng lực không đồng đều |
| IP Hash | Cùng một client luôn về cùng một worker | Cần duy trì quan hệ phiên (session affinity) |
| Random | Chọn worker ngẫu nhiên | Tải phân bố đều, không cần logic phức tạp |
Khuyến nghị: dùng least connections. Thời lượng một tác vụ dao động 5 giây–120 giây tùy loại CAPTCHA, nên round-robin dồn nhiều tác vụ chậm vào cùng một worker và tạo tải không đồng đều.
- Round-robin: worker đồng nhất, thời gian giải nằm trong dải hẹp (chỉ một loại CAPTCHA).
- Least connections: thời lượng giải dao động mạnh — mặc định an toàn cho hầu hết cụm worker CaptchaAI.
- IP Hash/backup: chỉ cần khi phải cách ly lỗi hoặc phiên nhạy cảm; request giải CAPTCHA vốn không trạng thái nên hiếm khi cần.
Máy chủ API cho worker (Python và Node.js)
Hai máy chủ dưới đây expose cùng route /solve và /health.
Python (Flask)
import os
import time
import threading
import requests
from flask import Flask, request, jsonify
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
app = Flask(__name__)
# Track active tasks for load reporting
active_tasks = 0
tasks_lock = threading.Lock()
max_concurrent = int(os.environ.get("MAX_CONCURRENT", "20"))
@app.route("/solve", methods=["POST"])
def solve():
global active_tasks
with tasks_lock:
if active_tasks >= max_concurrent:
return jsonify({"error": "WORKER_AT_CAPACITY"}), 503
active_tasks += 1
try:
data = request.json
result = solve_captcha(data)
return jsonify(result)
finally:
with tasks_lock:
active_tasks -= 1
@app.route("/health")
def health():
with tasks_lock:
load = active_tasks / max_concurrent
return jsonify({
"status": "healthy" if load < 0.9 else "overloaded",
"active_tasks": active_tasks,
"max_concurrent": max_concurrent,
"load_pct": round(load * 100, 1)
}), 200 if load < 0.9 else 503
def solve_captcha(data):
session = requests.Session()
payload = {
"key": API_KEY,
"method": data.get("method", "userrecaptcha"),
"googlekey": data.get("sitekey"),
"pageurl": data.get("pageurl"),
"json": 1
}
if data.get("proxy"):
payload["proxy"] = data["proxy"]
payload["proxytype"] = data.get("proxytype", "HTTP")
resp = session.post("https://ocr.captchaai.com/in.php", data=payload)
result = resp.json()
if result.get("status") != 1:
return {"error": result.get("request")}
captcha_id = result["request"]
for _ in range(60):
time.sleep(5)
poll = session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": captcha_id, "json": 1
}).json()
if poll.get("status") == 1:
return {"solution": poll["request"], "captcha_id": captcha_id}
if poll.get("request") != "CAPCHA_NOT_READY":
return {"error": poll.get("request")}
return {"error": "TIMEOUT"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, threaded=True)
active_tasks giới hạn request đồng thời; vượt max_concurrent worker trả 503 để load balancer chuyển hướng request khác.
JavaScript (Express)
const express = require("express");
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const MAX_CONCURRENT = parseInt(process.env.MAX_CONCURRENT || "20", 10);
const PORT = parseInt(process.env.PORT || "8080", 10);
let activeTasks = 0;
const app = express();
app.use(express.json());
app.post("/solve", async (req, res) => {
if (activeTasks >= MAX_CONCURRENT) {
return res.status(503).json({ error: "WORKER_AT_CAPACITY" });
}
activeTasks++;
try {
const result = await solveCaptcha(req.body);
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
} finally {
activeTasks--;
}
});
app.get("/health", (req, res) => {
const load = activeTasks / MAX_CONCURRENT;
const status = load < 0.9 ? "healthy" : "overloaded";
res
.status(load < 0.9 ? 200 : 503)
.json({ status, activeTasks, maxConcurrent: MAX_CONCURRENT, loadPct: Math.round(load * 100) });
});
async function solveCaptcha(data) {
const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method: data.method || "userrecaptcha",
googlekey: data.sitekey,
pageurl: data.pageurl,
json: 1,
},
});
if (submitResp.data.status !== 1) {
return { error: submitResp.data.request };
}
const captchaId = submitResp.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const pollResp = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (pollResp.data.status === 1) {
return { solution: pollResp.data.request, captchaId };
}
if (pollResp.data.request !== "CAPCHA_NOT_READY") {
return { error: pollResp.data.request };
}
}
return { error: "TIMEOUT" };
}
app.listen(PORT, () => console.log(`Worker listening on port ${PORT}`));
Logic giống bản Python, chỉ khác async/await thay vì blocking I/O.
Cân bằng tải phía client (khi chưa cần NGINX)
Chưa muốn dựng load balancer riêng? Tự định tuyến ngay trong code client:
import random
import requests
class ClientLoadBalancer:
def __init__(self, workers):
self.workers = [
{"url": url, "healthy": True, "active": 0}
for url in workers
]
def get_worker(self):
healthy = [w for w in self.workers if w["healthy"]]
if not healthy:
raise Exception("No healthy workers")
return min(healthy, key=lambda w: w["active"])
def solve(self, task):
worker = self.get_worker()
worker["active"] += 1
try:
resp = requests.post(
f"{worker['url']}/solve",
json=task,
timeout=300
)
if resp.status_code == 503:
worker["healthy"] = False
return self.solve(task) # Retry on another worker
return resp.json()
except requests.RequestException:
worker["healthy"] = False
return self.solve(task)
finally:
worker["active"] -= 1
lb = ClientLoadBalancer([
"http://10.0.1.10:8080",
"http://10.0.1.11:8080",
"http://10.0.1.12:8080"
])
result = lb.solve({"sitekey": "6Le-wvkS...", "pageurl": "https://example.com"})
Client tự theo dõi worker "healthy" và dồn task sang worker ít việc nhất — logic tương tự least_conn của NGINX nhưng chạy ngay trong code, không cần thêm hạ tầng. Đủ dùng cho 2-4 worker; nhiều hơn thì nên chuyển sang NGINX/HAProxy.
Khắc phục sự cố thường gặp
- 502 Bad Gateway — worker crash hoặc chưa khởi động xong; kiểm tra log worker và xác minh port binding.
- Tải phân bố không đều — round-robin với tác vụ có thời lượng dao động; chuyển sang least connections.
- Health check dương tính giả — health check trả
200dù worker đã đầy tải; đưaload_pctvào response/health. - Timeout kết nối —
proxy_read_timeoutquá ngắn; đặt tối thiểu 300s cho route/solve.
Câu hỏi thường gặp
Cần tối thiểu bao nhiêu worker mới nên dùng load balancer?
Với 2-4 worker, cân bằng tải phía client là đủ. Từ 5 worker trở lên, hoặc khi cần SSL termination và health check tập trung, chuyển sang NGINX/HAProxy sẽ dễ vận hành hơn.
CaptchaAI tính phí theo thread hay theo số worker?
Theo thread — số CAPTCHA xử lý đồng thời trên toàn tài khoản, không theo số worker hay mỗi lần giải. Gói BASIC ($15/tháng, 5 thread) cho tối đa 5 request cùng lúc dù chạy 2 hay 10 worker; số worker chỉ quyết định cách phân phối.
Có nên dùng sticky session cho worker giải CAPTCHA?
- Không cần thiết: request giải CAPTCHA không trạng thái, mọi ngữ cảnh (
sitekey,pageurl, proxy) đã nằm sẵn trong request. - Sticky session chỉ ép một client về một worker cố định, gây lệch tải mà không đổi lại lợi ích nào.
Gặp 502/504 lúc tải cao thì kiểm tra gì trước?
Xem proxy_read_timeout đã đủ (300s là mức an toàn) chưa, rồi kiểm tra /health có phản ánh đúng tải không — nhiều lúc worker "healthy" trên giấy tờ nhưng đã đầy max_concurrent.
Bài viết liên quan và bước tiếp theo
Xem thêm xử lý lỗi callback CaptchaAI, mẫu Puppeteer nâng cao Node.js và kiến trúc giải CAPTCHA khối lượng lớn.
Sẵn sàng scale throughput? Lấy API key CaptchaAI, triển khai worker theo mẫu trên, rồi đọc thêm failover có tính sẵn sàng cao, kiến trúc đa vùng và chọn kích thước connection pool.