لا ينبغي أن تعني ترقية خط أنابيب حل اختبار 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)
شارك في النقاش
سجّل الدخول لمشاركة رأيك.
تسجيل الدخوللا توجد تعليقات بعد.