DevOps ve Ölçeklendirme

Yük Dengeleyicinin Arkasındaki CaptchaAI: Mimari Desenler

Kazıma altyapınız binlerce CAPTCHA çözüm isteği gönderdiğinde, tek bir çalışan süreç darboğazına uğrar. Yük dengeleyici, istekleri birden fazla çalışana dağıtarak verimi artırır, yük devretmeyi etkinleştirir ve yatay olarak ölçeklendirmenize olanak tanır.

Mimariye Genel Bakış

[Scraper 1] ──┐                      ┌── [Worker 1] ──→ CaptchaAI API
[Scraper 2] ──┤── [Load Balancer] ──┤── [Worker 2] ──→ CaptchaAI API
[Scraper 3] ──┘                      └── [Worker 3] ──→ CaptchaAI API

NGINX Yapılandırması

Round-Robin (Varsayılan)

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;
    }
}

En Az Bağlantı (CAPTCHA Çözümü İçin Daha İyi)

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;
}

Yedekleme Çalışanlarıyla

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
}

Çalışan API Sunucusu

Python (Şişe)

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 (Ekspres)

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}`));

Yönlendirme Stratejilerinin Karşılaştırılması

Strateji Nasıl Çalışır? En İyisi
Round-Robin Sıralı dönüş Eşit kapasitede çalışanlar
En Az Bağlantı En az yüklenene rota CAPTCHA çözme (değişken görev süresi)
Ağırlıklı Ağırlıkla orantılı Karma kapasiteli çalışanlar
IP Karması Aynı müşteri – aynı çalışan Oturum benzeşimi gerekli
Rastgele Rastgele seçim Basit, eşit dağıtılmış yük

Öneri: CAPTCHA çözümü için en az bağlantıyı kullanın. Görev süreleri değişiklik gösterir (5 saniye ila 120 saniye), bu nedenle sıralı döngü eşit olmayan yük oluşturur.

İstemci Tarafı Yük Dengeleme

Harici bir yük dengeleyici kullanamadığınızda istemcide yönlendirme uygulayın:

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"})

Algoritma seçim matrisi

  • Çalışanlar homojen olduğunda ve çözüm süreleri dar bir gecikme bandında kaldığında, hepsini bir kez deneme yöntemini kullanın.
  • Çözüm süresi değişkenlik gösterdiğinde ve uzun süren zorluklar tek bir çalışanın omuzlarına yüklendiğinde en az bağlantıyı kullanın.
  • Yedekleme yönlendirmesini ve kaynak benzeşimini yalnızca hata izolasyonu veya oturuma duyarlı hedefler için tutun.

Sorun giderme

Sorun Sebep Düzeltme
502 Kötü Ağ Geçidi İşçi çöktü veya çalıştırılmadı Çalışan günlüklerini kontrol edin; bağlantı noktası bağlamayı doğrula
Düzensiz yük dağılımı Değişken görev süreleri ile hepsini bir kez deneme En az bağlantıya geç
Sağlık kontrolü yanlış pozitif Geçişleri kontrol edin ancak işçi kapasitesinde Durum yanıtına yük yüzdesini dahil et
Bağlantı zaman aşımı proxy_read_timeout çok kısa CAPTCHA çözümü için 300+'ya ayarlayın

SSS

2-3 çalışan için yük dengeleyiciye ihtiyacım var mı?

İstemci tarafı yük dengeleme, küçük kurulumlar için iyi çalışır. 5'ten fazla çalışanınız varsa veya SSL sonlandırma ve durum kontrolleri gibi özelliklere ihtiyacınız varsa, özel bir yük dengeleyici (NGINX, HAProxy) kullanın.

Yapışkan oturumları kullanmalı mıyım?

Hayır. CAPTCHA çözüm istekleri durum bilgisizdir; herhangi bir çalışan herhangi bir görevi yerine getirebilir. Yapışkan oturumlar, eşit olmayan yük dağılımına neden olur.

Farklı bölgelerdeki işçilerle nasıl ilgilenirim?

En yakın sağlıklı bölgeye yönlendirme yapan küresel bir yük dengeleyici (AWS Global Accelerator, Cloudflare Load Balancing) kullanın. Her bölge, o bölgedeki işçiler için kendi yerel yük dengeleyicisini çalıştırır.

İlgili Makaleler

Sonraki Adımlar

CAPTCHA çözme veriminizi ölçeklendirin —CaptchaAI API anahtarınızı alınve bir yük dengeleyicinin arkasına konuşlandırın.

İlgili kılavuzlar:

Bu makale için yorumlar devre dışı bırakılmıştır.