不能直接用 followers_count 字段存粉丝数,因为高并发写入会触发单文档锁且无法检测异常波动;应采用嵌入式时间序列结构,存储每小时快照与滑动统计,通过离群公式实时判断并异步告警。

为什么不能直接用 followers_count 字段存粉丝数
因为超大账号(比如千万级粉丝)的粉丝增长不是线性的,而是受热点、转发裂变、平台限流等强非线性因素驱动。如果只存一个整型 followers_count,每次更新都要 $inc,高并发写入会卡在单文档锁(尤其 MongoDB 5.0 前默认文档级锁),同时无法追溯异常突增/断崖下跌——而这些恰恰是风控和运营最关心的离群信号。
用嵌入式时间序列 + 统计元数据建模离群值
核心思路:不只记“当前值”,而是把最近 7 天每小时的粉丝快照 + 滑动统计打包进一个子文档,让离群检测逻辑下沉到数据结构里,避免每次查都要聚合计算。
示例文档结构:
{
"_id": "user_123456",
"handle": "tech_news",
"follower_history": {
"samples": [
{ "ts": ISODate("2024-04-01T00:00:00Z"), "val": 9876543 },
{ "ts": ISODate("2024-04-01T01:00:00Z"), "val": 9876548 },
...
],
"stats": {
"mean": 9876545.2,
"stddev": 3.8,
"last_delta": 5,
"is_outlier": false
}
}
}
关键点:
-
samples固定长度(如 168 个元素),用$push+$slice维护 FIFO 队列,避免数组无限膨胀 -
stats字段由应用层或 Change Stream 触发的轻量函数实时更新,不依赖$group聚合 - 判断离群用
abs(current_delta - stats.mean) > 3 * stats.stddev,比单纯看绝对增量更鲁棒 - 不要用
$where或 MapReduce 做实时离群扫描——它们无法利用索引,且会拖慢主库
如何安全触发告警而不阻塞写入
离群检测必须异步,否则写入延迟会随粉丝量指数上升。推荐用 MongoDB 的 Change Stream 监听 follower_history.samples 更新,再投递到消息队列(如 Kafka)做后续分析。
容易踩的坑:
- Change Stream 默认不包含完整文档,需开启
fullDocument: "updateLookup"才能拿到stats新旧值对比 - 如果用
db.collection.watch()在应用进程内消费,务必设置maxAwaitTimeMS防止长轮询饿死连接池 - 告警阈值别硬编码——存到独立集合
outlier_rules里,支持按账号类型(媒体/个人/KOL)动态加载
索引与分片必须配合离群模式设计
单纯对 handle 建唯一索引没用,查询离群账号时实际走的是时间范围 + 统计条件。需要复合索引:
db.accounts.createIndex({
"follower_history.stats.is_outlier": 1,
"follower_history.stats.last_delta": -1,
"follower_history.samples.ts": -1
})
分片键更要小心:_id 或 handle 分片会导致离群扫描变成全分片广播。建议用 floor(follower_history.stats.mean / 1000000) 这类桶化字段作为分片键辅助字段,让同一量级账号尽量落在同片,减少跨片查询。
真正难的不是建模本身,而是当某条 samples 数组因网络重试被重复写入两次时,stats 里的 stddev 会瞬间失真——这要求所有写路径必须带幂等 token,并在更新前校验样本时间戳是否严格递增。

















