DevOps & Skalierung

Hochverfügbare CAPTCHA-Lösung: Failover und Redundanz

Wenn Ihr Umsatz von der automatisierten Datenerfassung abhängt, bedeutet ein Ausfall der CAPTCHA-Lösung Datenverlust und gebrochene SLAs. Das Hochverfügbarkeitsdesign (HA) stellt sicher, dass Ihre Pipeline auch bei Teilausfällen – Worker-Abstürzen, Netzwerkproblemen oder API-Schluckauf – weiterläuft.

HA-Komponenten

[Health Checker] ──── monitors ────→ [Worker Pool A]
       ↓                                    ↓ (primary)
[Circuit Breaker]                    [CaptchaAI API]
       ↓                                    ↑ (fallback)
[Failover Router] ── redirects ──→ [Worker Pool B]
       ↓
[Dead Letter Queue] ← unrecoverable failures

Schicht 1: Arbeiterredundanz

Führen Sie mehr Worker aus, als Sie benötigen. Wenn einer ausfällt, übernehmen die verbleibenden Arbeiter die Last.

Python – Überwachter Worker-Pool

import os
import time
import threading
import queue
import requests

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
task_queue = queue.Queue(maxsize=200)
results = {}


class SupervisedWorkerPool:
    def __init__(self, worker_count, min_workers=2):
        self.worker_count = worker_count
        self.min_workers = min_workers
        self.workers = {}
        self.lock = threading.Lock()

    def start(self):
        """Launch workers and supervisor."""
        for i in range(self.worker_count):
            self._launch_worker(i)

        # Supervisor thread monitors worker health
        supervisor = threading.Thread(target=self._supervise, daemon=True)
        supervisor.start()

    def _launch_worker(self, worker_id):
        t = threading.Thread(
            target=self._worker_loop,
            args=(worker_id,),
            daemon=True
        )
        t.start()
        with self.lock:
            self.workers[worker_id] = {
                "thread": t,
                "alive": True,
                "last_heartbeat": time.time(),
                "tasks_completed": 0
            }

    def _worker_loop(self, worker_id):
        session = requests.Session()
        while True:
            try:
                task = task_queue.get(timeout=30)
                result = solve_captcha(session, task)
                results[task["task_id"]] = result

                with self.lock:
                    self.workers[worker_id]["last_heartbeat"] = time.time()
                    self.workers[worker_id]["tasks_completed"] += 1

                task_queue.task_done()
            except queue.Empty:
                # Heartbeat even when idle
                with self.lock:
                    self.workers[worker_id]["last_heartbeat"] = time.time()
            except Exception as e:
                print(f"Worker {worker_id} error: {e}")
                with self.lock:
                    self.workers[worker_id]["last_heartbeat"] = time.time()

    def _supervise(self):
        """Restart dead workers."""
        while True:
            time.sleep(15)
            with self.lock:
                now = time.time()
                for wid, info in list(self.workers.items()):
                    if not info["thread"].is_alive():
                        print(f"Worker {wid} died — restarting")
                        self._launch_worker(wid)
                    elif now - info["last_heartbeat"] > 120:
                        print(f"Worker {wid} stalled — replacing")
                        self._launch_worker(wid)

    @property
    def status(self):
        with self.lock:
            alive = sum(1 for w in self.workers.values()
                       if w["thread"].is_alive())
            return {
                "alive": alive,
                "total": len(self.workers),
                "healthy": alive >= self.min_workers
            }


def solve_captcha(session, task):
    resp = session.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": task.get("method", "userrecaptcha"),
        "googlekey": task["sitekey"],
        "pageurl": task["pageurl"],
        "json": 1
    })
    data = resp.json()

    if data.get("status") != 1:
        return {"error": data.get("request")}

    captcha_id = data["request"]
    for _ in range(60):
        time.sleep(5)
        result = session.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": captcha_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return {"solution": result["request"]}
        if result.get("request") != "CAPCHA_NOT_READY":
            return {"error": result.get("request")}
    return {"error": "TIMEOUT"}


# Start pool with 8 workers, minimum 3 healthy
pool = SupervisedWorkerPool(worker_count=8, min_workers=3)
pool.start()

Schicht 2: Leistungsschalter

Erkennen Sie, wenn CaptchaAI Probleme hat, und stoppen Sie das Senden von Anfragen, um eine Verschwendung von Guthaben durch Zeitüberschreitungen zu vermeiden:

JavaScript

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.resetTimeout = options.resetTimeout || 60000; // 1 minute
    this.failures = 0;
    this.lastFailure = 0;
    this.state = "closed"; // closed, open, half-open
    this.successesInHalfOpen = 0;
  }

  async execute(fn) {
    if (this.state === "open") {
      if (Date.now() - this.lastFailure > this.resetTimeout) {
        this.state = "half-open";
        this.successesInHalfOpen = 0;
      } else {
        throw new Error("Circuit breaker is OPEN — requests blocked");
      }
    }

    try {
      const result = await fn();
      this._onSuccess();
      return result;
    } catch (err) {
      this._onFailure();
      throw err;
    }
  }

  _onSuccess() {
    if (this.state === "half-open") {
      this.successesInHalfOpen++;
      if (this.successesInHalfOpen >= 3) {
        this.state = "closed";
        this.failures = 0;
        console.log("Circuit breaker CLOSED — service recovered");
      }
    } else {
      this.failures = 0;
    }
  }

  _onFailure() {
    this.failures++;
    this.lastFailure = Date.now();
    if (this.failures >= this.failureThreshold) {
      this.state = "open";
      console.log(
        `Circuit breaker OPEN — ${this.failures} consecutive failures`
      );
    }
  }
}

// Usage
const breaker = new CircuitBreaker({
  failureThreshold: 5,
  resetTimeout: 60000,
});

async function solveCaptchaWithBreaker(sitekey, pageurl) {
  return breaker.execute(() => solveCaptcha(sitekey, pageurl));
}

Schicht 3: Endpunkt für die Gesundheitsprüfung

Stellen Sie den Gesundheitsstatus für Load Balancer und Überwachung bereit:

Python (Flasche)

from flask import Flask, jsonify

app = Flask(__name__)


@app.route("/health")
def health_check():
    pool_status = pool.status
    queue_depth = task_queue.qsize()

    health = {
        "status": "healthy" if pool_status["healthy"] else "degraded",
        "workers_alive": pool_status["alive"],
        "workers_total": pool_status["total"],
        "queue_depth": queue_depth,
        "queue_capacity": task_queue.maxsize
    }

    code = 200 if health["status"] == "healthy" else 503
    return jsonify(health), code


@app.route("/health/ready")
def readiness_check():
    """Readiness probe — is this instance ready to receive tasks?"""
    if pool.status["alive"] > 0 and task_queue.qsize() < task_queue.maxsize:
        return "ready", 200
    return "not ready", 503

Schicht 4: Anmutige Degradierung

Wenn etwas schief geht, verschlechtern Sie sich sanft, anstatt völlig zu scheitern:

class GracefulDegradation:
    def __init__(self):
        self.mode = "normal"  # normal, degraded, emergency

    def set_mode(self, error_rate, queue_depth, workers_alive):
        if workers_alive == 0 or error_rate > 0.5:
            self.mode = "emergency"
        elif error_rate > 0.2 or queue_depth > 150:
            self.mode = "degraded"
        else:
            self.mode = "normal"

    def should_accept_task(self, priority):
        if self.mode == "normal":
            return True
        if self.mode == "degraded":
            return priority in ("high", "critical")
        return priority == "critical"  # Emergency: critical only

    @property
    def status(self):
        return {
            "mode": self.mode,
            "accepting": {
                "normal": self.mode == "normal",
                "degraded": self.mode in ("normal", "degraded"),
                "emergency": True
            }
        }

HA-Checkliste

Komponente Umgesetzt? Notizen
Mehrere Arbeiter (N+1) Ja Mindestens 1 Ersatzarbeiter
Gesundheitsüberwachung der Arbeitnehmer Ja Supervisor-Thread oder Prozessmanager
Automatischer Worker-Neustart Ja Bei Absturz oder Stillstand
Leistungsschalter Ja Stoppen Sie Anfragen bei API-Problemen
Endpunkt für die Integritätsprüfung Ja Für Load Balancer
Anmutige Erniedrigung Ja Prioritätsbasierte Aufgabenannahme
Warteschlange für unzustellbare Nachrichten Ja Für nicht behebbare Ausfälle
Fallback-Umfrage Ja Wenn Rückrufe fehlschlagen
Alarmierung Ja PagerDuty, Slack, E-Mail

Fehlerbehebung

Problem Ursache Lösung
Worker ist erreichbar, verarbeitet aber keine Aufgaben Queue, Credentials oder Eingabestrom stimmen nicht Prüfe Queue-Tiefe, API-Key, Health-Checks und Fehlerraten pro Worker gemeinsam
Fehlerrate steigt nach Rollout Neue Version verändert Session-, Proxy- oder Retry-Verhalten Vergleiche erfolgreiche und fehlschlagende Runs zwischen alter und neuer Version und rolle bei Bedarf zurück
Canary oder Health-Check bleibt rot Abhängigkeiten, Zeitlimits oder Secrets weichen von der Zielumgebung ab Prüfe Secrets, Netzwerkpfade und Schwellenwerte in exakt derselben Umgebung

Verwandte Leitfäden

  • Disaster Recovery für CAPTCHA-Lösung
  • Multi-Region-Architektur für CAPTCHA-Lösung
  • Circuit-Breaker-Muster für CAPTCHA-API-Aufrufe

Diskussionen (0)

Noch keine Kommentare.