1. 项目整体设计与思路拆解
1.1 项目背景:我们到底要解决什么问题
先说清楚这个 PLFM_RADAR 到底是个什么东西。它其实是一套面向多平台的数据感知与状态监控系统,核心目标就是“用一套标准化流程,把散落在不同平台上的公开信息、状态数据和业务指标统一采集回来,经过清洗、识别和聚合之后,以可视化的方式呈现出来”。
很多团队做到后面都会遇到一个共同的问题:平台越来越多,数据源越来越杂,今天要看这个平台的榜单,明天要盯那个平台的热点,后天还要关注竞品的动态。每个人各查各的,效率低不说,数据口径还不一致。PLFM_RADAR 想解决的,就是这种“信息感知混乱”的问题——给团队一双统一的“眼睛”,持续、稳定地感知各个平台的动态。
为什么叫 RADAR?因为它的工作机制和一个雷达系统非常像。雷达的原理是周期性地发射探测波,接收回波,然后从回波中识别目标、跟踪轨迹。PLFM_RADAR 做的事情一模一样:按照固定的节拍扫描目标平台,把返回的数据当作“回波”接收下来,然后从这些回波里识别出“目标”——也就是那些值得关注的变化、趋势或者异常信号,最后在仪表盘上把目标的位置和轨迹呈现出来。
我当时的场景比较典型:同时对接了六个不同类型的内容平台,每个平台的接口规范、限流策略、返回结构都不一样,有的有现成的开放接口,有的只能靠页面解析。再加上业务方要求的指标维度又多——曝光、互动、增量、排行变化、异常波动……零零总总加起来,靠人工盯根本不现实。所以我才动手写了这套系统,把“感知”这件事流程化、自动化。
1.2 雷达模型:从探测波到目标轨迹的层级映射
设计这套系统的时候,我参考了雷达的工作模型,把整个项目分成了四层,每一层对应雷达的一个核心概念。
第一层是“发射机”,对应的是采集调度模块。雷达不会一直发射波,而是按脉冲节奏来。PLFM_RADAR 也一样,每个目标平台对应一个独立的采集任务,按预设的时间间隔触发。比如榜单类平台十分钟扫一次,热点类平台五分钟扫一次,状态类接口三十秒扫一次。这个节奏不是拍脑袋定的,而是根据平台的数据更新频率和接口限流限制倒推出来的,后面我会详细讲怎么算这个间隔。
第二层是“回波接收”,对应的是数据抓取与解析模块。雷达接收回波之后,要经过放大、滤波、检波才能变成可用的信号。PLFM_RADAR 抓回原始数据之后,也要经过清洗、去重、格式归一化,才能进入下一步。这一层最烦人,因为每个平台的“波形”都不一样,有的返回 JSON,有的返回 HTML,有的还有签名和加密。我在设计的时候把解析逻辑全部做成插件化,每个平台一个适配器,互不干扰,这样新增平台的时候只需要写一个新的适配器,不需要动主流程。
第三层是“目标识别”,对应的是指标计算与异常检测模块。雷达从杂波中分辨出真实目标,靠的是回波的特征。PLFM_RADAR 里的“目标特征”就是各种业务指标和阈值规则。比如某个平台的账号新增数突然翻了三倍,或者某个话题的讨论量在一个周期内断崖式下跌,这些都会被识别成“值得关注的目标”,打上标签进入告警队列。
第四层是“态势显示”,对应的是可视化与通知模块。雷达最终要把目标的位置显示在屏幕上,PLFM_RADAR 则是把识别出来的所有信号聚合成一个总览面板,同时按需推送到企业微信、钉钉或者邮件。这一层直接决定系统的价值能否被业务方感知到,所以我花了很大的精力在视图的呈现方式上,后面会有详细的说明。
这个分层设计给我带来的最大好处是:每一层都可以独立替换、独立升级。采集模块要加代理池?改第一层就行。识别逻辑要引入机器学习模型?改第三层就行。不会出现牵一发动全身的尴尬情况。
1.3 方案选型:为什么没有直接买商业产品
前期我也调研过市面上的商业监控和舆情分析产品,最后决定自己动手,主要卡在三个原因上。
第一个是数据源的灵活性。商业产品通常只覆盖主流平台的标准数据,但我需要监控的有些是垂直领域的专业数据库,有些是需要登录才能看到的业务后台数据,这些商业工具根本没有办法覆盖。自己做,本质上没有任何限制——只要你能访问到的页面或接口,就能接入。
第二个是成本结构。成熟的数据监控产品基本都是按 API 调用次数或者按监控对象数量收费的,监控的平台一多、采集频率一高,账单就会涨得非常快。而自建系统的主要成本是开发时间和一台性能还不错的服务器,分摊到一年多的时间维度上,自建方案的性价比优势非常明显。
第三个是可控性。这个可控性体现在很多层面:数据存在自己的数据库里,不用担心第三方平台停止服务导致的历史数据丢失;告警规则的调整可以实时生效,不用提工单等排期;算法模型可以随时迭代,没有任何供应商的合同约束。
当然,自建不是没有代价。最大的代价就是运维负担和开发周期。系统跑起来之后,你要持续关注它是否稳定、是否需要扩容、平台端接口变动了谁来修。这实际上是一个长期投入的过程。但对我来说,这种投入带来的收益和掌控感是值得的。
2. 核心模块拆解与技术选型
2.1 采集层:定时任务、代理策略与防封禁的平衡
采集层是整个系统最基础也最容易出问题的地方。PLFM_RADAR 的采集层核心是一个基于 Celery 的异步任务队列,配合 Redis 作为 Broker,定时调度用 Celery Beat 来实现。
当时没有直接用传统的 Linux Crontab,原因很简单——Crontab 的最小粒度只能到分钟级,而且不支持任务失败后的自动重试和依赖管理。Celery Beat 可以把某个采集任务的调度周期精确到秒级,还支持分布式执行。我试过让两个 worker 实例同时跑采集任务,配合 Redis 做任务分发,吞吐量立刻翻了一倍。
定时间隔的设定,我是按下面这个公式来算的:
最小间隔 = 平台单账号单位时间内允许请求数 ÷ 计划采集的账号数 ÷ 请求消耗系数
举个例子,某个平台允许单个 IP 每秒最多 2 次请求,我要监控 50 个目标账号,保守起见把请求消耗系数设为 2(也就是实际频率比限制低一半),那么最小间隔就是 2 ÷ 50 ÷ 2 = 0.02 秒?这个算出来明显不合理,因为实际上我并不是所有账号都同时发起请求,而是按队列顺序依次执行。真实的计算方式要考虑的是“单轮全量扫描需要多少时间”以及“一天内最多可以发起多少次扫描”。
更实际的约束是这样的:假设单个账号的完整采集需要发出 5 次请求,5 个请求串行执行,每次平均耗时 0.5 秒,那么采集一个账号要 2.5 秒。50 个账号一轮下来需要 125 秒。如果平台限制单 IP 每天最多 2 万次请求,那么一天 24 小时里,我们最多能发起 2 万次请求,也就是最多执行 2 万除以 5 等于 4000 轮全量采集。这意味着每轮采集的最小间隔是 86400 秒除以 4000 轮,约等于 21.6 秒。所以定时任务的间隔至少要大于 21.6 秒,再留 1.5 倍安全余量,就是 30 秒以上。
这还没有考虑分布式部署。如果我把采集任务分散到 2 个 IP 上,每天的请求额度翻倍,最小间隔可以压缩到 11 秒。这也是为什么我强调要用分布式 worker 的原因——它不仅提升吞吐量,还能在相同的“合规安全水位”下提高采集频率。
防封禁的策略,我的经验是所有动作都要“拟人化”。每一种反爬机制都有一个核心逻辑:不能让平台发现你是脚本。所以我做了三件事:一是 IP 池轮换,我买了一批住宅代理 IP,每 5 到 10 分钟自动切换出口;二是请求头随机化,把 UA、Accept-Language、Sec-Fetch 这类字段全部做成随机组合;三是行为模拟,采集之间随机延时 1 到 3 秒,按照人类浏览的节奏发请求。这套组合打下来,我在大半年的运行中只出现过一次临时封禁,而且很快解除了。
2.2 解析层:站点适配器架构与结构化提取
解析层是所有平台差异性的集中爆发区。六个平台,六套接口规范,甚至同一平台的不同页面返回格式都不一样。我的解决方案是“统一入口 + 独立适配器”的架构。
统一入口很简单——所有平台的采集任务只做一件事:把页面或接口的数据拉回来,放进消息队列里,然后由对应的适配器去消费。适配器负责把原始内容转换成统一的 Schema。这个 Schema 定义了每一条数据的标准结构,比如标题、作者、发布时间、内容摘要、原始链接、平台名、分类、指标数组等字段。
@dataclass class NormalizedRecord: platform: str record_type: str title: str author: str publish_time: datetime content_excerpt: str source_url: str category: str metrics: dict[str, float] raw_data: dict每个适配器做三件事。第一,把原始数据中对应的字段抽出来,填入 NormalizedRecord;第二,对字段做清洗,比如去掉 HTML 标签、规范时间格式、统一数值单位;第三,做数据增强,比如从摘要里提取关键词、计算文本情感倾向、给记录打上内容标签。
解析层最容易踩的坑是“平台升级导致解析失效”。我遇到过一次平台偷偷改了返回结构,把一个字段从数据体里挪到了响应头里。最直观的表现是大量入库记录的某个字段全部为空。后来我给所有字段都加了解析完成率监控——如果某个字段的完成率突然掉到 95% 以下,系统会自动告警提示“解析规则可能已失效”。这个机制后来帮我止损了两次,非常值得推荐。
2.3 存储层:时序数据与业务数据的混搭策略
存储层我用了两套数据库方案,各管各的领域。
一套是 PostgreSQL,负责存放所有结构化业务数据,包括配置信息、账号信息、解析后的记录明细和告警记录。PostgreSQL 的好处是生态成熟、事务能力强,写复杂的聚合查询非常顺手。我给它加了 TimescaleDB 扩展,这样即使要查“某个平台最近 7 天每个小时的指标趋势”,SQL 也能跑得很快。
另一套是 ClickHouse,负责存放高吞吐的采集明细数据。为什么明细数据要单独放?因为采集产生的数据量级非常大,六平台 × 高频采集 × 每轮全量明细,一天的增量就有几百万行。如果用 PostgreSQL 存这些明细,查询性能很快会恶化,而且会和业务数据的存储空间抢资源。ClickHouse 的列式存储天生适合这种“写多读少、聚合分析为主”的场景,几十亿行数据做 Group By 聚合,响应时间仍然在秒级。
两套库之间的联动逻辑是:ClickHouse 存原始明细,PostgreSQL 存业务结果。在数据入库的环节,我写了一个轻量级的 ETL 任务,每隔三十分钟把新增明细做一次聚合计算,把聚合结果写进 PostgreSQL。业务方查面板上的趋势图、排行数据,走的都是 PostgreSQL;只有做深入的还原分析、漏斗对比时,才会去查 ClickHouse 的明细。
仓库选型上,PostgreSQL 我用的 14 版本,加了 TimescaleDB 2.9 扩展;ClickHouse 用的 22.8 LTS 版本,单节点部署,数据压缩比大约在 6 比 1,三千万行的明细数据占用的磁盘空间不到 3GB,这个成本完全可以接受。
2.4 展示层:从指标到大盘的组装方式
展示层用的是 Grafana,因为它的生态最完善,对时序数据的展示支持最好,而且和 Prometheus、ClickHouse、PostgreSQL 都有现成的数据源插件。
不过我不建议把业务方的复杂需求全部塞进 Grafana 的原生面板里。我的做法是:Grafana 负责“高大上的总览”,把系统健康度、采集成功率、任务延迟这类运维指标放上去;而业务方真正关心的“内容洞察”面板,我用 Grafana 的自定义 Iframe 插件嵌入了一个自己写的 React 前端。这样既能享受 Grafana 的开箱即用的可视化能力,又能让我自己控制业务视图的交互细节。
这个前端页面做了三个核心视图。第一个是“平台总览”,一屏展示六个平台的核心指标对比;第二个是“单平台深度分析”,点击任意平台后进入详情,能看到趋势曲线、热点分布、异常事件时间轴;第三个是“告警事件流”,把所有识别到的异常按时间倒序排列,红黄灰三色标记严重程度。这三个视图基本覆盖了从宏观到微观的绝大多数使用场景。
有一点需要提醒:Grafana 默认的告警配置是走它的内部状态机的,配置起来比较绕。如果你也是用 Grafana 做展示,建议把告警逻辑放在自己的后端服务里,Grafana 只做被动展示。这样告警规则的逻辑可以写得更加灵活,测试起来也更方便。
3. 实操过程与核心环节实现
3.1 环境准备与基础架构搭建
整套系统跑在一台 8 核 16G 内存的云服务器上,SSD 磁盘,带宽 100Mbps。这个配置对我来说够用了。如果后续采集规模再扩大一倍,我可能会把 ClickHouse 迁移到独立机器,避免存储和其他服务争抢 IO。
部署方式是 Docker Compose。我把所有依赖组件打包成了七个容器,包括 PostgreSQL、ClickHouse、Redis、Celery Worker、Celery Beat、API 服务、Grafana。用 Docker Compose 管理的最大好处是环境一致性有保证,换一台新服务器,一条 docker-compose up -d 就能全部拉起,不用担心某个底层依赖版本不一致导致起不来。
基础架构的核心是三个配置文件。一个是 docker-compose.yml,负责定义各容器的镜像、端口映射和数据卷;一个是 .env,存放所有敏感配置项,比如数据库密码、代理池地址、各平台的 API Key;最后一个是我自定义的 config.yaml,存放所有采集任务的调度参数和平台适配器的开关状态。
下面是我当时使用的 docker-compose.yml 的核心片段,精简了无关部分,只保留主结构:
version: "3.8" services: postgres: image: postgres:14 environment: POSTGRES_DB: plfm_radar POSTGRES_USER: radar POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - pg_data:/var/lib/postgresql/data ports: - "5432:5432" restart: always clickhouse: image: clickhouse/clickhouse-server:22.8 volumes: - ch_data:/var/lib/clickhouse ports: - "8123:8123" restart: always redis: image: redis:7 ports: - "6379:6379" restart: always celery-worker: build: ./app command: celery -A radar_core worker --loglevel=info --concurrency=4 depends_on: - redis volumes: - ./app:/app restart: always celery-beat: build: ./app command: celery -A radar_core beat --loglevel=info depends_on: - redis volumes: - ./app:/app restart: always api: build: ./app command: uvicorn api.main:app --host 0.0.0.0 --port 8000 depends_on: - postgres - clickhouse ports: - "8000:8000" restart: always grafana: image: grafana/grafana:9.3.2 environment: GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD} volumes: - grafana_data:/var/lib/grafana ports: - "3000:3000" restart: always volumes: pg_data: ch_data: grafana_data:整个环境从零到跑通,我大概花了一天时间。主要时间花在调试 ClickHouse 的初始化和 Grafana 的数据源配置上。如果你跟着做,尽量先启动 core 服务(PostgreSQL、Redis、ClickHouse),确认这三个容器起来之后,再启动 Celery 和 API,最后启动 Grafana。顺序错了容易出一些让人困惑的联动报错。
3.2 采集任务的循环逻辑:从定时触发到数据落地
采集任务的完整生命周期分五步:触发调度、执行采集、解析入库、聚合计算、状态上报。我用 Celery Beat 定义了两个核心周期任务。
# config.py 中关于调度的定义 from datetime import timedelta CELERY_BEAT_SCHEDULE = { "fast-platform-scan": { "task": "radar_core.tasks.collect_fast", "schedule": timedelta(seconds=30), }, "slow-platform-scan": { "task": "radar_core.tasks.collect_slow", "schedule": timedelta(minutes=10), }, }fast 任务每 30 秒跑一次,处理的是数据更新频繁的平台,比如热搜榜、实时榜单;slow 任务每 10 分钟跑一次,处理的是更新频率较低的站点,比如文章发布、账号资料变更。这样设计的原因是:不同平台的数据时效性完全不同,一刀切地使用同一个采集间隔,要么浪费额度要么时效性不够。
采集任务在 worker 里执行的流程,我简化成下面的代码逻辑。这段代码是整个采集层的核心骨架,值得多看两遍。
# radar_core/tasks.py import logging from celery import shared_task from .collectors import get_collector from .parsers import get_parser from .sinks import write_to_clickhouse, write_to_postgres logger = logging.getLogger(__name__) @shared_task(bind=True, max_retries=3, default_retry_delay=60) def run_collect(self, platform_name: str, target: str): try: collector = get_collector(platform_name) raw_data = collector.fetch(target) normalized = get_parser(platform_name).parse(raw_data) write_to_clickhouse(normalized) write_to_postgres_summary(normalized) except Exception as exc: logger.warning("采集任务失败: %s - %s", platform_name, exc) # 这里设置 60 秒后重试,最多 3 次 raise self.retry(exc=exc, countdown=60)我在此处加了 max_retries=3 和 default_retry_delay=60,这非常关键。刚开始做的时候没有重试机制,网络抖动导致的一次请求失败会直接丢失那一轮的数据。加了重试之后,只要不是平台彻底挂了或者代理池挂了,数据缺口基本不会出现。
入库我做了双写:一份明细进 ClickHouse,一份聚合摘要进 PostgreSQL。聚合作业是一个每 30 分钟运行一次的定时任务,逻辑大概是:从 ClickHouse 中取出最近 30 分钟的数据,按平台加指标维度做聚合,算总数、均值、方差和环比变化率,然后把结果写入 PostgreSQL。这些聚合结果就是业务面板上各种趋势图表的直接数据来源。
3.3 识别逻辑与异常检测阈值的设定
异常检测部分,我用的是传统统计方法,没上机器学习模型。核心逻辑是“滑动窗口 + 标准差阈值”——对每个指标,维护一个过去 7 天的历史窗口,计算均值和标准差。当新值超过均值加三倍标准差,或者低于均值减三倍标准差,就判定为一次异常波动。
这个方案有一个明显的优点:对于每个指标,阈值是自适应的,不会因为不同平台不同的数量级导致“同一套阈值在一家平台天天误报、在另一家平台永远不报”的问题。
但标准差方法也有问题:它对渐进趋势不敏感。比如某个指标连续 15 天每天缓慢增长 2%,累计涨幅已经很大了,但每一天的单点值都在历史均值附近,不触发异常判断。所以我又加了一个辅助规则——对每个指标做一元线性回归,计算斜率,如果最近 7 天的拟合斜率超过历史斜率的 3 倍,就标记为“趋势异动”。
# radar_core/anomaly.py import numpy as np def detect_anomaly(history: list[float], current: float) -> tuple[bool, str]: arr = np.array(history) mean = arr.mean() std = arr.std() if std == 0: return False, "" z_score = (current - mean) / std if abs(z_score) > 3: return True, f"z-score异常({z_score:.1f})" x = np.arange(len(arr)) slope_hist = np.polyfit(x, arr, 1)[0] recent = arr[-3:] slope_recent = np.polyfit(np.arange(3), recent, 1)[0] if slope_hist != 0 and abs(slope_recent / slope_hist) > 3: return True, f"趋势斜率剧变" return False, ""在确定告警触发条件的时候,我刻意避免“一异常就告警”。每个指标的异常被识别之后,会先进入一个 Pending 队列,只有连续三次采集周期都处于异常状态,才真正把告警发出去。这个设计极大减少了因瞬时网络异常或数据源短时抖动导致的误报。我统计过,加上这个确认机制之后,误报率下降了差不多七成。
3.4 API 服务与前端呈现
API 服务用的是 FastAPI,它天然支持异步和自动生成 OpenAPI 文档,开发效率很高。服务只暴露了三个核心接口:第一个是 GET /api/platforms,返回全平台实时总览;第二个是 GET /api/platform/{platform_name}/trend,返回指定平台的趋势数据;第三个是 GET /api/alerts,返回最近的告警事件。
前端一个小技巧:如果只是自己用,不需要花太多精力在交互设计上,把最核心的数据可视化做好就够了。我最常用的其实是 Grafana 面板上的三张图——采集成功率趋势图、数据入库延时图、平台更新频率热力图。这三张图能让我在 10 秒内判断出系统是否处于健康状态。
采集成功率这一点我要特别强调一下。很多同行刚开始做采集系统,只关注业务数据对不对,却忽略了采集任务本身的状态监控。实际上,采集成功率就是整个系统的“血压计”。一旦成功率连续下跌,就需要立刻检查:是代理池的 IP 被封了一批?还是平台改了接口签名?还是目标平台谱到了访问限制?如果连这个监控都没做,系统出了问题你往往是最后一个知道的。
4. 常见问题与排查技巧实录
4.1 平台接口升级导致解析失效
这是每个做采集对接的人都绕不开的坑。我遇到过三次,每次的情况都不同,这里说一次印象最深的。
某个平台把原来返回 JSON 的接口改成返回一个加密的 HTML 页面,原本直接取数据的地方全变成了空值。表现是:采集任务全部成功,状态码 200,内容也成功入库,但是关键指标字段全是 0。这个问题的危险之处在于它不报错,所有监控指标看起来都正常,业务方看了数据之后才觉得不对劲。
排查过程是这样的。我先用命令手动请求了一次目标接口,把返回内容和库里存的 raw_data 对比,发现结构完全变了。然后看适配器的日志,发现解析出来的字段全部为空是因为 HTML 里根本没有原来的数据节点。确认是平台升级之后,我立刻写了一个新的解析适配器,用 HTML 里的脚本标签中嵌的 JSON 数据块作为替代数据源,同时回填了历史数据的解析。
这个坑给我的教训有三个。第一,所有字段必须加解析完成率监控;第二,适配器代码要尽量做到“结构变更快速响应”,不要写死层级路径;第三,解析器一定要保留 raw_data 原始字段,否则平台升级后想回补数据都没有素材。
4.2 Celery Worker 假死与任务堆积
系统连续跑了两个月之后,有一次我发现采集任务大量堆积,实际完成量却很少。查了 worker 的日志,没有任何报错信息,进程还在,CPU 占用不高,看起来一切正常。这就是典型的“假死”状态。
原因是在某个第三方库的底层,调了一个阻塞式的网络请求,这个请求没有设置超时时间,当上游服务一直不返回响应时,worker 的线程就一直卡住。因为每个 worker 的并发线程数有限,几个线程全被卡住,任务队列自然就开始堆积了。
解决方案分两部分。一部分是加超时控制,给所有网络请求统一设置连接超时和读取超时,分别设为 5 秒和 10 秒,超过就直接抛异常重试。另一部分是加看门狗机制——Celery Worker 内部加了一个心跳任务,每 30 秒发一次心跳到 Redis,如果某个 worker 连续三次没有心跳,就从 worker 列表中剔除,并由 beat 把积压的任务重新分发到其他可用 worker 上。
这个组合拳下来,假死问题基本绝迹。之后我运行了大半年,再没出现过类似的现象。
4.3 代理池的 IP 质量参差不齐
代理池的 IP 质量是影响采集成功率的最大变量。刚开始我用的是公共代理池,图便宜。结果发现,公共代理的 IP 很多已经被目标平台标记过,成功率不到六成,大量请求超时或者返回验证码。后来换成了按量付费的住宅代理,成功率提升到 98% 以上,成本虽然高了一些,但重复请求的数量大幅下降,整体来看反而是划算的。
我的经验是:如果目标平台对 IP 的敏感度很高,选择代理有两个原则。第一,宁可用同一个稳定 IP 也不要频繁切换质量差的 IP,因为平台的风控系统对“同一账号突然频繁变换 IP”的容忍度,比对“单 IP 高频请求”的容忍度更低。第二,代理池最好做成自动健康检查,每次请求之后记录成功和失败数据,失败次数多的 IP 自动降低权重,连续失败超过 10 次就临时拉黑。这个机制能显著提升整体采集的成功率。
4.4 突发峰值与数据库写入瓶颈
有一次因为某平台出了大热点,采集频率虽然没有变化,但单个目标的数据量骤然增大,ClickHouse 的写入压力爆增。表现出来是写入队列积压,数据入库的延时从原来的毫秒级涨到了分钟级。
排查之后发现,瓶颈在 ClickHouse 的批量写入方式。我原本是每条明细写入一次,到了高峰期,细碎的小请求让 ClickHouse 频繁做合并,性能迅速恶化。解决方式是改成批量异步写入——用 Buffer 表承接实时数据,每秒批量 Flush 一次到目标表。这个改动让写入吞吐量提升了十倍以上,再也没出现过积压问题。
如果你也遇到类似的写入瓶颈,记住一个原则:列式存储数据库适合批量写入,忌讳高频小批量写入。所有落到 ClickHouse 的数据,都尽量攒批再写。
4.5 告警风暴与“狼来了”效应
系统上线初期,告警规则设置得比较激进,结果一天之内给业务方发了几百条告警通知。最初两天还有人在意,到第三天基本没人看了,这就是典型的“狼来了”效应。
调整策略是给告警设置了三个等级。第一级是 P0 级,代表系统完全不可用或目标数据大面积异常,立刻通过电话和短信通知;第二级是 P1 级,代表特定平台或特定指标异常,通过企业微信推送;第三级是 P2 级,代表轻微波动,只在仪表盘的告警事件流中展示,不主动推送。同时增加了告警聚合,相同平台的同类告警会在 30 分钟内合并成一条,避免重复轰炸。
调整之后,告警的响应率反而提高了。因为每条收到的告警都是值得看的。
5. 优化经验与踩坑后的心得
5.1 先跑通再优化,不要一开始就追求完美架构
做 PLFM_RADAR 这个项目最大的感悟就是:先跑通再优化,不要一上来就设计一个大而全的架构。
我最初的设想远比现在复杂,比如计划引入消息队列做数据总线、用 Kubernetes 做任务编排、用 Redis 做多级缓存。真到落地的时候才发现,这些对当时的场景来说都属于过度设计。一个 8 核 16G 的服务器,跑 Docker Compose 就够了,Kubernetes 在这里除了增加运维复杂度,不会带来多少收益。
建议从一个最简版本开始做:单机部署,先把采集、解析、入库、展示这条链路打通。等到数据量涨上来、并发压力变大之后,再逐步把组件拆出去、做分布式扩展。这样每一步都有明确的动力和目标,不会因为过度设计而拖延进度。
5.2 数据规范的统一比想象中还重要
六平台的数据格式千差万别,如果一开始不做好数据规范统一,后面写聚合的时候一定会非常痛苦。我在第一次设计 Schema 的时候就花了整整一个下午做字段规划,把所有平台的字段映射关系列了一张大表,逐一确认每个字段的含义、格式和粒度。
这个前期投入带来的回报巨大。后期加新平台的时候,只要按照规范写适配器就行,复用性非常高。甚至后来团队里另外两个人接手开发,也没有出现因为“字段口径不一致”导致的沟通成本。如果你也要做类似的多平台系统,我强烈建议把数据规范放到项目计划的第一优先级。
5.3 日志和监控才是系统的“隐藏骨架”
我能在这套系统出问题时快速定位,靠的不是运气,而是日志和监控做得还算到位。每个采集任务都有唯一的任务 ID,从请求下发到解析入库,全链路日志都带着这个 ID。查询问题的时候,一条命令就能看到完整链路。
监控方面,除了上面说过的采集成功率、入库延时之外,我还监控了 Redis 的内存使用量、ClickHouse 的磁盘增长曲线、Celery 的任务队列长度。这五个指标是我每次排查系统问题时的第一站。它们能告诉我的信息量,比任何业务面板都大。
日志不是用来给领导看的,也不是为了出事故报告时有内容可写。日志是系统的“黑匣子”,没有黑匣子的系统,出了事故就只能靠猜。靠猜排查故障的效率,大约只有有日志排查方式的十分之一。
6. 写在最后的一点经验
PLFM_RADAR 这个项目做到现在,我最满意的一点,不是它帮我节省了多少人工统计时间,也不是它带来了多少业务洞察,而是它把“感知数据”这个事变成了一种可以复用的能力。任何一个新平台接入,我只需要写一个新的适配器、配两条规则就能上线,整个过程通常不超过半天。
如果你也想做一个类似的多平台监控系统,我的建议很简单:想清楚哪个平台、哪份数据最值得关注,先解决十分钟内的痛点,再考虑一个月后要什么形态。贪多嚼不烂,这一类系统最怕一上来就铺开十几个平台——你会在数据维护的泥潭里被拖垮。选两个核心平台,把链路跑通,把数据存下来,你就已经赢了八成。后续每一次新平台的接入,都是顺势而为。