[{"data":1,"prerenderedAt":19},["ShallowReactive",2],{"blog:post:vi:thiet-ke-circuit-breaker-proxy-pool":3},{"slug":4,"lang":5,"title":6,"summary":7,"date":8,"tags":9,"tag_slugs":14,"thumbnail_url":15,"translations":16,"body":17,"asset_base":18},"thiet-ke-circuit-breaker-proxy-pool","vi","Thiết kế circuit breaker cho proxy pool khi nhà cung cấp gặp sự cố","Khi một nhà cung cấp proxy tăng độ trễ hoặc lỗi, xoay IP đơn thuần chỉ làm lỗi lan rộng. Hướng dẫn thiết kế circuit breaker, backoff và chuyển tuyến có trọng số để giữ scraper ổn định.","2026-09-20",[10,11,12,13],"proxy","mang","python","quan-sat",[10,11,12,13],"https://blog-api.ro-proxy.com/api/blog/posts/thiet-ke-circuit-breaker-proxy-pool/thumbnail.svg?lang=vi",[5],"## Vì sao xoay IP không phải là circuit breaker\n\nProxy pool không chỉ là một danh sách địa chỉ để luân phiên. Khi một nhà cung cấp gặp sự cố, việc tiếp tục chọn IP từ nhóm đó chỉ khiến lỗi được phân phối nhanh hơn. Timeout, DNS, TLS handshake hoặc lỗi gateway có thể ảnh hưởng hàng loạt request thay vì chỉ làm chậm một vài địa chỉ.\n\nGiả sử scraper SEO chạy 12.000 lượt kiểm tra mỗi ngày. Provider A phụ trách 40% lưu lượng, nhưng sau một thay đổi mạng, p95 latency tăng từ 400 ms lên 3,8 giây và tỷ lệ 502 đạt 35%. Nếu bộ xoay IP không nhận biết tình trạng này, 40% lưu lượng vẫn được gửi vào provider A trong nhiều phút. Kết quả là dữ liệu chậm, chi phí tăng và các provider khỏe hơn bị quá tải khi nhận lưu lượng chuyển hướng.\n\nCircuit breaker giúp hệ thống tạm ngừng sử dụng một upstream bị suy giảm, thay vì coi mọi request đều có cơ hội như nhau. Mục tiêu không phải là né mọi lỗi 4xx, mà là nhận diện nhanh các vấn đề của mạng, DNS, TLS và gateway proxy.\n\n### Phân loại lỗi trước khi mở mạch\n\nTrước khi thay đổi trạng thái provider, hãy phân loại nguyên nhân:\n\n- **Lỗi mạng:** kết nối timeout, DNS không phân giải, TCP reset hoặc TLS handshake thất bại.\n- **Lỗi gateway proxy:** 408, 502, 503, 504 hoặc 429 đến từ chính nhà cung cấp proxy.\n- **Phản hồi từ trang đích:** 403, 429 và 404 thường thuộc chính sách hoặc trạng thái của trang đích, không tự động chứng minh provider bị hỏng.\n- **Phản hồi hợp lệ nhưng chậm:** request thành công nhưng vượt ngưỡng latency đã định.\n\nĐừng mở circuit chỉ vì nhận được 403. 403 có thể là yêu cầu xác thực, giới hạn theo vùng hoặc quy tắc của trang đích. Ngược lại, 502 từ gateway và timeout lặp lại trên nhiều IP thuộc cùng provider là tín hiệu mạnh hơn. Nếu có thể, hãy ghi nhận nguồn phản hồi qua header, log của provider hoặc lớp proxy trung gian.\n\n### Ba trạng thái cần có\n\nMột circuit breaker đáng tin cậy nên có ba trạng thái:\n\n1. **Closed:** Provider hoạt động bình thường và nhận lưu lượng theo trọng số.\n2. **Open:** Provider bị tạm loại. Không gửi request mới trong thời gian làm mát, trừ các probe kiểm tra.\n3. **Half-open:** Gửi một số request thử nghiệm hạn chế. Chỉ đóng circuit khi mẫu probe thành công và đáp ứng latency.\n\nDùng cửa sổ trượt 20 đến 30 request thay vì đếm lỗi vĩnh viễn. Như vậy, một sự cố cũ không giữ provider ở trạng thái open indefinitely. Đồng thời, đặt số mẫu tối thiểu, chẳng hạn 10 request, để tránh mở mạch do một hai lỗi ngẫu nhiên.\n\nNên cấu hình breaker theo provider, khu vực và giao thức. Một provider datacenter ở Mỹ có thể khỏe trong khi nhóm mobile châu Âu gặp lỗi. Nếu gộp tất cả về một trạng thái chung, hệ thống sẽ либо bỏ qua vùng lành либо loại bỏ cả provider khi chỉ một segment bị hỏng.\n\n### Đặt ngưỡng thực tế\n\nMột cấu hình khởi đầu có thể là:\n\n- Cửa sổ: 20 đến 30 request.\n- Mẫu tối thiểu: 10 request.\n- Tỷ lệ lỗi gateway: từ 25% đến 35%.\n- p95 latency: lớn hơn 1 giây trong hai cửa sổ liên tiếp.\n- Thời gian open: 30 đến 60 giây.\n- Probe half-open: 3 đến 5 request.\n\nCác con số này không phải hằng số phổ quát. Hãy dựa vào SLA, loại proxy và hành vi của trang đích. Residential proxy có thể có độ trễ biến động cao hơn datacenter; mobile proxy có thể cần thời gian kết nối dài hơn. Quan trọng là đo trước khi chọn ngưỡng, rồi điều chỉnh theo phần trăm lỗi thật thay vì một ngưỡng cảm tính.\n\n### Một mẫu circuit breaker bằng Python\n\nĐoạn dưới đây minh họa trạng thái theo provider. Hàm `ok` đang coi các mã gateway là lỗi upstream; trong hệ thống thực tế, hãy phân biệt thêm header và metadata do provider trả về.\n\n```python\nimport random\nimport time\nfrom collections import deque\nfrom dataclasses import dataclass, field\n\nimport requests\n\n\n@dataclass\nclass ProviderState:\n    window: deque = field(default_factory=lambda: deque(maxlen=30))\n    state: str = 'closed'\n    opened_at: float = 0.0\n    half_open_used: int = 0\n    min_samples: int = 10\n    error_ratio: float = 0.30\n    open_seconds: float = 30.0\n    latency_limit_ms: float = 1000.0\n\n    def record(self, ok, latency_ms):\n        self.window.append((ok, latency_ms))\n\n        if self.state == 'half_open':\n            self.half_open_used += 1\n            if not ok or latency_ms > self.latency_limit_ms:\n                self.state = 'open'\n                self.opened_at = time.time()\n                return\n            if self.half_open_used >= 3:\n                self.state = 'closed'\n                self.window.clear()\n            return\n\n        if len(self.window) \u003C self.min_samples:\n            return\n\n        errors = sum(not item[0] for item in self.window)\n        if errors / len(self.window) >= self.error_ratio:\n            self.state = 'open'\n            self.opened_at = time.time()\n\n    def allow(self):\n        if self.state == 'closed':\n            return True\n\n        if self.state == 'open':\n            cooldown = self.open_seconds + random.uniform(0, 5)\n            if time.time() - self.opened_at >= cooldown:\n                self.state = 'half_open'\n                self.half_open_used = 0\n                return True\n            return False\n\n        if self.half_open_used >= 3:\n            return False\n        return True\n\n\nstates = {\n    'us-east': ProviderState(),\n    'eu-west': ProviderState(),\n}\n\nweights = {\n    'us-east': 50,\n    'eu-west': 30,\n}\n\n\ndef choose_provider(names, weights):\n    candidates = [name for name in names if states[name].allow()]\n    if not candidates:\n        raise RuntimeError('tất cả provider đang ở trạng thái open')\n\n    total = sum(weights[name] for name in candidates)\n    point = random.uniform(0, total)\n    for name in candidates:\n        point -= weights[name]\n        if point \u003C= 0:\n            return name\n    return candidates[-1]\n\n\ndef fetch(url, proxy_by_provider, proxies):\n    provider = choose_provider(list(proxies), weights)\n    state = states[provider]\n    started = time.perf_counter()\n\n    try:\n        response = requests.get(\n            url,\n            proxies={'http': proxy_by_provider[provider],\n                      'https': proxy_by_provider[provider]},\n            timeout=(2, 5)\n        )\n\n        # 403 hoặc 429 từ trang đích được xử lý ở tầng nghiệp vụ riêng.\n        ok = response.status_code \u003C 500\n        latency_ms = response.elapsed.total_seconds() * 1000\n        state.record(ok, latency_ms)\n        return response\n\n    except requests.RequestException:\n        latency_ms = (time.perf_counter() - started) * 1000\n        state.record(False, latency_ms)\n        raise\n```\n\nTrong dịch vụ nhiều luồng, dùng lock hoặc cơ chế single-writer cho mỗi `ProviderState`; nếu không, hai request có thể thay đổi trạng thái đồng thời và làm vòng lặp flapping. Backoff cũng nên có jitter, như đoạn mã, để tránh hàng nghìn worker cùng thử lại đúng một thời điểm.\n\n### Chuyển tuyến có trọng số\n\nKhi một provider mở circuit, đừng gửi toàn bộ lưu lượng sang một provider dự phòng. Giả sử ban đầu có ba nhóm với trọng số 50, 30 và 20. Nếu nhóm 50 bị open, hãy phân bổ lại phần còn lại theo dung lượng thực tế của hai nhóm khỏe, nhưng đặt trần, chẳng hạn không cho một nhóm nhận quá 70% lưu lượng.\n\nViệc chọn provider nên dựa trên:\n\n- Tỷ lệ request thành công theo từng segment.\n- p50 và p95 latency.\n- Dung lượng còn lại và giới hạn giá.\n- Khả năng giữ đúng quốc gia, giao thức và loại IP.\n- Lịch sử chuyển vùng gần đây.\n\nChuyển hướng đột ngột có thể tạo ra lỗi mới. Provider đang khỏe có thể tự đạt đến rate limit của chính nó, hoặc trang đích nhận thấy một nguồn IP duy nhất tăng tốc độ bất thường. Hãy tăng trọng số từ từ và theo dõi phản hồi trong vài phút.\n\n### Giữ nguyên phiên và cấu hình an toàn\n\nCircuit breaker không nên tự đổi provider giữa một phiên đang chạy. Với các tác vụ cần phiên ổn định, gán session ID qua hash tới một provider, giữ sticky TTL và chỉ di chuyển tại ranh giới phiên. Nếu provider bị open, tạm dừng hoặc hoàn tất request hiện tại trước khi chuyển.\n\nĐừng khắc phục TLS bằng cách tắt xác thực chứng chỉ. Với HTTPS, ưu tiên CONNECT qua proxy hỗ trợ, giữ nguyên certificate validation và kiểm tra ngày hết hạn của chứng chỉ. Nếu chỉ cần truy cập TCP hoặc SOCKS5, hãy bảo đảm ứng dụng và proxy cùng hỗ trợ giao thức đó. Cấu hình timeout ngắn ở tầng kết nối cũng giúp phát hiện provider chậm mà không giữ thread hoặc worker bị chặn lâu.\n\n### Đo lường để không mở mạch nhầm\n\nGhi ít nhất các chỉ số sau:\n\n- Số request theo provider, khu vực và loại proxy.\n- Số lỗi theo mã HTTP và nguyên nhân network.\n- p50, p95 và p99 latency.\n- Số lần chuyển từ closed sang open hoặc half-open.\n- Tỷ lệ request được chuyển hướng.\n- Tỷ lệ lỗi của provider khỏe sau khi nhận thêm lưu lượng.\n\nThêm một request canary nhỏ, chẳng hạn kiểm tra một URL nội bộ hoặc endpoint không quan trọng, để phát hiện sự cố trước khi nó ảnh hưởng toàn bộ job. Dashboard nên hiển thị cả circuit state và chất lượng phản hồi; chỉ nhìn tỷ lệ lỗi tổng sẽ che giấu một provider đang suy giảm nhẹ nhưng ngày càng nghiêm trọng.\n\n### Thử nghiệm và giới hạn đạo đức\n\nTrước khi đưa vào production, mô phỏng timeout, DNS failure, 502, 503 và latency cao. Kiểm tra rằng hệ thống không tạo retry storm, không gửi request POST nhiều lần không kiểm soát và không chọn provider không phù hợp vùng địa lý. Với các thao tác không có tính idempotent, thêm khóa phân phối hoặc cơ chế xác nhận trước khi retry.\n\nCircuit breaker là công cụ duy trì độ bền, không phải cách né rate limit. Hãy tôn trọng robots.txt, điều khoản dịch vụ, giới hạn tần suất và cơ chế CAPTCHA; chỉ thu thập dữ liệu được phép và dùng mức tốc độ phù hợp. Nếu trang đích trả 429, hãy tăng thời gian chờ hoặc giảm tốc độ tại tầng scraper thay vì liên tục đổi IP.\n\n### Checklist triển khai\n\n1. Phân loại lỗi network, gateway và phản hồi từ trang đích.\n2. Tạo trạng thái closed, open và half-open cho từng provider hoặc segment.\n3. Dùng cửa sổ trượt, mẫu tối thiểu và backoff có jitter.\n4. Chuyển lưu lượng theo trọng số khỏe, có trần dung lượng.\n5. Giữ affinity của session và không đổi provider giữa phiên.\n6. Theo dõi latency, lỗi, state transition và kiểm tra bằng故障 injection.\n\nLàm theo trình tự này giúp proxy pool phản ứng với sự cố thay vì chỉ xoay IP nhanh hơn. Hệ thống sẽ tốn ít thời gian hơn trong timeout, giảm áp lực lên provider khỏe và trả về dữ liệu ổn định hơn khi mạng thay đổi.\n","https://blog-api.ro-proxy.com/api/blog/posts/thiet-ke-circuit-breaker-proxy-pool/assets",1790057930127]