← Quay lại blog

Bộ phân tích phân tán trên nhiều máy chủ: chia sẻ pool proxy không xung đột và không bị cấm

Phân tích kiến trúc của bộ phân tích phân tán trên nhiều máy chủ - cách chia sẻ hợp lý nhóm proxy, tránh trùng lặp yêu cầu từ một IP và giảm thiểu việc bị cấm.

📅21 tháng 10, 2026

Khi trình phân tích chuyển từ một máy chủ sang một cụm gồm nhiều worker, một vấn đề mới phát sinh - nhiều máy bắt đầu sử dụng cùng một bể proxy đồng thời. Nếu không có sự phối hợp, điều này dẫn đến việc trùng lặp IP trong các yêu cầu, vượt quá giới hạn cho một địa chỉ và bị cấm hàng loạt. Trong bài viết này, chúng ta sẽ xem xét cách shard proxy giữa các máy chủ một cách chính xác, các sơ đồ phân phối nào hoạt động tốt nhất và cách thực hiện điều này trên thực tế với mã Python và Redis.

Tại sao proxy xung đột trong trình phân tích phân tán

Hãy tưởng tượng một tình huống điển hình: bạn có một bể gồm 500 proxy và ba máy chủ, mỗi máy chủ chạy 50 worker phân tích song song của Wildberries, Ozon hoặc Avito. Nếu mỗi máy chủ lấy proxy từ danh sách chung mà không có sự phối hợp, hai worker trên các máy khác nhau có thể đồng thời gửi yêu cầu từ cùng một IP đến cùng một miền. Đối với hệ thống chống bot, điều này trông giống như tần suất yêu cầu bất thường cao từ một địa chỉ - và IP sẽ bị cấm chỉ trong vài phút.

Vấn đề thứ hai là phân phối tải không đồng đều. Nếu proxy chỉ được chia sẻ "theo cách nào đó", một máy chủ có thể lấy những IP nhanh nhất và mới nhất, trong khi máy chủ khác nhận phần còn lại với danh tiếng đã bị tổn hại. Kết quả là tốc độ phân tích trên các máy chủ sẽ khác nhau rất nhiều, và tỷ lệ lỗi cũng vậy.

Vấn đề thứ ba là mất trạng thái. Nếu proxy bị cấm trên một máy chủ, các máy chủ còn lại sẽ không biết điều đó và tiếp tục sử dụng nó, cho đến khi cũng bị cấm. Nếu không có một hệ thống ghi nhận trạng thái proxy tập trung, mỗi máy chủ sẽ "mở lại nước Mỹ" từ đầu, lãng phí các yêu cầu.

Giải pháp cho cả ba vấn đề là thiết lập một cơ chế phối hợp duy nhất: hoặc thông qua một kho lưu trữ trạng thái tập trung (Redis, PostgreSQL, Consul), hoặc thông qua việc chia bể một cách xác định trước, không có sự giao thoa giữa các máy chủ.

Kiến trúc của trình phân tích phân tán

Trước khi chia sẻ proxy, cần xác định kiến trúc tổng thể của cụm. Có hai cách tiếp cận cơ bản.

Sơ đồ tĩnh (shared-nothing). Bể proxy được chia sẻ giữa các máy chủ trước, một lần khi khởi động. Mỗi máy chủ nhận một phân đoạn không giao thoa và làm việc với nó một cách độc lập, không cần liên hệ với các máy chủ khác. Điểm cộng - sự đơn giản tối đa và khả năng chịu lỗi: sự cố của một máy chủ không ảnh hưởng đến các máy chủ khác. Điểm trừ - nếu một máy chủ bị cấm một nửa số proxy, nó không thể "mượn" IP mới từ hàng xóm.

Sơ đồ động (shared-state). Tất cả các máy chủ truy cập vào một kho lưu trữ chung (thường là Redis), nơi lưu trữ trạng thái của mỗi proxy và phát hành nó theo yêu cầu với khóa. Điểm cộng - sử dụng bể đồng đều và phản ứng ngay lập tức với các lệnh cấm. Điểm trừ - kho lưu trữ trở thành điểm thất bại duy nhất, cần có logic riêng cho trường hợp nó không khả dụng.

Trong thực tế, để phân tích các thị trường và mạng xã hội, thường chọn một phương pháp kết hợp: broker trên Redis phát hành proxy với khóa trong thời gian ngắn (TTL), và khi Redis không khả dụng, các máy chủ tạm thời chuyển sang bể fallback cục bộ. Cách tiếp cận này kết hợp độ tin cậy của sơ đồ tĩnh với tính linh hoạt của sơ đồ động.

Cách chia sẻ bể proxy giữa các máy chủ

Hãy xem xét các thuật toán cụ thể mà có thể áp dụng để chia sẻ bể.

Chia sẻ đơn giản theo mô-đun (round-robin sharding). Đánh số proxy từ 0 đến N-1, máy chủ có số i lấy tất cả proxy mà index % số_máy_chủ == i. Đơn giản, nhưng không tốt khi thêm hoặc xóa máy chủ - khi số lượng máy thay đổi, hầu hết bể sẽ được phân phối lại từ đầu.

Băm nhất quán. Mỗi máy chủ và mỗi proxy được gán một vị trí trên vòng tròn băm (thường dựa trên băm của địa chỉ IP hoặc ID máy chủ). Proxy được gán cho máy chủ gần nhất trên vòng tròn. Khi thêm hoặc xóa máy chủ, chỉ một phần nhỏ proxy được phân phối lại, không phải toàn bộ bể - đây là lợi thế chính cho các cụm có thể mở rộng động (ví dụ, trong Kubernetes với tự động mở rộng).

Broker tập trung với khóa. Thay vì gán cứng proxy cho máy chủ, tất cả các máy chủ yêu cầu proxy "rảnh" từ dịch vụ chung (Redis, RabbitMQ), dịch vụ này phát hành nó với khóa tạm thời. Sau khi sử dụng, proxy được trả lại bể. Đây là lựa chọn linh hoạt nhất, đặc biệt nếu tải trên các máy chủ không đồng đều.

Chia sẻ theo miền hoặc tác vụ. Nếu trình phân tích làm việc với nhiều nguồn (ví dụ, Wildberries, Ozon và Avito đồng thời), có ý nghĩa chia sẻ không chỉ proxy giữa các máy chủ mà còn chia sẻ proxy giữa các nguồn - để cùng một IP không xuất hiện trên nhiều nền tảng cùng lúc. Điều này giảm thiểu rủi ro IP bị đưa vào danh sách đen chung của các hệ thống chống gian lận, nếu các nền tảng đó chia sẻ dữ liệu.

Bảng: so sánh các phương pháp shard proxy

Phương pháp Độ phức tạp Khả năng mở rộng Khi nào áp dụng
Chia sẻ theo mô-đun Thấp Kém Số lượng máy chủ cố định
Băm nhất quán Trung bình Tốt Tự động mở rộng cụm
Broker tập trung Trung bình-cao Xuất sắc Tải không đồng đều, lưu lượng cao
Chia sẻ theo miền Thấp Trung bình Phân tích nhiều nền tảng cùng lúc

Thực hiện broker proxy trên Redis

Lựa chọn thực tế nhất cho hầu hết các trình phân tích phân tán là broker tập trung trên Redis. Redis phù hợp vì nó nhanh, hỗ trợ các thao tác nguyên tử và TTL "ngay từ đầu". Ý tưởng rất đơn giản: tất cả các proxy có sẵn nằm trong Redis Set, và khi phát hành proxy cho một worker cụ thể, nó sẽ bị khóa trong một khoảng thời gian nhất định thông qua một khóa với TTL.

Dưới đây là một ví dụ hoạt động của broker trên Python với thư viện redis-py. Nó thực hiện việc phát hành proxy rảnh với khóa, giải phóng sau khi sử dụng và đánh dấu proxy là bị cấm.

import redis
import time
import random

r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)

PROXY_POOL_KEY = "proxy:pool"       # tập hợp tất cả các proxy hoạt động
LOCK_PREFIX = "proxy:lock:"         # tiền tố khóa của proxy cụ thể
BANNED_PREFIX = "proxy:banned:"     # tiền tố đánh dấu cấm
LOCK_TTL = 30                       # giây, thời gian khóa proxy
BAN_TTL = 3600                      # giây, thời gian coi proxy là bị cấm

def load_pool(proxy_list):
    """Tải danh sách proxy vào Redis một lần khi khởi động cụm."""
    if proxy_list:
        r.sadd(PROXY_POOL_KEY, *proxy_list)

def get_free_proxy(server_id, max_attempts=20):
    """Mỗi máy chủ gọi hàm này để nhận proxy rảnh."""
    candidates = list(r.smembers(PROXY_POOL_KEY))
    random.shuffle(candidates)

    for proxy in candidates[:max_attempts]:
        if r.exists(BANNED_PREFIX + proxy):
            continue
        lock_key = LOCK_PREFIX + proxy
        # thiết lập khóa nguyên tử, nếu proxy rảnh
        acquired = r.set(lock_key, server_id, nx=True, ex=LOCK_TTL)
        if acquired:
            return proxy
    return None  # không tìm thấy proxy rảnh, cần fallback

def release_proxy(proxy):
    """Giải phóng proxy sau khi yêu cầu thành công."""
    r.delete(LOCK_PREFIX + proxy)

def mark_banned(proxy):
    """Đánh dấu proxy là bị cấm trong BAN_TTL giây, xóa khóa."""
    r.set(BANNED_PREFIX + proxy, 1, ex=BAN_TTL)
    r.delete(LOCK_PREFIX + proxy)
    r.srem(PROXY_POOL_KEY, proxy)  # tạm thời xóa khỏi bể

def return_to_pool_if_recovered():
    """Công việc định kỳ: trả lại proxy mà đã hết thời gian cấm."""
    for proxy in r.scan_iter(match=BANNED_PREFIX + "*"):
        clean_proxy = proxy.replace(BANNED_PREFIX, "")
        if not r.exists(BANNED_PREFIX + clean_proxy):
            r.sadd(PROXY_POOL_KEY, clean_proxy)

Điểm chính ở đây là việc sử dụng SET ... NX EX trong Redis. Lệnh này là nguyên tử: nếu hai máy chủ đồng thời cố gắng khóa cùng một proxy, chỉ một trong số chúng sẽ thành công, còn lại sẽ bị từ chối và đi tìm ứng viên tiếp theo trong danh sách. Chính điều này loại bỏ xung đột giữa các máy chủ khi truy cập đồng thời vào bể chung.

Ví dụ về việc sử dụng broker trong worker của trình phân tích:

import requests

def fetch_with_proxy(url, server_id):
    proxy = get_free_proxy(server_id)
    if not proxy:
        raise RuntimeError("Không có proxy rảnh trong bể")

    proxies = {"http": f"http://{proxy}", "https": f"http://{proxy}"}
    try:
        response = requests.get(url, proxies=proxies, timeout=10)
        if response.status_code in (403, 429):
            mark_banned(proxy)
        else:
            release_proxy(proxy)
        return response
    except requests.exceptions.RequestException:
        mark_banned(proxy)
        raise

Sơ đồ này dễ dàng mở rộng - bạn có thể thêm 10 hoặc 50 máy chủ, chúng sẽ truy cập vào cùng một Redis instance, và sẽ không xảy ra xung đột IP vì khóa là nguyên tử ở cấp độ kho lưu trữ, không phải ở cấp độ máy chủ riêng lẻ.

Phiên sticky và gán proxy cho các tác vụ

Đối với một số tác vụ, việc phát hành "bất kỳ proxy rảnh nào" là không đủ. Nếu trình phân tích mô phỏng hành vi của người dùng - ví dụ, liên tục xem các thẻ sản phẩm, thêm vào giỏ hàng, chuyển qua các trang danh mục - điều quan trọng là tất cả các yêu cầu trong một phiên phải đến từ cùng một IP. Nếu không, trang web sẽ thấy sự thay đổi địa lý hoặc thiết bị đột ngột giữa phiên và sẽ chặn nó như một phiên đáng ngờ.

Để làm điều này, khái niệm phiên sticky được giới thiệu: proxy không được gán cho một yêu cầu mà được gán cho một tác vụ cụ thể hoặc phiên người dùng trong suốt thời gian tồn tại của nó. Trong Redis, điều này được thực hiện thông qua một khóa riêng có dạng session:{task_id} -> proxy với TTL bằng thời gian dự kiến của phiên.

SESSION_PREFIX = "proxy:session:"
SESSION_TTL = 600  # 10 phút cho một phiên

def get_sticky_proxy(task_id, server_id):
    existing = r.get(SESSION_PREFIX + task_id)
    if existing:
        return existing
    proxy = get_free_proxy(server_id)
    if proxy:
        r.set(SESSION_PREFIX + task_id, proxy, ex=SESSION_TTL)
    return proxy

Đối với các tác vụ có phiên dài - ví dụ, duyệt danh mục Wildberries với mô phỏng hành vi của một người mua thực sự - điều quan trọng là sử dụng các proxy có hành vi giống như người dùng bình thường nhất có thể. Các proxy cư trú rất phù hợp, vì chúng được gán cho các IP gia đình thực và ít gây nghi ngờ hơn đối với các hệ thống chống bot của các thị trường so với các địa chỉ từ trung tâm dữ liệu.

Xử lý cấm và luân chuyển trong cụm

Trong một hệ thống phân tán, điều quan trọng là thông tin về việc cấm proxy phải ngay lập tức có sẵn cho tất cả các máy chủ, không chỉ cho máy chủ đã nhận lệnh cấm đó. Trong ví dụ trên, hàm mark_banned chịu trách nhiệm cho điều này - ngay khi một máy chủ ghi nhận 403 hoặc 429 từ trang web mục tiêu, proxy sẽ được xóa khỏi bể chung trong Redis, và các máy chủ còn lại sẽ tự động ngừng nhận nó.

Để xác định việc cấm, nên phân tích không chỉ mã HTTP mà còn cả nội dung của trang - một số trang web trả về 200 OK, nhưng với captcha hoặc một thông báo thay thế thay vì nội dung thực. Logic phát hiện cấm thường như sau:

  • Mã HTTP 403, 429, 503 - dấu hiệu rõ ràng của việc bị chặn hoặc giới hạn tần suất.
  • Chuyển hướng đến trang captcha hoặc xác thực - kiểm tra URL sau yêu cầu.
  • Giảm đột ngột kích thước phản hồi hoặc thiếu các selector CSS mong đợi - dấu hiệu của một thông báo thay thế.
  • Thời gian kết nối - thường có nghĩa là proxy đã ngừng phản hồi hoàn toàn.

Sau khi ghi nhận việc cấm, có ý nghĩa không chỉ xóa proxy khỏi bể vĩnh viễn, mà còn đặt nó vào "khu cách ly" với TTL (như trong ví dụ - BAN_TTL), sau đó proxy sẽ tự động trở lại bể. Nhiều lệnh cấm trên các thị trường và mạng xã hội là tạm thời và sẽ được gỡ bỏ sau 30-60 phút.

Nếu các lệnh cấm xảy ra quá thường xuyên ngay cả khi tải được phân phối đồng đều, điều này thường là dấu hiệu cho thấy bể proxy có chất lượng thấp hoặc kích thước không đủ cho khối lượng yêu cầu cần thiết. Trong những trường hợp như vậy, việc mở rộng bể bằng cách sử dụng proxy từ trung tâm dữ liệu cho các tác vụ ít nhạy cảm hơn (ví dụ, kiểm tra tính khả dụng của các trang) và sử dụng các IP cư trú đắt tiền hơn chỉ cho các phiên quan trọng giúp giảm tỷ lệ cấm tổng thể mà không làm tăng chi phí cho toàn bộ lưu lượng.

Giám sát tình trạng bể proxy

Trong một cụm gồm nhiều máy chủ, dễ dàng mất đi cái nhìn tổng thể: hiện tại có bao nhiêu proxy đang hoạt động, bao nhiêu bị cấm, tốc độ phản hồi trung bình của mỗi proxy là bao nhiêu. Nếu không có giám sát, rất khó để hiểu tại sao việc phân tích lại chậm lại - có thể một máy chủ đã ngừng hoạt động, hoặc không còn proxy sống nào.

Bộ chỉ số tối thiểu mà bạn nên thu thập trong Redis hoặc một cơ sở dữ liệu riêng:

  • Số lượng proxy đang hoạt động trong bể tại thời điểm hiện tại.
  • Số lượng proxy bị cấm và thời gian trung bình để phục hồi chúng.
  • Thời gian phản hồi trung bình của mỗi proxy trong N yêu cầu gần nhất.
  • Tỷ lệ yêu cầu thành công (success rate) theo máy chủ và theo bể nói chung.
  • Phân phối tải - số lượng yêu cầu mà mỗi máy chủ đã xử lý trong một khoảng thời gian.

Để trực quan hóa, bạn có thể lưu trữ những chỉ số này trong Prometheus thông qua một exporter đơn giản, đọc dữ liệu từ Redis mỗi phút và trả về chúng ở định dạng mà Grafana có thể hiểu. Điều này cho phép bạn nhanh chóng thấy sự suy giảm tỷ lệ thành công theo địa lý cụ thể hoặc loại proxy và kịp thời thêm IP mới vào bể trước khi trình phân tích hoàn toàn dừng lại.

Cần phải giám sát riêng các đỉnh lệnh cấm khi khởi động các máy chủ mới trong cụm - nếu khi mở rộng từ 3 lên 6 máy chủ, tỷ lệ cấm tăng đột ngột, có thể vấn đề nằm ở việc shard: các máy chủ mới bắt đầu giao thoa với các proxy của các máy chủ cũ. Đây là dấu hiệu đầu tiên cho thấy cần chuyển từ chia sẻ theo mô-đun sang băm nhất quán.

Danh sách kiểm tra khởi động trình phân tích phân tán

Trước khi triển khai trình phân tích trên nhiều máy chủ, hãy kiểm tra các điểm sau:

  • Đã chọn cơ chế phối hợp cho bể: chia sẻ tĩnh, băm nhất quán hoặc broker tập trung trên Redis.
  • Đã thực hiện khóa nguyên tử cho proxy, loại trừ việc sử dụng cùng một IP bởi hai máy chủ đồng thời.
  • Đã thiết lập TTL cho khóa và cho việc cấm, để proxy không "treo" trong trạng thái bị khóa vĩnh viễn khi worker gặp sự cố.
  • Đối với các tác vụ yêu cầu mô phỏng phiên người dùng, đã thiết lập gán proxy sticky trong suốt thời gian của tác vụ.
  • Đã thiết lập phát hiện cấm không chỉ theo mã HTTP mà còn theo nội dung phản hồi.
  • Đã bật giám sát tỷ lệ thành công, proxy đang hoạt động và proxy bị cấm trên toàn cụm.
  • Bể proxy đã được chia sẻ theo loại tác vụ: IP từ trung tâm dữ liệu - cho các yêu cầu ít nhạy cảm hơn, proxy cư trú hoặc proxy di động - cho các phiên mà độ giống như người dùng thực là rất quan trọng.
  • Đã kiểm tra tải trên kho lưu trữ trạng thái (Redis) - khi có nhiều máy chủ và tần suất yêu cầu cao, có thể cần một cụm Redis thay vì một instance duy nhất.

Kết luận

Nguyên nhân chính gây ra xung đột khi chuyển trình phân tích sang nhiều máy chủ là sự thiếu vắng một nguồn thông tin duy nhất về trạng thái của bể proxy. Ngay khi có một broker tập trung với các khóa nguyên tử (trên Redis hoặc kho lưu trữ tương tự), vấn đề trùng lặp IP và đồng bộ hóa lệnh cấm sẽ tự động biến mất. Tiếp theo, chỉ cần chọn chiến lược shard phù hợp với quy mô của cụm của bạn: đối với số lượng máy chủ cố định, chia sẻ đơn giản là đủ, còn đối với cơ sở hạ tầng phát triển động, tốt hơn là ngay từ đầu thiết lập băm nhất quán.

Nếu trình phân tích phân tán của bạn làm việc với các thị trường hoặc mạng xã hội, nơi tính hợp lý của lưu lượng ở cấp độ phiên là rất quan trọng, bạn nên chia sẻ bể theo loại tác vụ: cho việc quét hàng loạt danh mục, các proxy từ trung tâm dữ liệu giá rẻ sẽ phù hợp, còn cho các phiên mô phỏng hành vi người dùng - proxy cư trú với các IP gia đình thực sẽ giảm đáng kể tỷ lệ cấm trong các hệ thống nhạy cảm với gian lận.