稳定性不是零错误,而是可预期

19 阅读4分钟

很多人一提“系统稳定性”,第一反应都是:
是不是出错了?是不是挂了?

但如果你真的做过企业级的数据系统,尤其是金融、舆情这类系统,就会慢慢意识到一件事:

真正可怕的,从来不是系统报错,
而是系统“什么都没说,但结果已经不对了”。

我正好踩过这样的坑。

为什么舆情系统里,“零错误”是一个危险目标?

我们先说一个很现实的背景。

企业品牌舆情系统,本质上是在做几件事:

  • 从外部平台抓数据(新闻、社媒、论坛)
  • 做清洗、分析、汇总
  • 最终给业务方一个结论或趋势

这里面有一个天然事实是绕不过去的:

你控制不了数据源。

平台会限流、接口会变、反爬策略会升级,代理 IP 也一定会失效。
如果你的系统目标是“所有请求都成功”,那你迟早会遇到下面这些问题:

  • 无限重试,把系统拖慢
  • 数据延迟越来越大
  • 失败被吞掉,结果却看起来“很完整”

最后的状态通常是:

系统在跑,报表也在出,但你已经不知道这些数据还靠不靠谱了。

真正的稳定性问题,其实是“失败有没有被看见”

后来我们复盘时发现,问题不在于“请求失败”,而在于:

  • 请求失败了
  • 但系统没有明确告诉任何人
  • 下游默认这是一份“正常数据”

这在舆情场景下尤其危险。

因为舆情系统最怕的不是延迟,而是:

  • 负面信息刚好没抓到
  • 风险被悄悄过滤掉
  • 决策建立在错误的“完整数据”之上

所以我现在对稳定性的理解是:

稳定性不是系统不出错,而是系统出错时,行为仍然符合你的预期。

那什么叫“可预期”的失败?

在舆情系统里,我认为至少要做到三点:

第一,失败必须是显性的
请求失败就明确告诉你失败了,而不是返回一个空列表假装成功。

第二,失败必须是可分类的
超时、接口异常、封禁、代码异常,这些在语义上完全不一样。

第三,失败不能污染结果
宁可告诉下游“这批数据不可用”,也不要给一个看起来很完整的假结论。

下面用一段真实工程中常见的示例代码,来说明这种思路。

一个舆情抓取示例,重点不在“抓到”,而在“抓不到时怎么办”

这个例子是一个简化版的企业品牌舆情抓取逻辑,用 Python 写,核心点有三个:

  • 使用代理 IP(以亿牛云爬虫代理为例)
  • 明确设置超时与重试上限
  • 所有异常都转成结构化结果返回

代码本身并不复杂,但设计思路很重要。

import requests

# 爬虫代理配置(示例)
PROXY_HOST = "proxy.16yun.cn"
PROXY_PORT = "8000"
PROXY_USER = "your_username"
PROXY_PASS = "your_password"

proxy_url = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"

proxies = {
    "http": proxy_url,
    "https": proxy_url
}

HEADERS = {
    "User-Agent": "Mozilla/5.0 (compatible; BrandMonitor/1.0)"
}

TIMEOUT = 8
MAX_RETRY = 2
BRAND_KEYWORD = "某知名企业"

def fetch_brand_news():
    url = "https://example.com/api/news"

    for attempt in range(1, MAX_RETRY + 1):
        try:
            resp = requests.get(
                url,
                params={"q": BRAND_KEYWORD},
                headers=HEADERS,
                proxies=proxies,
                timeout=TIMEOUT
            )

            if resp.status_code != 200:
                return {
                    "status": "http_error",
                    "code": resp.status_code,
                    "data": None
                }

            return {
                "status": "success",
                "data": resp.json()
            }

        except requests.exceptions.Timeout:
            if attempt == MAX_RETRY:
                return {
                    "status": "timeout",
                    "data": None
                }

        except Exception as e:
            return {
                "status": "exception",
                "error": str(e),
                "data": None
            }

注意这里一个很关键的点:

这个函数永远不会“悄悄失败”。

它要么成功,要么明确告诉你失败类型。

为什么我更愿意返回“失败原因”,而不是空数据?

很多系统在失败时,习惯返回一个空数组。

从代码角度看,这很省事;
从工程角度看,这是在埋雷。

因为空数组在业务语义上,往往会被理解为:

  • 没有舆情
  • 风险为零
  • 一切正常

但实际上,这可能只是:

  • 数据源挂了
  • IP 被封了
  • 网络抖了一下

所以我更愿意在最终结果里,明确加上类似这样的信息:

{
  "可信度": "不可用",
  "原因": "timeout"
}

这比任何“稳定运行”的假象都重要。

在金融和舆情系统里,这是稳定性的底线

做久了你会发现,金融级系统和脚本最大的差别,不是技术多复杂,而是态度完全不同。

脚本的思路是:

  • 能跑就行
  • 报错了再说

而金融、舆情系统的思路是:

  • 我永远假设它会失败
  • 所以我必须提前设计好失败的样子

所以回到标题那句话:

稳定性不是零错误,而是可预期。

系统可以失败,但不能骗你。
这件事,比成功率高不高重要得多。