一次高并发采集系统的架构设计评审记录

0 阅读4分钟

评审主题:高并发数据采集系统设计
核心争议:当前采集失败率高,是代码质量问题,还是系统架构问题?
评审结论:这是一个典型的架构失配问题,而非代码层缺陷。

一、业务背景说明

当前采集系统的目标非常明确:

  • 日采集任务量不低于一万
  • 目标站点具备一定反爬策略
  • 必须使用代理 IP
  • 允许部分失败,但不允许系统性雪崩
  • 成本可控,可长期运行

在采集规模维持在一千以内时,系统运行基本稳定;
当任务量提升到五千至一万后,问题开始集中出现:

  • 请求成功率明显下降
  • 大量代理 IP 被快速封禁
  • 线程阻塞,请求队列堆积
  • CPU 与内存利用率异常

评审会议的第一个核心问题随之出现:
问题究竟出在代码层,还是系统结构本身?

二、现有系统结构回顾

当前系统属于典型的“脚本增强型爬虫”:

  • 多线程或协程并发
  • 每个请求独立获取代理 IP
  • 请求失败立即重试
  • 请求、调度、异常处理集中在同一进程

从代码质量角度评审:

  • 请求逻辑清晰
  • 异常处理完整
  • 日志较为齐全
  • 单次请求成功率尚可

代码评审并未发现明显缺陷,这意味着问题很可能不在实现细节。

三、方案一:继续深度优化代码(评审否决)

方案描述

方案提出方认为,可以通过进一步代码优化解决问题,例如:

  • 精简请求与解析逻辑
  • 调整超时时间
  • 提升并发执行效率
  • 减少不必要的数据处理

评审结论

该方案被否决,理由如下:

第一,代码优化只能改善单次请求质量,无法解决系统级资源竞争问题。
第二,高并发下代理 IP 被无序滥用,失败重试会放大请求压力。
第三,在架构不变的前提下,性能优化反而可能加速系统崩溃。

评审结论非常明确:
当采集规模达到上万时,继续在代码层“打磨细节”,并不能改变系统失稳的根本原因。

四、方案二:引入架构拆分与代理池(评审通过)

核心设计思想

将“请求执行”和“资源调度”从逻辑上彻底分离,让系统具备规模意识。

系统被拆分为四个核心模块:

  1. 任务调度层
    负责控制整体并发规模,避免瞬时流量洪峰。
  2. 代理 IP 池
    统一管理代理资源,控制单个 IP 的使用频率和生命周期。
  3. Worker 执行层
    只负责执行请求,不关心代理来源和并发策略。
  4. 失败与降级策略层
    对失败请求进行延迟重试、限流或降级处理。

五、关键模块实现说明

以下为评审通过后的核心实现示例,使用 Python 进行说明。

1. 代理统一配置入口

# 16YUN代理配置
PROXY_HOST = "proxy.16yun.cn"
PROXY_PORT = 9020
PROXY_USER = "你的用户名"
PROXY_PASS = "你的密码"

def get_proxy():
    """
    返回 requests 可用的代理配置
    """
    proxy_auth = f"{PROXY_USER}:{PROXY_PASS}"
    proxy_url = f"http://{proxy_auth}@{PROXY_HOST}:{PROXY_PORT}"
    return {
        "http": proxy_url,
        "https": proxy_url
    }

在评审中明确要求:
代理配置必须集中管理,禁止在各个业务逻辑中随意拼接和分散使用。

2. 代理使用限速封装

import time
import threading

class ProxyLimiter:
    """
    简单的代理使用限速器
    """
    def __init__(self, interval=1):
        self.lock = threading.Lock()
        self.last_used = 0
        self.interval = interval

    def wait(self):
        with self.lock:
            now = time.time()
            wait_time = self.interval - (now - self.last_used)
            if wait_time > 0:
                time.sleep(wait_time)
            self.last_used = time.time()

proxy_limiter = ProxyLimiter(interval=0.8)

评审共识是:
代理 IP 是受限资源,而不是并发加速器。

3. Worker 执行层实现

import requests

def fetch(url):
    """
    单个任务的执行逻辑
    """
    proxy_limiter.wait()
    proxies = get_proxy()

    try:
        response = requests.get(
            url,
            proxies=proxies,
            timeout=10
        )
        response.raise_for_status()
        return response.text
    except Exception as e:
        print(f"请求失败: {e}")
        return None

Worker 层遵循单一职责原则:
只负责执行请求,不承担调度、限流或代理管理职责。

4. 简化版任务调度示例

from concurrent.futures import ThreadPoolExecutor, as_completed

def run(urls):
    """
    控制整体并发规模
    """
    results = []
    with ThreadPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(fetch, url) for url in urls]
        for future in as_completed(futures):
            results.append(future.result())
    return results

在评审结论中明确指出:
并发上限是系统的安全边界,而不是性能指标。

六、风险点与预案

  • 代理被封:动态切换代理并降低请求频率
  • 请求失败激增:引入失败队列与延迟重试
  • 突发任务洪峰:调度层统一限流
  • 单机性能瓶颈:Worker 模块支持横向扩展

七、最终评审结论

当采集规模达到上万级别时,继续纠结代码是否足够“优雅”已经失去意义。
真正决定系统稳定性的,是架构是否具备资源调度、限流和隔离能力。

一句话总结本次评审结论:

代码没有犯错,错的是让一个脚本级结构去承担系统级规模。