DevOps & Scaling

Yük Dengeleyicinin Arkasındaki CaptchaAI: Mimari Desenler

Tek bir worker süreci saniyede yalnızca sınırlı sayıda CAPTCHA çözüm isteğini işleyebilir; istek hacminiz büyüdüğünde çözüm, trafiği birden fazla worker'a dağıtan bir yük dengeleyicidir. Kısa cevap: değişken çözüm süreleri nedeniyle CAPTCHA iş yükleri için en doğru yönlendirme stratejisi en az bağlantıdır (least connections). Bu rehber worker'ları NGINX arkasında nasıl konumlandıracağınızı, hangi stratejinin ne zaman işe yaradığını ve sağlık kontrollerini üretimde nasıl kuracağınızı adım adım gösterir.

Mimarinin genel görünümü

Yük dengeleyici, scraper'lardan gelen çözüm isteklerini bir worker havuzuna dağıtır; her worker CaptchaAI API'sine bağımsız olarak istek gönderir. Böylece tek bir sürecin darboğaza dönüşmesini önler ve verimi worker sayısıyla birlikte artırırsınız.

Mimari üç katmandan oluşur:

  • Scraper'lar: çözüm isteklerini üreten istemciler.
  • Yük dengeleyici: istekleri sağlıklı worker'lara dağıtan katman.
  • Worker'lar: CaptchaAI API'sine görev gönderip sonucu sorgulayan süreçler.
[Scraper 1] ──┐                      ┌── [Worker 1] ──→ CaptchaAI API
[Scraper 2] ──┤── [Load Balancer] ──┤── [Worker 2] ──→ CaptchaAI API
[Scraper 3] ──┘                      └── [Worker 3] ──→ CaptchaAI API

Yönlendirme stratejileri karşılaştırması

Hangi stratejinin iş yükünüze uyduğuna en baştan karar verin:

Strateji Nasıl çalışır? En uygun senaryo
Round-robin Sırayla dağıtım Eşit kapasiteli worker'lar
En az bağlantı En az yüklü worker'a yönlendirme CAPTCHA çözme (değişken görev süresi)
Ağırlıklı Ağırlıkla orantılı Karma kapasiteli worker'lar
IP karması Aynı istemci – aynı worker Oturum benzeşimi gerektiğinde
Rastgele Rastgele seçim Basit, eşit dağıtılmış yük

Öneri: CAPTCHA çözümü için en az bağlantı stratejisini kullanın. Görev süreleri 5 saniye ile 120 saniye arasında değiştiği için round-robin bazı worker'ları boşta bırakırken diğerlerini aşırı yükler. Oturum benzeşimi (session affinity) gerektiren bir hedefiniz yoksa IP karması stratejisine ihtiyacınız olmaz — CAPTCHA çözüm istekleri durum bilgisizdir.

NGINX yapılandırması

NGINX, worker havuzunun önünde ters proxy ve yük dengeleyici olarak çalışır. Aşağıdaki üç yapılandırma, aynı worker kümesi için üç farklı yönlendirme davranışı gösterir.

Round-robin (varsayılan)

İstekleri worker'lara sırayla dağıtır. Worker'lar eşit kapasitedeyse ve çözüm süreleri birbirine yakınsa yeterlidir; ancak CAPTCHA çözüm süreleri değişken olduğundan tek başına genellikle eşit olmayan yük üretir.

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ü için önerilen)

Her yeni isteği o an en az aktif bağlantıya sahip worker'a yönlendirir. Çözüm süreleri 5 saniye ile 120 saniye arasında değiştiğinden, uzun süren bir görev bir worker'ı meşgul ederken yeni istekler boştaki worker'lara gider. weight=2 ile daha yüksek kapasiteli bir worker'a orantılı olarak daha fazla trafik verebilirsiniz.

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

Yedek worker'larla

backup işaretli worker yalnızca birincil worker'ların tümü devre dışı kaldığında devreye girer. Sıcak yedek (hot standby) kapasitesi için idealdir.

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 sunucusu

Her worker, yük dengeleyiciden /solve isteğini alır, CaptchaAI API'sine görevi gönderir ve sonucu sorgular. /health uç noktası ise anlık yükü bildirerek yük dengeleyicinin aşırı yüklenmiş worker'ları geçici olarak devre dışı bırakmasını sağlar.

Python (Flask)

Eşzamanlı görev sayısını bir kilit (lock) ile izleyen, kapasite dolduğunda 503 WORKER_AT_CAPACITY döndüren örnek bir worker:

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)

Aynı worker mantığının Node.js karşılığı — Axios ile aynı gönder-ve-sorgula döngüsünü kullanır:

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

İstemci tarafı yük dengeleme

Ayrı bir yük dengeleyici (NGINX, HAProxy) kuramadığınız durumlarda yönlendirmeyi doğrudan istemci kodunda uygulayabilirsiniz. Aşağıdaki sınıf, sağlıklı worker'lar arasından en az yüklü olanı seçer ve bir worker 503 döndürdüğünde ya da yanıt vermediğinde görevi otomatik olarak başka bir worker'a yeniden yönlendirir:

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

Strateji seçim rehberi

  • Round-robin: Worker'lar homojen olduğunda ve çözüm süreleri dar bir bantta kaldığında yeterlidir.
  • En az bağlantı: Çözüm süreleri değişkenken ve uzun görevler tek bir worker'ı meşgul edebiliyorken varsayılan tercihiniz olsun.
  • Ağırlıklı yönlendirme: Worker'larınız farklı kapasitelere sahipse (weight) kullanın.
  • IP karması / yedek yönlendirme: Yalnızca oturum benzeşimi ya da hata izolasyonu gereken hedefler için ayırın.

Worker eşzamanlılığını thread planınızla eşleştirme

Yük dengeleyicinin ötesinde sıkça göz ardı edilen bir sınır vardır: CaptchaAI planınızın thread sayısı. CaptchaAI çözüm başına değil eşzamanlı thread başına ücretlendirir ve her plan thread başına sınırsız çözüm içerir. Bir thread, o an işlenen tek bir CAPTCHA demektir.

Tüm worker'larınızdaki MAX_CONCURRENT değerlerinin toplamı planınızın thread sayısını aşmamalıdır. Örneğin her biri MAX_CONCURRENT=20 ile çalışan üç worker toplam 60 eşzamanlı görev üretir; bu, ADVANCE ($90/ay, 50 thread) planının 50 thread sınırını aşar ve fazla istekler API tarafında kuyruğa girer. Bu durumda ya worker başına eşzamanlılığı düşürün ya da PREMIUM ($170/ay, 100 thread) gibi daha yüksek bir plana geçin. Thread tabanlı USD fiyatlandırması, eşzamanlılığı öngörülebilir bir aylık maliyete bağladığı için kapasite planlamasını kolaylaştırır.

Yerel senaryo: e-ticaret QA ekibi

Türkiye'deki bir e-ticaret ekibinin, ödeme adımı (checkout) ve giriş akışlarını Europe/Istanbul saat diliminde gece toplu olarak doğruladığını düşünün. QA test akışları kısa sürede yüzlerce reCAPTCHA doğrulaması üretir; tek bir worker bu ani yükü kaldıramaz. En az bağlantı stratejisiyle üç worker'a dağıtılan trafik, uzun süren çözümlerin kısa olanları bloke etmesini önler. Test sırasında kişisel veri içeren sayfalar toplanıyorsa, bu verinin KVKK kapsamında olduğunu ve yalnızca yetkili QA/veri toplama iş akışlarında işlenmesi gerektiğini unutmayın.

Sağlık kontrolleri ve gözlemlenebilirlik

/health uç noktasının yalnızca "ayakta mı" değil, "kapasitede mi" bilgisini de döndürmesi kritiktir. Yukarıdaki worker, yük %90'ı aştığında 503 döndürerek yük dengeleyicinin o worker'a yeni istek göndermesini durdurur. Üretimde ayrıca şu metrikleri izleyin: worker başına aktif görev sayısı, çözüm başarı oranı, ortalama çözüm süresi ve 503/TIMEOUT yanıtlarının sıklığı. Bu metrikler, bir worker'ı büyütmeniz mi yoksa havuzu yatay olarak genişletmeniz mi gerektiğini net biçimde gösterir.

Sorun giderme

Sorun Sebep Çözüm
502 Bad Gateway Worker çöktü veya hiç başlatılmadı Worker günlüklerini inceleyin; port bağlamasını (port binding) doğrulayın
Düzensiz yük dağılımı Değişken görev süreleriyle round-robin En az bağlantı stratejisine geçin
Sağlık kontrolü yanlış pozitif veriyor Kontrol geçiyor ama worker kapasitede /health yanıtına yük yüzdesini ekleyin
Bağlantı zaman aşımı proxy_read_timeout çok kısa CAPTCHA çözümü için 300 saniye veya üzerine ayarlayın

Sık sorulan sorular

CAPTCHA çözüm worker'ları için hangi yönlendirme stratejisi en iyisidir?

En az bağlantı (least connections). Çözüm süreleri görevden göreve büyük ölçüde değiştiği için, isteği o an en az meşgul worker'a yönlendirmek yükü round-robin'den çok daha dengeli dağıtır.

Toplam MAX_CONCURRENT değerim planımın thread sayısını aşarsa ne olur?

Fazla istekler CaptchaAI tarafında kuyruğa girer ve çözüm süreleri uzar. Tüm worker'larınızdaki eşzamanlılık toplamını planınızın thread sayısıyla (ör. ADVANCE'te 50 thread) hizalayın veya daha yüksek bir plana geçin.

Yapışkan oturum (sticky session) kullanmalı mıyım?

Hayır. CAPTCHA çözüm istekleri durum bilgisizdir; herhangi bir worker herhangi bir görevi işleyebilir. Yapışkan oturumlar yalnızca eşit olmayan yük dağılımına yol açar.

Bir worker çöktüğünde trafik otomatik yeniden yönlendirilir mi?

Evet. NGINX'te max_fails ve fail_timeout ayarları çöken worker'ı havuzdan çıkarır; istemci tarafı yük dengelemede ise 503 veya bağlantı hatası alan görev başka bir worker'a yeniden gönderilir.

proxy_read_timeout değerini kaça ayarlamalıyım?

Uzun süren çözümlerin kesilmemesi için en az 300 saniye. Çözüm süresi tek bir CAPTCHA'da 120 saniyeye kadar çıkabildiğinden, daha kısa bir zaman aşımı geçerli çözümleri boşa harcar.

İlgili makaleler

Sonraki adımlar

Çözüm veriminizi ölçeklendirmeye hazır mısınız? CaptchaAI API anahtarınızı alın ve worker'larınızı bir yük dengeleyici arkasında yayına alın.

İlgili kılavuzlar:

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