Saat infrastruktur scraping Anda mengirimkan ribuan request solve CAPTCHA, satu worker process akan menjadi bottleneck. Load balancer mendistribusikan request ke beberapa worker — meningkatkan throughput, memungkinkan failover, dan memungkinkan horizontal scaling.
Ikhtisar Arsitektur
[Scraper 1] ──┐ ┌── [Worker 1] ──→ CaptchaAI API
[Scraper 2] ──┤── [Load Balancer] ──┤── [Worker 2] ──→ CaptchaAI API
[Scraper 3] ──┘ └── [Worker 3] ──→ CaptchaAI API
Konfigurasi NGINX
Round-Robin (Default)
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;
}
}
Least Connections (Lebih Baik untuk Solve 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;
}
Dengan Worker 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 API Server
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)
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}`));
Perbandingan Strategi Routing
| Strategi | Cara Kerja | Terbaik Untuk |
|---|---|---|
| Round-Robin | Rotasi berurutan | Worker dengan kapasitas sama |
| Least Connections | Route ke worker paling sedikit load | Solve CAPTCHA (durasi task bervariasi) |
| Weighted | Proporsional dengan bobot | Worker dengan kapasitas campuran |
| IP Hash | Klien yang sama → worker yang sama | Diperlukan session affinity |
| Random | Pilihan acak | Distribusi load sederhana |
Rekomendasi: Gunakan least connections untuk solve CAPTCHA. Durasi task bervariasi (5 detik – 120 detik), sehingga round-robin menghasilkan beban yang tidak merata.
Client-Side Load Balancing
Jika Anda tidak bisa menggunakan load balancer eksternal, implementasikan routing di sisi klien:
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"})
Matriks Pemilihan Algoritma
- Gunakan round-robin ketika worker homogen dan waktu solve berada dalam rentang latensi sempit.
- Gunakan least connections ketika durasi solve bervariasi dan task yang lama akan menumpuk pada satu worker.
- Gunakan backup routing dan source affinity hanya untuk isolasi kegagalan atau target yang sensitif terhadap session.
Pemecahan Masalah
| Masalah | Penyebab | Solusi |
|---|---|---|
| 502 Bad Gateway | Worker crash atau tidak berjalan | Periksa log worker; verifikasi port binding |
| Distribusi beban tidak merata | Round-robin dengan durasi task bervariasi | Beralih ke least connections |
| Health check false positive | Check lulus tapi worker sudah penuh kapasitas | Sertakan load percentage dalam respons health |
| Connection timeout | proxy_read_timeout terlalu pendek |
Set ke 300s+ untuk solve CAPTCHA |
Pertanyaan Umum
Apakah saya perlu load balancer untuk 2-3 worker?
Client-side load balancing sudah cukup untuk setup kecil. Gunakan load balancer khusus (NGINX, HAProxy) saat Anda memiliki 5+ worker atau butuh fitur seperti SSL termination dan health check.
Haruskah saya menggunakan sticky session?
Tidak. Request solve CAPTCHA bersifat stateless — worker mana pun bisa menangani task apa pun. Sticky session akan menyebabkan distribusi beban tidak merata.
Bagaimana cara menangani worker di berbagai region?
Gunakan global load balancer (AWS Global Accelerator, Cloudflare Load Balancing) yang mengarahkan ke region sehat terdekat. Setiap region menjalankan load balancer lokalnya sendiri untuk worker di region tersebut.
Artikel Terkait
- Pola Penanganan Error Callback CaptchaAI
- Pola Arsitektur Solve CAPTCHA Volume Tinggi
- Failover High Availability
Skalakan hasil solve CAPTCHA Anda — dapatkan API key CaptchaAI dan deploy di belakang load balancer.