DevOps & Scaling

النشر باللونين الأزرق والأخضر للبنية الأساسية لحل اختبار CAPTCHA

لا ينبغي أن تعني ترقية خط أنابيب حل اختبار CAPTCHA الخاص بك التوقف عن العمل. تتيح لك عمليات النشر ذات اللون الأزرق والأخضر تشغيل بيئتين متطابقتين - تحويل حركة المرور إلى الإصدار الجديد، والتحقق من أنه يعمل، والتراجع على الفور في حالة تعطل شيء ما.

هذا النمط مناسب عندما تكون خدمة الحل جزءًا من مسار إنتاجي لا يحتمل الانقطاع، مثل عمّال scraping طويلة التشغيل أو مهام معالجة تعتمد على معدل نجاح مستقر. أما إذا كنت تشغّل عاملًا واحدًا أو بيئة صغيرة جدًا، فقد يكون النشر المتدرج أو نافذة صيانة قصيرة أبسط من إدارة بيئتين كاملتين.

ملاحظة: تستند الأرقام الواردة في هذه المقالة إلى اختبارات داخلية في ظروف محددة. قد تختلف النتائج حسب نوع CAPTCHA والموقع وبيئة التشغيل. يُنصح بإجراء قياسات مستقلة في بيئتك قبل الاعتماد على هذه الأرقام.

متى تختار الأزرق/الأخضر؟

الحالة هل يناسبها هذا النمط؟ لماذا؟
عمّال إنتاج متعددة خلف موازن حمل نعم يمكنك تحويل الحركة بسرعة والتراجع فورًا
فريق يحتاج اختبار canary قبل التحويل الكامل نعم يسهّل التحقق من الإصدار الجديد على بيئة standby
خدمة صغيرة داخلية قليلة الاستخدام غالبًا لا كلفة بيئتين كاملتين قد تكون أكبر من الفائدة
نشرات تتطلب تغيير قاعدة بيانات غير متوافق نعم، لكن مع خطة ترحيل منفصلة التحويل وحده لا يحل مشكلة التوافق مع البيانات

العمارة الزرقاء والخضراء

                    ┌─────────────────────┐
[Scraper Clients] → │   Traffic Router    │
                    └──────┬──────┬───────┘
                           │      │
                     Active│      │Standby
                           ▼      ▼
                    ┌───────┐  ┌───────┐
                    │ BLUE  │  │ GREEN │
                    │Workers│  │Workers│
                    └───┬───┘  └───┬───┘
                        │          │
                        └────┬─────┘
                             ▼
                    [CaptchaAI API]

التنفيذ

بايثون – جهاز التوجيه الأزرق والأخضر

import os
import time
import threading
import requests

API_KEY = os.environ["CAPTCHAAI_API_KEY"]


class CaptchaWorkerPool:
    """Represents one environment (blue or green)."""

    def __init__(self, name, config):
        self.name = name
        self.config = config
        self.session = requests.Session()
        self.tasks_solved = 0
        self.errors = 0
        self.healthy = True

    def solve(self, task):
        resp = self.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:
            self.errors += 1
            return {"error": data.get("request")}

        captcha_id = data["request"]
        for _ in range(60):
            time.sleep(5)
            result = self.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:
                self.tasks_solved += 1
                return {"solution": result["request"]}
            if result.get("request") != "CAPCHA_NOT_READY":
                self.errors += 1
                return {"error": result.get("request")}

        self.errors += 1
        return {"error": "TIMEOUT"}

    @property
    def error_rate(self):
        total = self.tasks_solved + self.errors
        return self.errors / total if total > 0 else 0.0

    @property
    def stats(self):
        return {
            "name": self.name,
            "solved": self.tasks_solved,
            "errors": self.errors,
            "error_rate": round(self.error_rate, 4),
            "healthy": self.healthy
        }


class BlueGreenRouter:
    def __init__(self, blue_config, green_config):
        self.blue = CaptchaWorkerPool("blue", blue_config)
        self.green = CaptchaWorkerPool("green", green_config)
        self.active = self.blue
        self.standby = self.green
        self.lock = threading.Lock()

    def solve(self, task):
        """Route task to the active environment."""
        with self.lock:
            pool = self.active
        return pool.solve(task)

    def switch(self):
        """Swap active and standby environments."""
        with self.lock:
            self.active, self.standby = self.standby, self.active
            print(f"Switched: {self.active.name} is now ACTIVE")
        return self.active.name

    def rollback(self):
        """Switch back to the previous environment."""
        return self.switch()

    def canary_test(self, test_tasks, threshold=0.9):
        """Run test tasks on standby before switching."""
        successes = 0
        for task in test_tasks:
            result = self.standby.solve(task)
            if "solution" in result:
                successes += 1

        success_rate = successes / len(test_tasks) if test_tasks else 0
        passed = success_rate >= threshold
        print(
            f"Canary test: {successes}/{len(test_tasks)} "
            f"({success_rate:.0%}) — {'PASS' if passed else 'FAIL'}"
        )
        return passed

    @property
    def status(self):
        return {
            "active": self.active.stats,
            "standby": self.standby.stats
        }


# Usage
router = BlueGreenRouter(
    blue_config={"version": "1.2.0", "workers": 4},
    green_config={"version": "1.3.0", "workers": 4}
)

# Canary test before switching
test_tasks = [
    {"sitekey": "6Le-wvkS...", "pageurl": "https://example.com/test"}
]

if router.canary_test(test_tasks, threshold=0.8):
    router.switch()
    print(f"Now active: {router.status['active']['name']}")
else:
    print("Canary failed — staying on current environment")

JavaScript - المحول الآلي باللونين الأزرق والأخضر

const axios = require("axios");

const API_KEY = process.env.CAPTCHAAI_API_KEY;

class BlueGreenDeployment {
  constructor() {
    this.environments = {
      blue: { name: "blue", version: null, solved: 0, errors: 0 },
      green: { name: "green", version: null, solved: 0, errors: 0 },
    };
    this.activeEnv = "blue";
  }

  get active() {
    return this.environments[this.activeEnv];
  }
  get standby() {
    return this.environments[this.activeEnv === "blue" ? "green" : "blue"];
  }

  async deploy(version, config = {}) {
    const target = this.standby;
    target.version = version;
    target.solved = 0;
    target.errors = 0;

    console.log(`Deployed v${version} to ${target.name} (standby)`);

    // Run canary checks
    const canaryPassed = await this.canaryCheck(config.canaryTasks || []);
    if (!canaryPassed && config.canaryTasks?.length > 0) {
      console.log("Canary check failed — aborting deployment");
      return { success: false, reason: "canary_failed" };
    }

    // Switch traffic
    this.activeEnv = target.name;
    console.log(`Switched traffic to ${target.name} (v${version})`);

    // Monitor for rollback
    if (config.monitorDuration) {
      const stable = await this.monitorAfterSwitch(config.monitorDuration);
      if (!stable) {
        this.rollback();
        return { success: false, reason: "post_deploy_errors" };
      }
    }

    return { success: true, active: this.activeEnv };
  }

  async canaryCheck(tasks) {
    if (tasks.length === 0) return true;

    let successes = 0;
    for (const task of tasks) {
      try {
        await this.solveCaptcha(task);
        successes++;
      } catch (err) {
        console.log(`Canary task failed: ${err.message}`);
      }
    }

    const rate = successes / tasks.length;
    console.log(`Canary: ${successes}/${tasks.length} (${(rate * 100).toFixed(0)}%)`);
    return rate >= 0.8;
  }

  async monitorAfterSwitch(durationMs) {
    const start = Date.now();
    const checkInterval = 10000;

    while (Date.now() - start < durationMs) {
      await new Promise((r) => setTimeout(r, checkInterval));
      const errorRate = this.active.errors /
        Math.max(1, this.active.solved + this.active.errors);

      if (errorRate > 0.2) {
        console.log(`Error rate ${(errorRate * 100).toFixed(1)}% — triggering rollback`);
        return false;
      }
    }
    return true;
  }

  rollback() {
    const previous = this.activeEnv === "blue" ? "green" : "blue";
    console.log(`Rolling back: ${this.activeEnv} → ${previous}`);
    this.activeEnv = previous === "blue" ? "blue" : "green";
  }

  async solveCaptcha(task) {
    const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
      params: {
        key: API_KEY,
        method: "userrecaptcha",
        googlekey: task.sitekey,
        pageurl: task.pageurl,
        json: 1,
      },
    });

    if (submitResp.data.status !== 1) {
      this.active.errors++;
      throw new 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) {
        this.active.solved++;
        return pollResp.data.request;
      }
      if (pollResp.data.request !== "CAPCHA_NOT_READY") {
        this.active.errors++;
        throw new Error(pollResp.data.request);
      }
    }
    this.active.errors++;
    throw new Error("TIMEOUT");
  }
}

// Deploy new version with canary and monitoring
const deployer = new BlueGreenDeployment();

deployer
  .deploy("1.3.0", {
    canaryTasks: [
      { sitekey: "6Le-wvkS...", pageurl: "https://example.com/test" },
    ],
    monitorDuration: 60000, // Monitor for 1 minute after switch
  })
  .then((result) => console.log("Deploy result:", result));

سير عمل النشر

خطوة العمل مشغل التراجع
1 نشر التعليمات البرمجية الجديدة في وضع الاستعداد بناء الفشل
2 تشغيل اختبارات الكناري في وضع الاستعداد معدل النجاح < 80%
3 تبديل حركة المرور إلى الإصدار الجديد
4 مراقبة معدل الخطأ (5 دقائق) معدل الخطأ > 20%
5 الاستغناء عن البيئة القديمة

استكشاف الأخطاء وإصلاحها

المشكلة السبب الإجراء
البيئة الجديدة تجتاز الاختبارات لكن تفشل بعد التحويل بيانات الإعداد أو الأسرار تختلف بين البيئتين قارن المتغيرات والاتصال الخارجي قبل تحويل الحركة
نجاح اختبارات الكناري لكن ارتفاع الأخطاء لاحقًا حجم الكناري صغير ولا يمثل الحمل الفعلي أضف مجموعة مهام أكثر تنوعًا ومراقبة بعد التحويل لعدة دقائق
التراجع يعيد الحركة لكن الأعطال تستمر السبب مشترك بين الأزرق والأخضر مثل مفتاح API أو الوكيل افصل بين عيوب الإصدار وعيوب البنية التحتية المشتركة
كلفة البنية ارتفعت كثيرًا ترك بيئة standby كاملة العمل طوال الوقت استخدم standby مصغرة خارج أوقات النشر، ثم وسّعها فقط قبل التحويل

الخطوات التالية

النقاشات (0)

لا توجد تعليقات بعد.

مقالات ذات صلة

DevOps & Scaling حل CAPTCHA بدون خوادم باستخدام AWS Lambda وCaptchaAI
دليل تشغيلي لـ حل CAPTCHA بدون خوادم باستخدام AWS Lambda وCaptcha AI يغطّي قرارات البنية، والاعتمادية، والمراقبة، وأنماط الأتمتة المناسبة لتشغيل Captcha AI في ب...

دليل تشغيلي لـ حل CAPTCHA بدون خوادم باستخدام AWS Lambda وCaptcha AI يغطّي قرارات البنية، والاعتمادية، والمراق...

Apr 20, 2026
DevOps & Scaling نشر عمّال CaptchaAI باستخدام Ansible
دليل تشغيلي لـ نشر عمّال Captcha AI باستخدام Ansible يغطّي قرارات البنية، والاعتمادية، والمراقبة، وأنماط الأتمتة المناسبة لتشغيل Captcha AI في بيئات الإنتاج.

دليل تشغيلي لـ نشر عمّال Captcha AI باستخدام Ansible يغطّي قرارات البنية، والاعتمادية، والمراقبة، وأنماط الأتم...

Apr 17, 2026
DevOps & Scaling Azure Functions + CaptchaAI: تكامل سحابي بدون خوادم
دليل تشغيلي لـ Azure Functions + Captcha AI: تكامل سحابي بدون خوادم يغطّي قرارات البنية، والاعتمادية، والمراقبة، وأنماط الأتمتة المناسبة لتشغيل Captcha AI في بي...

دليل تشغيلي لـ Azure Functions + Captcha AI: تكامل سحابي بدون خوادم يغطّي قرارات البنية، والاعتمادية، والمراقب...

Apr 21, 2026