Nâng worker giải CAPTCHA lên phiên bản mới mà không cần xin lịch downtime là hoàn toàn khả thi: chạy song song hai môi trường blue và green giống hệt nhau, đẩy traffic sang môi trường mới sau khi canary test đạt ngưỡng, rồi rollback tức thì nếu có sự cố.
Đây là cách nhiều đội scraping/QA vận hành hàng chục worker CaptchaAI 24/7 mà không cần chờ cửa sổ bảo trì.
Kiến trúc Blue-Green: hai môi trường, một traffic router
Một traffic router đứng trước hai nhóm worker gọi CaptchaAI API độc lập; chỉ một nhóm nhận traffic thật tại một thời điểm:
┌─────────────────────┐
[Scraper Clients] → │ Traffic Router │
└──────┬──────┬───────┘
│ │
Active│ │Standby
▼ ▼
┌───────┐ ┌───────┐
│ BLUE │ │ GREEN │
│Workers│ │Workers│
└───┬───┘ └───┬───┘
│ │
└────┬─────┘
▼
[CaptchaAI API]
Triển khai Blue-Green trong code
Python – Bộ định tuyến Blue-Green
BlueGreenRouter gói hai CaptchaWorkerPool (blue/green) và chỉ cho switch() khi canary_test() đạt ngưỡng pass:
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")
canary_test() chỉ trả True khi tỷ lệ pass vượt ngưỡng — tránh chuyển traffic sang một bản build lỗi.
JavaScript – Chuyển đổi Blue-Green tự động
Bản Node.js tự giám sát tỷ lệ lỗi sau khi chuyển traffic và tự rollback nếu vượt ngưỡng:
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));
monitorAfterSwitch() gọi rollback() ngay khi tỷ lệ lỗi vượt 20% trong cửa sổ theo dõi — không cần ai thức đêm bấm nút.
Luồng triển khai 5 bước
| Bước | Hành động | Ngưỡng rollback |
|---|---|---|
| 1 | Triển khai code mới vào môi trường chờ (standby) | Build lỗi |
| 2 | Chạy canary test trên môi trường chờ | Tỷ lệ pass < 80% |
| 3 | Chuyển traffic sang phiên bản mới | — |
| 4 | Giám sát tỷ lệ lỗi trong 5 phút | Tỷ lệ lỗi > 20% |
| 5 | Ngừng môi trường cũ | — |
Kinh nghiệm cắt traffic trong thực tế
- Đưa cả hai môi trường qua cùng một bộ health check trước khi cho traffic thật chạm vào stack mới.
- Chuyển traffic theo giai đoạn rõ ràng (ví dụ 10% → 50% → 100%), chỉ đi tiếp khi độ trễ và tỷ lệ lỗi ổn định ở mỗi bước.
- Rollback ngay khi vượt ngưỡng đã thống nhất trước — đừng chờ "theo dõi thêm chút nữa".
- Một đội QA outsourcing tại TP.HCM theo dõi giá Shopee/Lazada bằng ~40 worker song song thường chạy canary trên gói ADVANCE ($90/tháng, 50 thread) — dư thread cho cả hai môi trường cùng hoạt động, thay vì sát ngưỡng STANDARD ($30/tháng, 15 thread).
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Canary pass nhưng production fail | Task test quá đơn giản | Dùng task thực tế từ hàng đợi production |
| Rollback quá thường xuyên | Ngưỡng giám sát quá nhạy | Tăng ngưỡng lỗi; kéo dài soak time |
| Traffic split không rõ ràng | Còn request in-flight ở môi trường cũ | Chờ xử lý xong in-flight trước khi ngừng |
| Cả hai môi trường cùng unhealthy | Lỗi dependency dùng chung (network, API) | Dùng circuit breaker; không rollback vì hạ tầng chung |
Câu hỏi thường gặp
Cần bao nhiêu thread để chạy song song cả hai môi trường blue và green?
Không có con số cố định — cộng thread ổn định với phần dùng cho canary. Workload cần ~40 thread nên chọn gói dư, ví dụ ADVANCE ($90/tháng, 50 thread) thay vì sát ngưỡng STANDARD ($30/tháng, 15 thread).
Blue-Green và canary deployment khác nhau ở điểm nào?
Blue-Green chuyển 100% traffic cùng lúc sau canary pass. Canary deployment tăng dần traffic (1% → 10% → 50% → 100%) — an toàn hơn cho hệ thống lớn.
Có thể triển khai Blue-Green giải CAPTCHA chỉ với một server không?
Có, chạy blue và green như hai process/container riêng trên cùng máy, dùng reverse proxy (NGINX) chuyển traffic giữa hai cổng.
Ngưỡng tỷ lệ lỗi bao nhiêu thì nên rollback thay vì tiếp tục theo dõi?
Rollback khi tỷ lệ lỗi vượt 20% trong 5 phút giám sát. Với hệ thống quan trọng, hạ ngưỡng xuống 10%.
Triển khai Blue-Green có làm gián đoạn các task CAPTCHA đang xử lý dở không?
Không, nếu làm đúng: task cũ tiếp tục polling res.php đến khi có kết quả, task mới route sang môi trường mới. Chỉ ngừng môi trường cũ khi hàng đợi in-flight đã rỗng.
Bước tiếp theo
Dựng thử router Blue-Green ở trên với dữ liệu canary thật từ hàng đợi của bạn — không cần đợi cửa sổ bảo trì để nâng cấp worker. Lấy API key CaptchaAI của bạn và bắt đầu triển khai không downtime.
Hướng dẫn liên quan: