ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

商品搜索|利用AI模拟问答后的问题深度复盘(含ES设计、线上排查、性能优化)

商品搜索|利用AI模拟问答后的问题深度复盘(含ES设计、线上排查、性能优化) 最近复盘自己电商项目的商品搜索模块并利用AI模拟了一些深度问题以下为问题梳理。Q问题A标准回答M我最开始的答案的问题Q1介绍一下实现的商品搜索模块整体流程从用户输入搜索关键词开始到最终返回商品列表中间经过了哪些步骤为什么不用 MySQL 的 like 查询而选择 ElasticsearchA商品搜索模块主要基于Elasticsearch实现。用户输入关键词后请求进入搜索服务ES根据配置的分词器对关键词进行分析然后利用倒排索引快速定位相关商品文档同时支持按照价格、销量、新品等条件排序并返回商品列表和筛选条件。相比MySQLMySQL更适合结构化数据查询虽然有B树索引优化但是对于商品名称这种全文搜索场景需要支持分词、关键词匹配以及相关度排序而ES基于倒排索引更适合这种搜索业务。M我之前对这个问题的回答存在两处明显漏洞理解不够精准表述也不够专业第一处错误我模糊说“业务层对关键词分词”这个说法是不准确的。真正的流程里分词不是业务代码完成的用户输入关键词后是ES根据字段配置的IK分词器在内部搜索分析阶段完成分词、词条匹配业务层只负责调用ES接口不参与分词逻辑之前混淆了业务层和ES的职责边界。第二处严重缺失完全没有提及ES的存储数据细节。面试官一定会追问ES索引存储的数据内容这是搜索模块的核心关键点。我之前的回答只讲了查询流程没说明ES会预存商品id、名称、分类、品牌、价格、销量等搜索所需字段搜索命中后再按需查MySQL补全详情这是MySQL和ES分工的关键。第三处表述不严谨我之前错误提及MySQL是“从上到下一条条搜索”这个说法很容易扣分。MySQL是有B树索引的并非全表扫描正确的逻辑是MySQL不擅长分词匹配、相关度排序这类全文检索场景而非单纯查询速度慢。Q2你的商品数据是如何同步到 Elasticsearch 的比如商品新增、修改、删除后ES里面的数据如何保证和 MySQL 保持一致A商品数据采用MySQL和Elasticsearch双写同步。管理员操作商品时首先修改MySQL中的商品数据然后调用搜索服务同步ES。同步过程中会把数据库中的商品详情对象转换成ES专用的搜索实体只保存搜索需要的字段。新增和修改商品使用save更新ES文档商品下架或者删除时根据商品id删除ES中的文档。这样保证MySQL作为业务数据源ES作为搜索数据源。M我之前的回答有一个致命错误完全不符合项目实战逻辑我之前错误认为商品修改后需要清空ES全部数据再重新同步这是非常低级的认知错误。真实项目中如果商品数据量达到几十万、上百万每次单商品修改就清空全量ES数据同步会造成ES写入压力爆炸、服务卡顿、数据延迟极高完全不可用。结合我的项目源码正确的同步逻辑是精准单条数据同步新增/修改商品时先更新MySQL再调用syncGoodsToES()方法将单条商品数据转为GoodsES实体通过save方法单条更新ES删除/下架商品时根据商品id单条删除ES文档不会批量清空数据。另外我之前随口提到“消息数据同步”这也是不严谨的。我的项目中没有使用MQ消息队列主动提及会被面试官追问细节答不上来直接扣分后续回答必须贴合自身项目不额外延伸未用到的技术。Q3你的商品搜索使用了 Elasticsearch,你为什么要设计 单独的 GoodsES 实体类而不是直接把数据库里的 Goods 实体类存入 ElasticsearchAMySQL的Goods实体是面向业务存储设计的只保存商品主表字段而ES是面向搜索场景需要品牌、分类、规格等多表的冗余字段。所以单独设计GoodsES实体同步时把多张表的数据组装聚合转换成搜索需要的结构存入ES提升检索效率。M我之前的回答核心方向对了但理由存在明显偏差解释得很不专业我之前错误说“ES无法直接识别前端JSON数据”这是错误认知。ES本身就是基于JSON格式存储数据完全可以识别Java序列化后的JSON对象不存在识别不了的问题。真正的核心原因是实体设计的场景不同MySQL的Goods实体是为业务存储设计的字段精简只保留核心业务字段而GoodsES是纯搜索场景设计的实体需要聚合品牌名称、分类名称、规格参数、搜索标签等多表冗余字段专门适配全文检索、筛选、聚合的需求所以必须单独封装实体适配搜索业务。Q4你的ES索引中商品名称 goodsName 使用了 IK 分词器而品牌 brand 使用了 keyword 类型为什么这样设计为什么不能所有字段都使用分词A商品名称goodsName是全文检索场景用户输入关键词需要IK分词拆分词语依靠倒排索引做模糊匹配所以用textIK分词。brand是筛选过滤条件需要完整精确匹配不能分词拆分因此用keyword类型。如果全部字段都分词像品牌这类筛选字段会被切分造成误匹配筛选结果出错。M这道题我的标准答案逻辑是通顺的结合我的项目索引设计核心逻辑完全匹配我项目中goodsName采用text类型IK分词器专门用于商品名称的全文模糊检索brand采用keyword类型完整保留品牌字符串实现精准筛选过滤。所有字段不能统一分词否则精准筛选字段会被拆分导致搜索结果错乱这部分知识点我已掌握无核心漏洞。Q5你的商品搜索支持价格区间查询比如最低价和最高价。你在ES中是怎么实现价格范围查询的为什么价格字段不适合使用text类型A商品价格查询属于范围查询不是关键词匹配。用户传入最低价和最高价后我会构造ES的RangeQuery如果有最低价使用gte条件如果有最高价使用lte条件对price字段进行过滤。价格字段不能使用text因为text主要用于全文检索会进行分词而价格需要支持范围比较和排序所以应该使用数值类型。M我之前的回答存在表述错误和要点缺失的问题第一处错误我之前说“先检索出最高价和最低价”完全偏离业务逻辑。实际业务中是用户主动传入最低价、最高价参数我的代码直接构造RangeQuery通过gte、lte匹配价格区间不是程序先查询价格极值。第二处缺失要点没有讲透价格不用text类型的核心原因。text类型会对内容分词只适用于文本检索不支持大小比较、区间查询、排序而价格需要频繁做区间筛选和排序必须用double等数值类型这也是我项目中price字段设计为double类型的核心原因。Q6做商品搜索时页面要展示当前搜索结果对应的品牌、分类筛选项。为什么不查MySQL而是用ES聚合来获取这些筛选维度A因为筛选条件是当前搜索命中商品的子集维度。ES支持聚合直接在搜索结果内部统计出品牌、分类、规格不需要二次查询数据库减少数据库IO输入输出响应更快。如果去数据库查需要拿到ES返回的商品id再去MySQL做in查询会增加数据库压力性能较差。M我之前的回答结论正确但对底层逻辑的理解不够透彻没有讲清楚ES聚合的核心优势ES聚合的核心是查询时同步统计无需将数据加载到Java内存处理。比如搜索“手机”ES在返回商品列表的同时会自动对命中的文档做分组统计筛选出当前结果对应的品牌、分类只统计本次搜索的子集数据而非全量数据库数据。如果改用MySQL查询需要先获取所有命中商品ID再通过in查询数据库多一次DB请求大幅增加数据库IO压力。ES聚合全程在搜索引擎内部完成性能优势非常明显这也是项目选用ES聚合做筛选维度的核心原因。Q7你的搜索服务对外提供了接口如果有一天线上发现搜索响应非常慢接口超时率明显上升你从何入手排查请说出你的排查思路和可能的原因不限于ES本身。A我会先从日志看请求参数是否异常是不是由于输入了特殊字符或者是用户输入的字段过长导致的响应变慢然后看ES的监控重点看看当前ES的CPU、内存、查询响应时间。如果 CPU 飙高说明查询太重可能是 查询语句写得不合理或者索引数据量太大没有做分页限制 的原因如果ES正常再看下游的MySQL和Redis查 MySQL的慢查询日志、Redis的响应时间超时最后检查网络和中间件。是不是网络抖动或者Nacos/网关转发有延迟。按这个顺序逐步缩小范围。M我之前的回答思路是对的但缺乏工程化排查思维排查逻辑不够清晰、优先级混乱正确的线上排查核心是从最简单、最高效的维度开始逐层缩小范围1. 优先排查请求本身看日志区分是个别请求慢还是全部请求慢排查是否存在超长关键词、特殊字符导致解析异常2. 排查核心服务ES查看监控CPU、内存、响应时间判断是否存在查询语句不合理、无分页、未使用缓存等问题3. 排查下游依赖ES无异常时检查MySQL慢查询、Redis超时等问题4. 最后排查网络、网关、服务GC等底层问题。我之前只是罗列了排查点没有明确优先级真实线上排查必须遵循「先简单后复杂、先业务后底层」的原则避免盲目排查。Q8你的搜索接口支持按销量和价格排序。如果用户要求“按价格从低到高”排序ES内部是如何实现的这种排序对性能有什么影响如果价格字段数据量很大比如上千万商品排序会变慢吗为什么你怎么优化AES 搜索是基于倒排索引实现的而排序依赖 doc values正排索引ES 会把命中的商品的价格全部加载到内存里进行全局排序。如果对字段数据量非常大的做排序会非常吃内存和 IO可能导致查询变慢甚至 OOM。因为ES排序不是免费的数据量大了会慢深分页会慢对text字段排序会慢。优化手段包括限制分页深度、用更轻量的排序方式比如 search_after、设计索引时避免对 text 字段排序。最笨最贵的优化方式就是加内存。M我之前的回答只记住了结论但对底层原理理解模糊存在概念混淆第一我混淆了排序和聚合的内存逻辑二者都依赖doc values但排序需要将所有命中文档的排序字段值全部加载到内存内存消耗远大于聚合数据量大时极易触发OOM内存溢出第二我对深分页的原理理解不深项目中用fromsize分页from值越大ES需要遍历的数据越多排序性能指数级下降第三没有讲透字段类型的重要性text字段无法用于排序只有数值、keyword类型支持排序这是索引设计的基础规范也是我之前的知识盲区。整体来说我只会调用ES排序API但不了解底层执行逻辑后续需要重点吃透ES排序、分页的底层原理应对深度提问。Q9你搜索商品的时候如果用户输入“手机”关键词能正常返回结果。但如果输入“手机 华为 5000”这种包含价格和品牌混合的搜索词你的搜索接口能正确处理吗如果不能问题出在哪里你会怎么设计来支持这种“自然语言混合搜索”A用户输入的搜索词需要先做意图识别拆分成不同维度的条件而不是直接全部交给分词器。我会把输入词按空格拆分分别判断如果包含“华为”这种品牌词走品牌过滤如果包含“5000”这种数字走价格范围过滤剩余部分走商品名称的全文搜索。然后组合成ES的bool查询把must和filter拼在一起这样才能做到精准搜索。M我之前的回答给出了解决方案但没有讲透核心问题根源回答不够完整核心问题是IK分词器只能处理文本词条无法识别数值、品牌这类特殊语义。像“手机 华为 5000”这种混合搜索词如果直接交给分词器会把价格数字“5000”当成普通文本关键词匹配而非价格区间条件导致搜索结果错乱出现名称含5000但价格超标的无效商品。同时我只说了拆分、组合查询没有补充具体实现方式。完善的方案需要通过正则识别数字、自定义品牌词典识别品牌词拆分出文本、品牌、价格三类条件再组合bool查询精准匹配搜索需求这部分细节是我之前缺失的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进