Nâng cấp quy trình giải CAPTCHA của bạn không có nghĩa là ngừng hoạt động. Triển khai xanh lam cho phép bạn chạy hai môi trường giống hệt nhau — chuyển lưu lượng truy cập sang phiên bản mới, xác minh rằng nó hoạt động và quay lại ngay lập tức nếu có sự cố.
Kiến trúc xanh lam
┌─────────────────────┐
[Scraper Clients] → │ Traffic Router │
└──────┬──────┬───────┘
│ │
Active│ │Standby
▼ ▼
┌───────┐ ┌───────┐
│ BLUE │ │ GREEN │
│Workers│ │Workers│
└───┬───┘ └───┬───┘
│ │
└────┬─────┘
▼
[CaptchaAI API]
Thực hiện
Python – Bộ định tuyến xanh lam
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 – Trình chuyển đổi xanh lam-xanh tự độ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));
Quy trình triển khai
| Bước | hành động | Kích hoạt khôi phục |
|---|---|---|
| 1 | Triển khai mã mới vào chế độ chờ | Xây dựng thất bại |
| 2 | Chạy thử nghiệm canary ở chế độ chờ | Tỷ lệ thành công < 80% |
| 3 | Chuyển lưu lượng truy cập sang phiên bản mới | — |
| 4 | Giám sát tỷ lệ lỗi (5 phút) | Tỷ lệ lỗi > 20% |
| 5 | Ngừng hoạt động môi trường cũ | — |
Sổ tay cắt giảm giao thông
- Giữ các nhóm màu xanh lam và xanh lục trên các đầu dò tình trạng được phản chiếu trước khi gửi bất kỳ lưu lượng truy cập trực tiếp nào đến ngăn xếp mới.
- Thay đổi lưu lượng truy cập theo các giai đoạn rõ ràng và yêu cầu độ trễ ổn định cùng với việc kiểm tra tỷ lệ từ chối trước mỗi bước.
- Khôi phục ngay lập tức nếu giải quyết được độ trễ, tỷ lệ từ chối mục tiêu hoặc độ sâu hàng đợi vượt quá ngưỡng đã thỏa thuận.
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Canary vượt qua nhưng sản xuất thất bại | Nhiệm vụ kiểm tra quá đơn giản | Sử dụng các nhiệm vụ thực tế từ hàng đợi sản xuất |
| Quay lại thường xuyên | Ngưỡng giám sát tích cực | Tăng ngưỡng lỗi; thời gian ngâm lâu hơn |
| Phân chia lưu lượng không rõ ràng trong quá trình chuyển đổi | Yêu cầu trong chuyến bay trên môi trường cũ | Chờ cho các nhiệm vụ trên chuyến bay hoàn tất trước khi ngừng hoạt động |
| Cả hai môi trường đều trở nên không lành mạnh | Lỗi phụ thuộc được chia sẻ (mạng, API) | Bộ ngắt mạch; không quay trở lại vì các vấn đề về cơ sở hạ tầng |
Câu hỏi thường gặp
Thử nghiệm canary nên chạy trong bao lâu?
Chạy ít nhất 10 giải CAPTCHA thực trên môi trường chờ. Đối với các hệ thống quan trọng, hãy chạy phần trăm lưu lượng sản xuất (5–10%) qua chế độ chờ trong 10 phút trước khi chuyển đổi hoàn toàn.
Tôi có thể thực hiện màu xanh lam với một máy chủ không?
Có - chạy màu xanh lam và xanh lục dưới dạng các quy trình hoặc vùng chứa riêng biệt trên cùng một máy chủ. Sử dụng proxy ngược (NGINX) để chuyển đổi lưu lượng giữa các cổng.
Sự khác biệt giữa triển khai xanh dương và xanh hoàng yến là gì?
Màu xanh lam chuyển 100% lưu lượng truy cập cùng một lúc. Canary tăng dần lưu lượng truy cập sang phiên bản mới (1% -> 10% -> 50% -> 100%). Màu xanh lam đơn giản hơn; canary an toàn hơn cho các hệ thống quy mô lớn.
Các bước tiếp theo
Triển khai với sự tự tin —lấy khóa API CaptchaAI của bạnvà thiết lập triển khai không có thời gian ngừng hoạt động.
Hướng dẫn liên quan:
- Kiến trúc đa vùng
- Chuyển đổi dự phòng có tính sẵn sàng cao
- Giải quyết trong vùng chứa Docker