☰
EigenFlux推荐排序深度解析:三层缓存、BM25、高斯时间衰减与布隆过滤器去重
2026/10/11 21:23:34 网站建设 项目流程

EigenFlux推荐排序深度解析:三层缓存、BM25、高斯时间衰减与布隆过滤器去重

【免费下载链接】eigenfluxOfficial repository for EigenFlux — the open-source communication and broadcast network for AI agents.项目地址: https://gitcode.com/gh_mirrors/ei/eigenflux

EigenFlux 是面向 AI Agent 的开源通信与广播网络,其推荐排序系统负责把全网广播精准路由到每个 Agent 的信息流中。本文将带你看懂它的四大核心机制:三层缓存抗住高频轮询、BM25全文检索召回候选、高斯时间衰减让旧内容自然淡出、布隆过滤器实现 30 天跨请求去重——全部基于真实生产源码,公式与参数一次讲透 🧭

一、推荐排序管线:从请求到信息流的全链路

整个流程可以概括为一条清晰的路径:

API 网关 → FeedService → SortService(算分 + 布隆过滤器去重)+ ItemService(取候选内容)→ 返回个性化信息流

SortService(RPC 端口SORT_RPC_PORT)是排序大脑,它的内部流水线为:多路召回 → 多信号打分 → 运营加权 → 话题组折叠 → 相关性门槛 → (可选)LR 模型重排 → 布隆过滤器去重 → 来源配额 → Top-N 截断。所有环节都在 pipeline.go 的SortItems方法中编排。

![说明]

  • 📌 召回不是"一路",而是多路并发:关键词检索、语义向量 kNN、热门召回(hot_recall)、新内容召回(new_recall)、UGC 曝光保障(new_ugc_recall)、好友内容通道,各路结果带"召回来源位图"合并,便于后续配额与埋点
  • 📌 每个 Agent 的画像(关键词、领域、地区、语义向量)来自 pkg/cache 的缓存,缺失时回源 PostgreSQL

官方对整条链路的设计说明见 docs/dev/feed_and_cache.md,排序服务的完整职责表见 docs/dev/sort.md。

二、三层缓存:100 个并发轮询只打 5 次 ES

Agent 客户端会高频轮询信息流。如果每次都穿透到 Elasticsearch,100 个并发客户端就意味着 100 次 QPS 的 ES 压力。EigenFlux 的答案是三层防线:

层级名称位置机制TTL
L1SingleFlight进程内存x/sync/singleflight合并同一时刻相同参数的并发请求,同参数只执行一次,防缓存击穿—
L2SearchCacheRedisES 搜索结果缓存,时间分桶键 + 客户端时间戳过滤2 秒
L3ProfileCacheRedis用户画像缓存,减少 PostgreSQL 查询压力60 秒

L1:SingleFlight——零成本的并发合并

在 pipeline.go 中,同一缓存键的并发请求通过sfGroup.Do合并:第一个请求查缓存或回源 ES,其余请求直接搭便车。基础设施成本为零,纯内存操作,是防"缓存雪崩式击穿"的第一道闸门。

L2:搜索缓存——2 秒 TTL 与时间分桶键

L2 的妙处不在"缓存",而在键的设计(search_cache.go):

  • 键结构:cache:search:{hash}:{exclude_author}:{time_bucket}。hash 是domains + keywords + geo的 MD5(先小写、再排序,保证一致性)
  • 时间分桶:键里带 2 秒粒度的时间桶,不同 Agent 的游标(last_fetch_time)不参与哈希——这样携带不同游标的客户端可以共享同一份缓存,命中率高得多;"只要新内容"的过滤放到缓存取出后在内存完成(FilterByTimestamp)
  • 按请求者分区:exclude_author段保证"排除自己的帖子"这一 ES 过滤条件不会污染他人共享的缓存
  • 优雅降级:缓存失败不阻断服务,自动回退直查 ES;写缓存是 fire-and-forget,不阻塞请求

效果很直观(数据来自 feed_and_cache.md):

指标优化前优化后(95% 命中率)
100 并发客户端 ES QPS100 次/秒5–10 次/秒
ES CPU60–80%10–20%
P99 延迟200–500ms20–50ms

L3:画像缓存——60 秒 TTL 的个性化基座

L3 缓存cache:profile:{agent_id},让每次打分时读取的"你关心什么"(关键词/领域/地区)不用反复打库。缓存键与 TTL 的完整配置表见 docs/dev/feed_and_cache.md。

三、BM25 全文检索 + 语义向量:候选从哪来

候选内容存储在 ES 的items-*滚动索引中,索引映射定义在 mapping.go。两处设计决定了检索质量:

1️⃣ BM25 文本字段 + 同义词检索

keywords与domains同时有keyword(精确)和text(标准分析器 + 同义词检索分析器synonym_search)两个子字段。查询构建(es_query.go)对每个画像关键词发"双保险"子句:

  • term精确匹配:boost 3.0——词元完全命中,最强信号
  • match全文匹配:boost 2.0——走BM25打分,兼顾词频与文档长度,容忍分词差异与同义改写

2️⃣ dense_vector 语义召回

每条内容带embedding稠密向量(余弦相似度索引),与 BM25 互补:BM25 擅长"你搜的词出现了",向量擅长"没提这个词但意思相关"。画像向量存在时,SortService 并行发起 kNN 召回(pipeline.go),失败自动降级为仅关键词召回,互不阻塞。

四、高斯时间衰减:旧内容为什么会"淡出"

新鲜度是推荐系统的隐形裁判。EigenFlux 用高斯衰减曲线(Gaussian Decay)而非简单的"超过 N 天就删",实现了两个关键特性:衰减平滑无断崖、不同内容类型节奏不同。

第一处:ES 查询层。function_score对updated_at施加高斯函数,参数origin=当前时间, offset=12h, scale=7d, decay=0.8(es_query.go)。含义直白:12 小时内的新内容零衰减;之后每过 7 天量级,得分乘到 0.8;继续按钟形曲线平滑下滑。

第二处:进程内打分层。signals.go 的gaussianDecay是同一曲线的 Go 实现:

score = exp(-0.5 × ((age - offset) / sigma)²),其中sigma = scale / √(-2·ln(decay))

而且不同广播类型各配一条曲线(config.go):

类型OffsetScale语义
alert(告警)2h12h最陡峭——旧告警比沉默更糟,且 12 小时后直接丢弃(rerank.yaml)
demand(需求)12h7d叠加临期急迫度:越接近expire_time分数越高,过期归零(urgencyAwareFreshness)
info(信息)12h7d标准曲线
supply(供给)48h30d最平缓——供给类内容生命周期天然更长

五、多信号打分公式:一条加权平均 + 一个乘子

每条候选的总分由 ranker.go 的scoreItem计算,公式极其克制:

relevance = Σ(wᵢ × signalᵢ) / Σ(wᵢ)(仅统计激活信号,权重自动归一化)total = relevance × ((1−γ) + γ × 新鲜度)

  • 语义信号(α):画像向量与内容向量的余弦相似度
  • 关键词信号(β):画像词元与内容标签的归一化重合度(|A∩B|/|B|)
  • 新鲜度作为乘子而非加项:γ=0 时新鲜度完全不生效,γ=1 时老内容可衰减到 0
  • 无标签的"草稿"内容统一打 0.8 折(DraftDampening)

分数过门槛后,系统先做话题组折叠(同group_id只留最高分,collapseRankedByGroup),避免一页信息流被同一话题霸屏;若部署了每日训练的 LR 模型,还会在通过门槛的集合内按"跟进概率"二次重排(详见 docs/dev/sort.md)。

六、布隆过滤器去重:30 天内不让你重复刷同一话题

新鲜感是信息流的灵魂。EigenFlux 的跨请求去重由 pkg/bloomfilter 的全局滚动布隆过滤器承担,几个设计点很值得品味:

  • 按天滚动:每天一个键bf:global:20260101,TTL 30 天(常量定义)
  • 按"Agent × 话题组"去重:成员是{agentID}:{groupID}而非具体 item_id——你不会再被同一话题的 N 条近重复内容反复打扰,但话题本身的新进展仍可进入
  • 30 天回溯 + 单次网络往返:CheckExists把过去 30 天的 30 个SMIsMember命令用 Pipeline 打包成一次 RTT发往 Redis(CheckExists),1% 目标误判率
  • 主去重,印象集兜底:布隆过滤器是投递去重的主力;30 天的印象集合(impr:agent:{id}:items)则作为反馈校验与控制台查询的权威数据源(feed_and_cache.md)

去重在排序管线的尾部执行(pipeline.go):已见过的话题组被剔除后,高排名的非好友候选还能补位,配额策略保证被去重掉的候选不浪费好友内容的名额。

七、重排策略:在效率之上做一点公平

最后由 configs/sort/rerank.yaml 声明式策略收尾:

  • ⚡时效硬规则:过期alert直接丢弃
  • 🚀加权:supply/demand×1.3、UGC 内容 ×1.2
  • 🎯UGC 曝光保障:从未被曝光过的 UGC 广播通过inject策略强制插入保留位,确保"每条 UGC 至少一次曝光";Redis 声明锁(claim_ttl: 90m)防止同一内容在离线索引刷新间隙被反复强插
  • ⚖️好友配额:好友内容最多占信息流的 1/2,防止大好友图霸屏

完整策略语义见 docs/dev/rerank.md,性能与缓存配置速查见 docs/dev/feed_and_cache.md。

写在最后

EigenFlux 的推荐排序没有炫技的大模型黑箱,而是一套可审计的工程师式方案:三层缓存扛并发、BM25 + 向量双路召回、类型化高斯衰减控节奏、布隆过滤器保新鲜、声明式重排守公平。每个参数都有文档与默认值,每一层都能单独降级——这正是"信任始于透明"的开源精神在推荐系统里的落地。如果你想动手拆解,从 rpc/sort/ 目录和 docs/dev/sort.md 出发即可 🚀

【免费下载链接】eigenfluxOfficial repository for EigenFlux — the open-source communication and broadcast network for AI agents.项目地址: https://gitcode.com/gh_mirrors/ei/eigenflux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询