عامل حل CAPTCHA السليم يحتاج إلى ثلاث فحوصات منفصلة لا واحدة: الحيوية (هل العملية تعمل؟)، الجاهزية (هل يستطيع استقبال مهام جديدة؟)، والتبعية (هل الخدمات التي يعتمد عليها سليمة؟). حين تعرّض هذه الفحوصات على نقاط نهاية HTTP منفصلة، يستطيع موازن التحميل أو Kubernetes أن يميّز العامل المتعطّل عن العامل المشغول، فيعيد التوجيه أو يعيد التشغيل تلقائياً بدل أن يستمر في إرسال العمل إلى عملية ميتة.
المشكلة الجوهرية أن "الخدمة تستجيب" لا يعني "العامل صالح للإنتاج". في أنظمة حل CAPTCHA تحديداً، قد تبقى العملية ترد على طلبات HTTP بينما نفد الرصيد، أو انقطع الاتصال بالـ API، أو مرّت دقائق طويلة دون حل ناجح واحد. الفحص الصحي الجيد يقرأ هذه الإشارات الداخلية ويترجمها إلى رمز استجابة يفهمه المنسّق: 200 يعني "استمر"، و503 يعني "توقف عن الإرسال إلى هذا العامل".
لماذا لا يكفي أن يكون العامل "حيًا"؟
تخيّل عامل استخراج بيانات يعمل ضمن أسطول من عشر حاويات خلف موازن تحميل. إحدى الحاويات استنفدت رصيد الحساب قبل ساعة، لكن عمليتها لا تزال تعمل وترد على المنفذ 8080. بدون فحص جاهزية، سيظل موازن التحميل يوزّع نحو 10% من كل الطلبات على هذه الحاوية، وكل طلب يفشل بصمت ويعود بمهلة انتهاء. النتيجة: انخفاض في معدل الحل الكلي يصعب تفسيره، لأن العامل من الخارج "يعمل".
الفحص الصحي يحوّل هذا الفشل الصامت إلى إشارة صريحة. حين يبلّغ العامل عن نفسه بأنه not_ready، يسحبه موازن التحميل من دورة التوزيع خلال ثوانٍ، فيتركّز العمل على الحاويات السليمة ويرتفع معدل النجاح مباشرةً — دون تدخّل يدوي وقت الحادثة.
الفحوصات الثلاثة التي تحتاجها
| الفحص | السؤال الذي يجيب عنه | الإجراء عند الفشل |
|---|---|---|
| الحيوية (liveness) | هل العملية ما زالت تعمل؟ | أعد تشغيل الحاوية |
| الجاهزية (readiness) | هل يستطيع قبول عمل جديد؟ | أوقف توجيه حركة المرور إليه |
| التبعية (dependency) | هل خدمات المنبع سليمة؟ | تدهور برشاقة بدل الانهيار |
الفكرة أن كل فحص يقود إلى قرار مختلف. فشل الحيوية يعني عملية مجمّدة يجب استبدالها، بينما فشل الجاهزية مؤقت غالباً — رصيد منخفض أو موجة أخطاء متتالية — ولا يستدعي إعادة تشغيل، بل مجرد إبعاد مؤقت. الخلط بينهما هو أشيع خطأ في إعداد المراقبة.
أي فحص تبدأ به حسب نوع النشر؟
| نوع العامل | أهم فحص يبدأ به | لماذا؟ |
|---|---|---|
| عامل واحد خلف supervisor بسيط | الحيوية + الجاهزية | يمنع إرسال العمل إلى عملية معلّقة أو خاملة |
| عمّال على Kubernetes | الحيوية + الجاهزية + التبعية | التوجيه الآلي وإعادة التشغيل يعتمدان على التمييز بينها |
| عمّال دفعات ضمن قائمة انتظار | الجاهزية قبل الحيوية | العملية قد تكون حية لكنها غير مؤهلة لاستقبال دُفعة جديدة |
إذا كنت تشغّل حاوية واحدة فقط، يكفيك فحصا الحيوية والجاهزية. أما مع أسطول على Kubernetes، فأضف فحص التبعية لأنه يمنحك حالة degraded وسطى بين السليم والمنهار، وهي أدق قرار تحتاجه عند تذبذب الشبكة.
Python: نقاط النهاية الصحية في Flask
المثال التالي يبني ثلاث نقاط نهاية فوق Flask ويتتبّع مقاييس العامل داخل كائن WorkerHealth محمي بقفل. لاحظ أن استعلام الرصيد مخزّن مؤقتاً لمدة 60 ثانية، حتى لا يستدعي كل طلب فحص واجهة CaptchaAI ويبطّئها:
import requests
import time
import threading
from flask import Flask, jsonify
from dataclasses import dataclass, field
API_KEY = "YOUR_API_KEY"
RESULT_URL = "https://ocr.captchaai.com/res.php"
app = Flask(__name__)
@dataclass
class WorkerHealth:
"""Tracks worker health metrics."""
started_at: float = field(default_factory=time.monotonic)
last_solve_at: float = 0.0
total_solved: int = 0
total_failed: int = 0
consecutive_failures: int = 0
balance: float | None = None
balance_checked_at: float = 0.0
_lock: threading.Lock = field(default_factory=threading.Lock)
def record_success(self):
with self._lock:
self.total_solved += 1
self.last_solve_at = time.monotonic()
self.consecutive_failures = 0
def record_failure(self):
with self._lock:
self.total_failed += 1
self.consecutive_failures += 1
@property
def success_rate(self) -> float:
total = self.total_solved + self.total_failed
return self.total_solved / total if total > 0 else 1.0
@property
def seconds_since_last_solve(self) -> float:
if self.last_solve_at == 0:
return time.monotonic() - self.started_at
return time.monotonic() - self.last_solve_at
health = WorkerHealth()
# Thresholds
MAX_CONSECUTIVE_FAILURES = 10
MAX_SECONDS_WITHOUT_SOLVE = 600 # 10 minutes
MIN_BALANCE = 1.0
def check_balance() -> float | None:
"""Check CaptchaAI balance."""
now = time.monotonic()
# Cache balance for 60 seconds
if health.balance is not None and now - health.balance_checked_at < 60:
return health.balance
try:
resp = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "getbalance", "json": 1,
}, timeout=10).json()
health.balance = float(resp.get("request", 0))
health.balance_checked_at = now
return health.balance
except Exception:
return health.balance # Return cached value on error
@app.route("/health/live")
def liveness():
"""Liveness probe — is the process responsive?"""
return jsonify({"status": "ok", "uptime_s": int(time.monotonic() - health.started_at)}), 200
@app.route("/health/ready")
def readiness():
"""Readiness probe — can the worker accept tasks?"""
issues = []
# Check consecutive failures
if health.consecutive_failures >= MAX_CONSECUTIVE_FAILURES:
issues.append(f"consecutive_failures={health.consecutive_failures}")
# Check time since last solve
if health.total_solved > 0 and health.seconds_since_last_solve > MAX_SECONDS_WITHOUT_SOLVE:
issues.append(f"no_solve_for={int(health.seconds_since_last_solve)}s")
# Check balance
balance = check_balance()
if balance is not None and balance < MIN_BALANCE:
issues.append(f"low_balance=${balance:.2f}")
if issues:
return jsonify({
"status": "not_ready",
"issues": issues,
"stats": {
"solved": health.total_solved,
"failed": health.total_failed,
"success_rate": round(health.success_rate, 3),
},
}), 503
return jsonify({
"status": "ready",
"stats": {
"solved": health.total_solved,
"failed": health.total_failed,
"success_rate": round(health.success_rate, 3),
"balance": balance,
},
}), 200
@app.route("/health/dependencies")
def dependencies():
"""Check upstream dependencies."""
checks = {}
# CaptchaAI API reachability
try:
resp = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "getbalance", "json": 1,
}, timeout=10)
checks["captchaai_api"] = {
"status": "ok" if resp.status_code == 200 else "degraded",
"response_ms": int(resp.elapsed.total_seconds() * 1000),
}
except Exception as e:
checks["captchaai_api"] = {"status": "down", "error": str(e)}
all_ok = all(c["status"] == "ok" for c in checks.values())
return jsonify({
"status": "ok" if all_ok else "degraded",
"checks": checks,
}), 200 if all_ok else 503
# --- Worker loop (runs in background) ---
def worker_loop():
"""Simulated CAPTCHA solving worker."""
while True:
try:
# ... solve CAPTCHA logic ...
health.record_success()
except Exception:
health.record_failure()
time.sleep(1)
threading.Thread(target=worker_loop, daemon=True).start()
JavaScript: نقاط النهاية الصحية في Express
نفس المنطق منقول إلى Express، مع الاحتفاظ بالمقاييس في كائن health وحساب successRate عند الطلب. لاحظ أن فحص الحيوية هنا لا يستدعي أي خدمة خارجية إطلاقاً، لأنه يجب أن يجيب فوراً ليثبت أن العملية نفسها لم تتجمّد:
const express = require("express");
const API_KEY = "YOUR_API_KEY";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
const app = express();
const health = {
startedAt: Date.now(),
lastSolveAt: 0,
totalSolved: 0,
totalFailed: 0,
consecutiveFailures: 0,
balance: null,
balanceCheckedAt: 0,
recordSuccess() {
this.totalSolved++;
this.lastSolveAt = Date.now();
this.consecutiveFailures = 0;
},
recordFailure() {
this.totalFailed++;
this.consecutiveFailures++;
},
get successRate() {
const total = this.totalSolved + this.totalFailed;
return total > 0 ? this.totalSolved / total : 1;
},
};
async function checkBalance() {
if (health.balance !== null && Date.now() - health.balanceCheckedAt < 60000) {
return health.balance;
}
try {
const url = `${RESULT_URL}?key=${API_KEY}&action=getbalance&json=1`;
const resp = await (await fetch(url)).json();
health.balance = parseFloat(resp.request);
health.balanceCheckedAt = Date.now();
return health.balance;
} catch {
return health.balance;
}
}
app.get("/health/live", (req, res) => {
res.json({ status: "ok", uptimeMs: Date.now() - health.startedAt });
});
app.get("/health/ready", async (req, res) => {
const issues = [];
if (health.consecutiveFailures >= 10) {
issues.push(`consecutive_failures=${health.consecutiveFailures}`);
}
if (health.totalSolved > 0) {
const silentMs = Date.now() - health.lastSolveAt;
if (silentMs > 600_000) {
issues.push(`no_solve_for=${Math.round(silentMs / 1000)}s`);
}
}
const balance = await checkBalance();
if (balance !== null && balance < 1.0) {
issues.push(`low_balance=$${balance.toFixed(2)}`);
}
const stats = {
solved: health.totalSolved,
failed: health.totalFailed,
successRate: Math.round(health.successRate * 1000) / 1000,
balance,
};
if (issues.length > 0) {
return res.status(503).json({ status: "not_ready", issues, stats });
}
res.json({ status: "ready", stats });
});
app.get("/health/dependencies", async (req, res) => {
const checks = {};
try {
const start = Date.now();
const url = `${RESULT_URL}?key=${API_KEY}&action=getbalance&json=1`;
const resp = await fetch(url);
checks.captchaaiApi = {
status: resp.ok ? "ok" : "degraded",
responseMs: Date.now() - start,
};
} catch (e) {
checks.captchaaiApi = { status: "down", error: e.message };
}
const allOk = Object.values(checks).every((c) => c.status === "ok");
res.status(allOk ? 200 : 503).json({
status: allOk ? "ok" : "degraded",
checks,
});
});
app.listen(8080, () => console.log("Health server on :8080"));
إعداد Kubernetes للفحوصات الصحية
بعد تعريف نقاط النهاية، يربطها Kubernetes بمسبار الحيوية ومسبار الجاهزية. المفتاح هنا هو initialDelaySeconds: امنح العامل وقتاً كافياً للإقلاع قبل أول فحص، وإلا أعيد تشغيله أثناء بدء التشغيل الطبيعي:
apiVersion: apps/v1
kind: Deployment
metadata:
name: captcha-worker
spec:
replicas: 3
template:
spec:
containers:
- name: worker
image: captcha-worker:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
رموز الاستجابة للفحص الصحي
| نقطة النهاية | 200 | 503 |
|---|---|---|
/health/live |
العملية سريعة الاستجابة | العملية مجمّدة — أعد تشغيلها |
/health/ready |
يمكن قبول العمل | توقّف عن إرسال المهام |
/health/dependencies |
جميع التبعيات سليمة | خدمات المنبع متدهورة |
القاعدة العملية: احجز رمز 503 لحالات "لا ترسل لي عملاً الآن"، واحتفظ بحالة degraded للحالات الوسطى التي لا تبرّر سحب العامل بالكامل. هذا التدرّج يمنع تأرجح موازن التحميل بين تشغيل العامل وإيقافه عند كل تذبذب عابر في الشبكة.
استكشاف الأخطاء وحلولها
| المشكلة | السبب | الإجراء |
|---|---|---|
العامل يمرّ في live لكنه يفشل دائماً في ready |
العملية حية لكن الرصيد منخفض أو الأخطاء المتتالية مرتفعة | راجع شروط الجاهزية بدل الاعتماد على الحيوية وحدها |
| الحاوية تُعاد تشغيلها كثيراً | فحص الحيوية صارم أكثر من اللازم أو بطيء الاستجابة | زد initialDelaySeconds أو خفّض شرط الفشل المؤقت |
| العامل لا يستقبل مهامًا رغم أنه يعمل | موازن الحمل أو Kubernetes يعتبره غير جاهز | راجع استجابة /health/ready والعتبات المرتبطة بها |
dependencies يفشل مؤقتًا بسبب بطء خارجي |
المهلة قصيرة أو التبعية متذبذبة | استخدم حالة degraded بدل إعلان الفشل الكامل مباشرة |
أسئلة شائعة
ما الفرق العملي بين الحيوية والجاهزية والتبعية؟
الحيوية تجيب عن سؤال واحد: هل العملية مجمّدة؟ إن فشلت، الحل الوحيد هو إعادة التشغيل. الجاهزية تسأل: هل يستطيع هذا العامل قبول مهمة الآن؟ وقد يفشل مؤقتاً دون أن يكون معطوباً. أما التبعية فتراقب الخدمات المنبع مثل واجهة CaptchaAI، وتمنحك حالة degraded وسطى بدل قرار ثنائي حاد بين سليم ومنهار.
لماذا يُعاد تشغيل الحاوية باستمرار رغم أن العامل يعمل؟
غالباً لأن مسبار الحيوية أضيق من اللازم: مهلة قصيرة، أو initialDelaySeconds صغير يجعل Kubernetes يفحص قبل أن يكمل العامل الإقلاع. ابدأ بزيادة initialDelaySeconds ثم failureThreshold، واحرص على ألا يستدعي فحص الحيوية أي خدمة خارجية حتى يجيب فوراً.
كيف أتصرف عند انخفاض الرصيد في CaptchaAI أثناء التشغيل؟
اجعل انخفاض الرصيد يُسقط العامل في ready فقط، لا في live، حتى يُسحب من التوزيع دون إعادة تشغيل لا فائدة منها. تذكّر أن خطط CaptchaAI تُحسب بعدد الـ threads المتزامنة مع حلول غير محدودة لكل thread، لذا "الرصيد المنخفض" هنا مؤشر على قرب انتهاء اشتراكك لا على تسعير بالحل الواحد؛ جدّد الخطة ليعود العامل إلى الجاهزية تلقائياً.
هل أعرّض نقطة /metrics منفصلة عن نقاط الفحص الصحي؟
نعم. نقاط الفحص الصحي تعطي قراراً لحظياً (200 أو 503) للمنسّق، بينما /metrics بصيغة Prometheus تجمّع الاتجاهات عبر الأسطول لعرضها على لوحات المتابعة. افصل بينهما: الأولى للتوجيه الآلي، والثانية للتحليل والتنبيهات طويلة المدى.
الخطوات التالية
- البدء السريع مع CaptchaAI: حلّ أول كابتشا في دقائق
- حلّ reCAPTCHA v2 عبر الـ API خطوة بخطوة
- حلّ Cloudflare Turnstile عبر واجهة API
- حلّ GeeTest v3 باستخدام API