十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Elasticsearch 7.6集成Carrot2实现搜索结果聚类实战指南

Elasticsearch 7.6集成Carrot2实现搜索结果聚类实战指南 简介这是用于Elasticsearch 7.6.0的Carrot2聚类插件包面向需要为搜索系统添加结果聚合能力的开发者与数据分析师。插件将开源聚类框架Carrot2无缝集成到ES查询流程中支持Lingo、Stemmer、Diversified及Lingo3G等多种算法可根据业务场景自动将文档组织为结构化主题显著提升海量数据下的检索导航效率。压缩包共35个文件核心为两个jar——carrot2-core-4.0.0-beta3.jar和elasticsearch-carrot2-7.6.0.jar前者提供聚类算法与数据逻辑后者实现ES接口对接另含plugin-descriptor.properties、plugin-security.policy、config.yml等配置与安全文件以及30个utf8编码的多语言停用词表便于多语种使用。整体仅647KB体量轻巧部署方便。资源已获得275人学习下载适合正在使用ES 7.x并希望增强搜索结果聚合展示的开发者参考使用。 如果你维护过任何带站内搜索的 Elasticsearch 服务大概率会遇到同一个场景用户搜同一个词背后其实是好几种完全不同的意图。拿我们当时做的技术文档搜索来说输入“分布式事务”有人来找 Seata 接入指南有人想对比 TCC 和 Saga还有人在翻两阶段提交的源码分析。ES 的相关性排序只能按分数把结果拉成一条直线用户得自己在一百多个条目里筛体验很差。我当时的解法是给 Elasticsearch 7.6.0 装上 elasticsearch-carrot2 插件让搜索结果按语义自动分组。运行一段时间后页面点击深度明显改善。这篇文章就把我在 7.6.0 上装插件、调接口、处理中文聚类、压测排错的过程完整写出来给同样在用 ES 7.6 做站内搜索、又对结果聚类感兴趣的同学一个可复用的参考。1. 搜索结果聚类是个什么需求先搞懂 Carrot2 解决的问题1.1 从一次搜索体验说起先说个我在业务里反复遇到的观察。用户搜“mac 风扇”系统把所有匹配的记录按相关性排出来第一条可能是“如何拆机清灰”第二条是“Mac 风扇转速控制软件推荐”第三条是“外接散热底座横评”。用户要找的是“风扇转速控制软件”他就得自己往下翻很多条才能找到。这不是 ES 排序没做好而是用户意图在查询词里根本没有体现。关键词搜索天然有歧义靠 BM25 这类相关性打分很难消解。解决办法里结果聚类是成本最低的一种让系统把返回的这批文档先自动分组每组给一个简短标签用户在左侧导航里点“转速控制”就能立刻过滤出那一小簇。这比重新训练模型、做语义向量要快得多而且不需要改索引结构纯查询后处理就能实现。Carrot2 插件在 Elasticsearch 里干的正是这件事。1.2 Carrot2 是什么开源文本聚类引擎的三个算法Carrot2 是一个专门做搜索结果聚类的开源引擎它不负责索引也不负责排序只负责把你喂给它的文档集合切成若干簇。最常用的算法有三个我整理成一张表方便对比算法特点适合场景Lingo标签可读性最好基于词项文档矩阵分解搜索结果页、文档站、标题清晰的文档STC速度最快基于后缀树找公共子串候选集非常大的场景对延迟敏感KMeans 系列需要预设簇数量传统聚类玩法业务上已经能估计出主题数量我在 7.6.0 上大部分时间用的是 Lingo因为搜索结果页更讲究标签可读性不是单纯分组速度。Lingo 的“标签”是一组短词或短语用户一眼能懂。STC 虽然快但生成的标签经常是句子片段阅读观感差一些。如果你的候选集只有一两百条Lingo 的耗时完全能接受。1.3 它和 ES 自带 terms 聚合的边界在哪里这里有个很多人问的问题ES 自带的 terms 聚合不就能分组吗为什么还要多装一个插件因为 terms 聚合的本质是字段值枚举比如按文档里的 brand 字段分组所有值为“Apple”的文档聚到一起。它无法理解两篇没有共同标签的文档在语义上是同一类。Carrot2 做的事情是后处理ES 先把相关性排序后的 hits 返回来插件立刻在内存里对这批文档的 title/content 文本本身做聚类所以它不依赖任何预先设计好的字段。代价也在这每一次搜索都额外消耗 CPU而且聚类结果只对当前返回的这批 hits 负责翻页后不一定稳定。所以它不是聚合 API 的替代品更像是一个搜索体验增强组件。你需要把“精确过滤”和“语义探索”分开来用后者才是 Carrot2 的主场。2. 版本配对与离线安装7.6.0 的坑从这里开始2.1 为什么文件名里的 7.6.0 一个数字都不能差我第一次在这个坑里栽跟头就是没有仔细看文件名。Elasticsearch 插件和 ES 核心版本是严格绑定的尤其 7.x 之后插件在加载阶段会做版本校验。你下载的是 elasticsearch-carrot2-7.6.0.zip就只能装到 7.6.x 上7.5 会拒绝加载7.7 也大概率起不来。我当时图省事在一台 7.5.2 的测试环境上装了 7.6.0 的包启动日志直接报 plugin 版本不兼容。所以拿到 zip 之后第一件事不是安装而是确认 ES 版本。用elasticsearch --version看一眼或者直接看解压目录里的 version 文件。2.2 Linux 与 Windows 下的安装命令Linux 上ES 通常部署在/usr/share/elasticsearch进入目录后执行cd /usr/share/elasticsearch bin/elasticsearch-plugin install file:///tmp/elasticsearch-carrot2-7.6.0.zip如果你的 zip 在其他目录把file:///后面跟的绝对路径写对就行。安装过程会问你权限确认默认选 y。Windows 上假设 ES 解压在D:\elasticsearch-7.6.0zip 放在D:\downloads\打开 PowerShell 或 CMD 进入 ES 目录cd /d D:\elasticsearch-7.6.0 bin\elasticsearch-plugin.bat install file:///D:/downloads/elasticsearch-carrot2-7.6.0.zip这里有个细节要注意Windows 的 file URL 写法是file:///后面直接跟盘符且路径里的反斜杠要全部换成正斜杠。我第一次用file:///D:\downloads\...就报路径不存在。安装完成后重启 ESWindows 上如果注册成了服务就重启服务如果手动启动的bin\elasticsearch.bat停了重新跑。2.3 装完怎么确认真的生效了装完别急着写代码先确认插件真的加载了。执行bin/elasticsearch-plugin list能看到carrot2之类的条目。然后看 ES 启动日志重启后应该有一行类似插件加载成功的记录。如果 list 有记录但启动日志没有多半是插件目录权限不对或者 ES 是旧进程没真正重启。另外一个容易被忽略的点开了 xpack.security 之后后续所有_search请求都要带认证信息否则插件内部调用节点客户端时会拿到 401表现为查询正常但聚类结果缺失。这个我排查过很久最后发现是认证问题。3. 把聚类跑起来一次完整请求里发生了什么3.1 在 _search 请求体里挂上 carrot2 配置插件装好以后实际使用的方式比想象中简单在普通_search请求体里加一个carrot2段。这是插件为 ES 搜索请求注册的扩展结构ES 原生解析器会把这段交给插件接管。我的 7.6.0 上最常用请求长这样POST /knowledge/_search { query: { match: { content: 分布式事务 } }, size: 200, carrot2: { algorithm: Lingo, language: Chinese, fields: [title, content], maxClusters: 8, minClusterSize: 3 } }size决定了聚类窗口。默认搜索只回 10 条拿 10 条做聚类没有实际意义我会至少调到 100推荐 200 到 500。fields是参与聚类的文档字段language我选了Chinese如果你的文本是中英文混排可以不写让 Carrot2 自动识别。这里提醒一句不同 ES 主版本上这个自定义参数的结构有过调整2.x 和 5.x 的写法就有差异。如果你照上面的格式请求报 unknown field先去看对应版本仓库的 README而不是怀疑插件没装。3.2 响应里的 clusters 怎么读、怎么用请求发出后hits 部分和普通搜索一样聚类输出会在响应末尾多出一块结构核心是 clusters 数组。每个簇一般包含 label标签文本、score簇的置信度、docCount簇内文档数以及 docIds对应 hits 数组里的文档索引。我用 Python 脚本拿响应时会先打印 label 和 docCount再根据 docIds 回原 hits 里映射文档标题。整个响应字段名在不同小版本上可能略有差异我建议第一次调试时把响应体完整打印出来以实际键名为准比猜字段可靠。前端渲染时我会把 clusters 渲染成搜索页左侧的“按主题浏览”区块。用户点击某个标签前端就把当前结果按对应 docIds 过滤或者把标签作为二次筛选条件重新请求。这样做下来用户找到目标页面的路径短了很多搜索页的整体跳出率也有下降。3.3 中文文档输入分词器和字段选择是关键中文文档是 Carrot2 使用中最大的变量。Carrot2 虽然支持中文语言识别但它默认的预处理对中文没有天然的分词能力如果字段里直接放整段中文句子聚类质量会很差标签会变成一长串重复的短语。我在项目里的做法是索引里专门准备title_ik和content_ik字段用 IK 的ik_max_word分词查询时把这两个字段喂给 Carrot2。同时提前把 HTML 标签、模板占位符、版权页脚这些噪声去掉否则聚类标签会被p、版权所有、/api/xxx这类词污染。说白了Carrot2 对输入文本的干净程度要求比 ES 索引高得多。在 ES 里能搜出来不表示文本可以直接送聚类。我建议在写入索引之前就做好清洗不要等到查询阶段再处理否则每个请求都要做一遍字符串清理浪费 CPU 还容易漏。4. 实测踩过的坑启动失败、标签乱飞、延迟飙高4.1 启动期报错版本校验和 JDK 版本冲突第一次装插件的人最容易遇到的是 ES 根本启动不了。典型日志是plugin [carrot2] is incompatible with version [7.6.0]这个就是版本没配对。另外还有一种隐蔽情况ES 7.6 自带 JDK但如果你在JAVA_HOME环境变量里指了一个很老的 JDK 8插件编译进来的 class 文件会抛出UnsupportedClassVersionError。解决方案是启动前检查java -version确保用的是 ES 自带的或者 JDK 11 以上。Windows 上还有一个坑之前用低版本 ES 注册过服务升级到 7.6 后服务指向的还是旧目录插件装了却一直加载不进来。我的建议是装插件后不要只重启服务用bin\elasticsearch-service.bat stop和start完整停启一遍确认日志里真的出现了插件加载记录再继续。4.2 聚类质量翻车标签全是废话的根因聚类结果全是废话大部分时候不是算法问题而是进料问题。我自己总结了一个排查顺序第一看字段内容把参与聚类的字段原始值打几条出来确认没有混入 HTML、URL、JSON 序列化残留第二看分词中文必须走 IK没分词的字段聚类标签几乎必乱第三看语言配置数据是中文却写死 English标签会四不像第四看 minClusterSize设得太大比如 10会漏掉小主题设得太小又会出现大量单文档噪声簇。另外Lingo 的标签质量很依赖标题。如果文档没有清晰的标题字段可以把正文前 50 个字拼进一个虚拟 title 字段再喂进去实测对标签可读性有帮助。这些调整需要结合人工抽检不要只看一两个请求就下结论。我会周期性地把聚类日志导出来随机抽几十个标签给产品同事看让他们判断是否和用户认知一致。4.3 性能与超时Lingo 不是免费的午餐性能这块我实测过8 核 16G 的节点上500 条结果用 Lingo 聚类额外耗时大约在 100 到 300 毫秒同样 500 条如果把候选集从 500 涨到 1000耗时不是线性增长而是接近翻倍。原因在于 Lingo 要对词项文档矩阵做分解文本越杂、词项越多开销越大。所以我把size限制在 500 以内并对聚合搜索入口加超时控制服务端控制在 1 秒到 1.5 秒。如果你对延迟很敏感可以先用 STC 算法顶上等业务验证了聚类价值再切回 Lingo。还有一个容易忽略的点_search请求里如果同时挂了大量 terms 聚合和高亮CPU 会瞬间飙高。我的做法是聚类入口单独用一个不带复杂聚合的查询模板避免所有功能挤在一个请求里互相拖累。5. 生产化落地的取舍什么时候该用什么时候别硬上5.1 适合聚合类搜索场景不适合高频精确查询值不值得上 Carrot2取决于你的搜索词意图分布。我自己的判断标准是如果搜索词是名词性强、意图分散的比如课程平台搜“Java”、电商搜“充电宝”、文档站搜“分布式事务”聚类收益非常明显反过来如果系统大量是精确 ID 查询、编号查询、长尾精确词聚类基本是浪费 CPU。Carrot2 的聚类结果是统计意义上的近似分组不是精确过滤不能拿来做权限隔离、库存过滤这类需要严格逻辑的场景。我不会把它放在核心交易链路而是放在帮助中心、文档站、课程检索这类“帮助用户探索”的页面上。这类页面用户本身没有明确的目标 URL需要系统引导才能发现内容聚类导航刚好补上这个空缺。5.2 和高亮、分页、排序一起用时的注意事项和高亮、分页、排序一起用的时候有几条经验。第一高亮模板不要直接把带em的字段传给 Carrot2否则标签里会到处是标签字符我会在_source里过滤只返回原始 text聚类和展示字段分开。第二聚类只对当前返回的 hits 有效分页一深聚类窗口就变了标签也不稳定所以前端只在第一页展示聚类导航点击后走普通二次查询不要求聚类标签跨页一致。第三排序发生在聚类之前Carrot2 拿到的始终是排序后的结果集所以不用担心中途打乱顺序。这些协作关系理顺之后插件的改动面其实很小基本只加了一个carrot2段和一个渲染组件。如果需要关闭聚类直接把请求体里的carrot2段去掉就行ES 普通查询完全不受影响这对灰度发布很友好。5.3 我的最终建议与一个小技巧最后说点个人体会。我在 7.6.0 上用这个插件跑了大概半年最大收获不是技术上的而是让产品同学意识到“搜索结果的下一步动作”比“搜索结果本身”更重要。用户点开聚类标签是一次清晰的意图确认比单纯猜他要什么靠谱得多。如果你也想在项目里试建议分三步走先在测试环境用真实搜索词录日志离线跑一批聚类看标签质量再上灰度只对一个搜索入口开启聚类对比点击深度和跳出率最后再逐步扩大范围。另外记得把安装 zip 和对应版本号记到部署文档里ES 升级时这个插件要跟着升级。我有一次升级 ES 漏了插件整套搜索在聚类环节断了半天教训挺深刻。真遇到了问题优先看插件日志而不是 ES 主日志排查路径会短很多。本文还有配套的精品资源点击获取
返回列表