尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

电商搜索架构演进:从单体到Search Agent的AI化实践

电商搜索架构演进:从单体到Search Agent的AI化实践 1. 项目概述为什么电商搜索必须经历这场“认知革命”我做电商搜索系统落地已经八年从最早用 MySQL LIKE 模糊查商品标题到后来搭 Solr 集群扛住双十一流量洪峰再到去年把整个搜索链路重构成 Search Agent 架构——这三段经历让我彻底明白一件事电商搜索从来不是“找得到”而是“猜得准”。标题里说的“从单体到 AI 搜索”表面看是技术栈升级实则是搜索系统从“被动响应查询”转向“主动理解意图”的范式迁移。它不只关乎 Solr 或 Elasticsearch 怎么配、微服务怎么拆更核心的是当用户输入“送妈妈的生日礼物”系统该优先返回口红还是按摩仪当搜索词只有“夏天穿的”要不要自动补全“连衣裙”“冰丝裤”“防晒帽”这些决策背后不再是规则引擎或 TF-IDF 权重表能解决的而是需要语义理解、多源协同、实时反馈的 AI 能力。这个进化路径不是凭空画饼。我参与过的三个典型项目印证了它的必然性第一个是某母婴垂直平台单体搜索在大促时 QPS 突破 8000 就开始超时运维半夜改 JVM 参数都压不住第二个是某综合电商用微服务把搜索拆成 query 解析、召回、排序、聚合四个服务但各环节仍靠人工调参新品上架后搜索曝光率下降 40%第三个就是现在正在跑的 Search Agent 架构把“用户搜什么”和“用户真正要什么”之间的鸿沟用轻量级 LLM 规则引擎 实时行为反馈闭环填平。它不是抛弃 Solr而是让 Solr 只干它最擅长的事——高效倒排索引检索也不是推翻微服务而是让每个微服务变成可插拔的“智能模块”。适合谁来看如果你正被搜索转化率卡在 2.3% 上不去、被运营反复追问“为什么爆款搜不到”、或者刚接手一个年久失修的单体搜索系统这篇就是为你写的。它不讲虚概念只拆真实场景里的每一步怎么做、为什么这么选、踩过哪些坑。2. 整体架构演进逻辑单体 → 微服务 → Search Agent 的三次跃迁2.1 单体搜索的“甜蜜陷阱”与崩溃临界点早期电商搜索几乎全是单体架构一个 Java Web 应用内置 Lucene 或 Solr 嵌入式实例前端请求进来经过分词、查询解析、打分排序最后返回 JSON。这种结构开发快、部署简单上线三天就能跑通基础搜索。但它的“甜蜜”背后埋着三颗定时炸弹第一颗是资源争抢。搜索服务和商品详情页、购物车共用同一套 Tomcat 线程池和 JVM 内存。大促期间搜索 QPS 暴涨线程池被打满连带详情页也 503。我见过最极端的案例某平台在 618 零点搜索接口耗时从 80ms 拉到 2.3s而商品详情页同步卡死客服电话被打爆——根本原因是 Solr 的 IndexWriter 在合并 segment 时占用了全部 CPU而应用层毫无感知。第二颗是扩展僵化。想提升吞吐量只能垂直扩容——换更高配服务器。但 Solr 的 JVM Heap 超过 32GB 后GC 停顿时间会指数级增长。我们试过把单节点堆内存从 16G 加到 48G结果 Full GC 平均每次停 8.7 秒比用户耐心还长。横向扩容单体架构下 Solr 集群的主从同步延迟、分片路由策略、查询合并逻辑全得自己写代码量比业务逻辑还多。第三颗是能力耦合。搜索排序逻辑硬编码在 Java 里比如“销量权重 * 0.3 评分权重 * 0.5 新品标签 * 0.2”。运营想临时加个“618 专属权重”得改代码、走发布流程、重启服务。有次为赶活动开发连夜改完测试却漏测了“价格区间筛选失效”的 bug导致当天搜索 GMV 下跌 12%。提示单体搜索的崩溃临界点不是绝对 QPS 值而是QPS × 平均查询复杂度 × 数据更新频率的乘积。当商品库日增 5 万 SKU、搜索平均词数从 2.1 个升到 3.8 个、且支持同义词/错别字/拼音搜索时单体架构基本在 QPS 3000 左右就会出现不可控抖动。2.2 微服务化拆解物理边界却暴露了认知断层微服务改造是多数团队的第一步解药。我们把单体拆成四个核心服务Query Parser查询解析、Recall Service召回、Ranking Service排序、Aggregation Service聚合。每个服务独立部署、独立扩缩容用 Nacos 做服务发现Sentinel 控制熔断降级。这套架构在稳定性上立竿见影召回服务因数据源异常挂了排序服务仍能返回默认排序结果用户体验降级而非中断。但很快暴露出新问题服务间的数据语义不一致。Query Parser 输出的“用户意图标签”是 {“品类”: “手机”, “价格敏感”: true, “品牌倾向”: “华为”}而 Ranking Service 接收时发现“价格敏感”字段在协议里定义为布尔值但实际传过来的是字符串 “true” 或数字 1导致特征计算全错。更麻烦的是“新品识别”逻辑Query Parser 认为上架 7 天内是新品Recall Service 却按库存更新时间判断结果召回列表里一堆“已售罄新品”排序再准也没用。我们曾用 Knife4j 统一 API 文档用 OpenFeign 强制类型校验甚至引入 Apache Avro 做序列化——但所有这些只解决了“数据怎么传”没解决“数据什么意思”。微服务把物理边界拆干净了却让语义边界变得更模糊。就像把一台发动机拆成活塞、曲轴、缸体分别外包给三家工厂每家都造得精密但装回去发现活塞行程和曲轴扭矩根本不匹配。注意微服务不是银弹。如果团队没有统一的领域建模能力拆得越细协作成本越高。我们最终在团队内推行“语义契约先行”所有服务接口设计前先用 PlantUML 画出领域模型图明确每个字段的业务含义、取值范围、变更影响签字确认后才写代码。这多花 2 天设计时间却省下 3 周联调返工。2.3 Search Agent用 AI 作为“语义粘合剂”重构搜索心智Search Agent 不是又一个微服务而是搜索系统的“大脑”。它不处理具体数据只负责协调、决策、学习。我们把它设计成三层结构感知层Perception Layer对接用户行为日志点击、加购、下单、实时商品库变更、外部舆情如某手机品牌突发公关事件用轻量级 BERT 模型做实时意图分类决策层Decision Layer基于感知层输出动态选择召回策略是走 Solr 倒排索引还是触发向量召回或是调用第三方比价 API并生成排序特征权重组合执行层Execution Layer将决策翻译成具体指令下发给下游微服务。例如“对 query‘iPhone15’启用向量召回相似商品关闭价格过滤用户未提预算排序权重设为销量 0.2、好评率 0.5、时效性 0.3”。关键突破在于Agent 的“可解释性”设计。我们没用黑盒大模型直接生成结果而是让 Agent 输出决策依据{ query: iPhone15, decision_reason: 用户历史 3 次搜索含‘苹果’最近点击集中在 5000-8000 元价位故关闭价格过滤, recall_strategy: vector_recall, ranking_weights: {sales: 0.2, review_score: 0.5, freshness: 0.3} }这个 JSON 不仅给下游服务执行也存入审计日志。运营人员在后台能看到“为什么这个搜索结果这样排”技术同学能快速定位决策偏差——比如发现“freshness”权重被误设为 0.8导致老款 iPhone12 排在前面立刻回滚配置。实操心得Search Agent 的价值不在“多智能”而在“可干预”。我们预留了 3 个干预入口① 运营手动覆盖决策如大促期间强制置顶某款② 算法同学上传新策略包JSON 格式无需发版③ 系统自动熔断当决策置信度 0.6 时退回到规则引擎兜底。这保证了 AI 不是取代人而是放大人的判断力。3. 核心模块实现细节Solr、微服务、AI 如何真正协同3.1 Solr 的“瘦身”实践从全能选手到专业检索引擎很多人以为微服务化后 Solr 就该淘汰其实恰恰相反——它在 Search Agent 架构里变得更重要只是角色变了。我们把 Solr 从“什么都干”变成“只干检索”做了三件事第一剥离分词与查询解析。原 Solr 的 schema.xml 里定义了 IK 分词器、同义词库、拼音转换器导致每次改分词规则都要重启 Solr。现在 Query Parser 服务用 jieba自定义词典做分词输出标准 token 流再拼成 Solr 的q参数。比如用户搜“华为mate60”Parser 输出qtitle:华为 AND title:mate60而不是让 Solr 自己去切词。这样分词策略升级只需改 Parser 代码Solr 零感知。第二固化召回策略为 Solr 查询模板。我们预定义了 8 种召回模板存在 Nacos 配置中心template_hot:qcategory:手机 AND sales:[1000 TO *]热销召回template_new:qcategory:手机 AND publish_time:[NOW-7DAYS TO NOW]新品召回template_vector:q{!knn fembedding_vector topK50}...向量召回需 Solr 9.2Agent 根据决策层指令从配置中心拉取对应模板填充变量后发请求。模板化让召回策略可灰度、可回滚、可 A/B 测试。第三用 Solr 插件实现轻量级重排序。虽然主体排序交给 Ranking Service但某些低延迟场景如搜索建议需要 Solr 内部快速重排。我们开发了一个BoostByClickRatePlugin在 Solr 的SearchComponent中注入根据实时 Redis 中的商品点击率每 5 分钟更新动态调整boost参数。实测在搜索建议场景首屏点击率提升 18%且不影响主搜索链路。关键参数说明Solr 的maxBooleanClauses默认 1024当用户搜“苹果 香蕉 橙子 葡萄 草莓……”等长尾词时极易触发TooManyClausesException。我们将其调至 32768并配合 Query Parser 做关键词截断保留前 8 个有效词避免异常。这个值不是越大越好——实测超过 65536 后Solr 查询解析耗时陡增得不偿失。3.2 微服务整合实战Nacos Sentinel Knife4j 的黄金三角微服务不是搭好就完事关键是让它们“安全地协作”。我们用 Nacos、Sentinel、Knife4j 构成稳定三角每个组件都针对搜索场景做了深度定制Nacos 服务治理不只是注册中心服务分级将搜索相关服务标记为search-group非搜索服务如订单、支付标记为trade-group避免跨组调用污染链路配置隔离为不同环境dev/test/prod设置独立命名空间搜索服务的 Solr 地址、Redis 密码等敏感配置绝不混用健康检查对 Recall Service 增加自定义健康检查——不仅 ping 端口还定期发curl -X GET http://localhost:8080/health?checksolr验证 Solr 连通性。当 Solr 不可用时服务自动下线避免流量打过去超时。Sentinel 流量治理搜索场景的精准熔断QPS 限流对 Query Parser 的/search接口按集群维度限流 5000 QPS单机阈值动态计算总限流值 ÷ 当前在线实例数熔断降级当 Ranking Service 的平均 RT 超过 300ms连续 5 秒自动熔断 30 秒期间请求直接返回缓存结果热点参数限流针对category_id做热点限流防止某类目如“手机”突发流量打垮服务。我们用 Sentinel 的ParamFlowRule对 category_id 设置每秒 200 次调用上限超出的请求返回“类目热度太高请稍后再试”。Knife4j 文档让协作从“猜”变“看”字段注释强化在 Swagger 注解中不仅写ApiModelProperty(商品ID)还补充业务约束ApiModelProperty(商品ID64位字符串全局唯一格式sku_开头16位随机码)示例值驱动每个接口提供 3 个真实场景示例正常搜索、空结果搜索、错误参数搜索附带返回体和状态码权限标识在文档中标明接口权限等级如ApiOperation(value 搜索建议, author search-team, tags {public})避免前端误调用内部接口。实操心得Knife4j 的ApiIgnore别乱用我们曾为“隐藏内部调试接口”加了这个注解结果测试同学不知道接口存在漏测了关键路径。后来改成统一前缀/internal/并在 Knife4j 配置中过滤该前缀既隐藏又留痕。3.3 Search Agent 的 AI 能力落地轻量模型 规则引擎 实时反馈Search Agent 的 AI 不是堆算力而是“小步快跑”。我们选型原则很明确能用规则解决的绝不用模型能用小模型解决的绝不用大模型。具体实现分三层感知层BERT-Base 微调 行为日志流处理模型选型放弃 LLaMA 或 Qwen用 HuggingFace 的bert-base-chinese在自有搜索日志上微调。训练数据是 100 万条 query-click pair标签是 5 类意图{“找商品”, “比价格”, “查参数”, “看评价”, “找活动”}实时处理用 Flink 消费 Kafka 中的用户行为日志每 10 秒窗口统计用户最近点击的品类分布、平均停留时长、加购率。这些指标和 query 一起喂给 BERT 模型输出意图概率。比如搜“戴尔笔记本”若用户最近 3 次点击都是“游戏本”模型会高置信度输出 {“intent”: “找商品”, “sub_intent”: “游戏本”}成本控制模型部署用 ONNX Runtime单实例 QPS 达 1200GPU 显存占用仅 1.2GB比 PyTorch 原生部署省 60% 资源。决策层规则引擎 策略编排规则引擎用 Drools但做了搜索定制。定义规则时条件部分支持实时变量rule 新品优先 when $q: Query(intent 找商品, subIntent 新品) $p: Product(publishTime now.minusDays(7)) then modify($p) { setBoost(2.0) } end策略编排Agent 启动时加载策略包JSON 格式包含召回策略、排序权重、熔断阈值。策略包版本号与 Git Tag 对齐回滚就是切版本号5 秒生效。执行层指令化 API 审计追踪指令格式Agent 不返回商品列表只返回执行指令{ command: RECALL, strategy: template_hot, params: {category: 手机, min_sales: 500}, timeout_ms: 200 }审计追踪每条指令生成唯一 trace_id记录下发时间、下游服务响应、实际执行耗时。当某次搜索结果异常运营输入 trace_id5 秒内查到是 Ranking Service 的某个特征计算超时而非 Agent 决策错误。关键经验AI 模型上线前必须过“三关”①离线关在历史数据上 AUC ≥ 0.85②灰度关1% 流量走 AI对比规则引擎的 CTR、GMV③熔断关设置置信度阈值如 0.7低于此值自动切回规则引擎。我们曾因灰度期没设熔断某次模型误判“苹果”为水果类目导致 iPhone 搜索结果里出现苹果手机壳和红富士苹果紧急回滚。4. 实操全流程从零搭建 Search Agent 搜索系统4.1 环境准备与基础组件部署部署不是复制粘贴而是理解每个组件的“生存逻辑”。我们按搜索链路依赖顺序部署确保上游组件就绪后才启动下游第一步Nacos 高可用集群3 节点服务器3 台 4C8G磁盘 100GB SSD部署命令# 下载 nacos-server-2.2.3.tar.gz解压后修改 conf/application.properties spring.datasource.platformmysql db.num1 db.url.0jdbc:mysql://10.0.1.10:3306/nacos?charsetutf8mb4connectTimeout1000socketTimeout3000autoReconnecttrue db.userroot db.passwordyour_password # 启动./bin/startup.sh -m standalone # 测试环境单机启动 # 生产环境用集群模式./bin/startup.sh -p embedded关键配置nacos.core.auth.enabledtrue开启鉴权nacos.core.auth.plugin.nacos.token.secret.key设为强密码避免未授权访问。第二步Solr 9.2 集群2 主 2 从服务器4 台 8C16GSSD 磁盘JVM 参数-Xms8g -Xmx8g -XX:UseG1GC配置要点solrconfig.xml中luceneMatchVersionLUCENE_9_0/luceneMatchVersion必须匹配managed-schema里定义text_general字段类型禁用copyField避免冗余存储启用solr.log日志级别设为WARN避免海量 INFO 日志拖慢磁盘 IO。第三步Sentinel 控制台与客户端控制台部署java -Dserver.port8080 -Dcsp.sentinel.dashboard.serverlocalhost:8080 -jar sentinel-dashboard-1.8.6.jar服务端接入在每个微服务的pom.xml加依赖dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-spring-cloud-gateway-client/artifactId version2.2.9.RELEASE/version /dependency关键配置spring.cloud.sentinel.transport.dashboardlocalhost:8080spring.cloud.sentinel.eagertrue启动即连接。注意Nacos 和 Solr 的 ZooKeeper 不能共用我们曾为省资源让 Solr 复用 Nacos 的 ZooKeeper结果 Nacos 重启时 Solr 集群脑裂数据不一致。后来给 Solr 单独部署 3 节点 ZooKeeper虽多 3 台机器但稳定性提升 100%。4.2 微服务模块开发与集成每个服务都遵循“最小职责”原则代码结构高度统一Query Parser 服务Spring Boot 2.7核心逻辑RestController public class SearchController { PostMapping(/parse) public ParseResult parse(RequestBody QueryRequest request) { // 1. 敏感词过滤调用内部风控服务 String cleanQuery riskService.filter(request.getQuery()); // 2. 分词jieba 自定义词典 ListString tokens jiebaSegmenter.segment(cleanQuery); // 3. 意图识别调用 Search Agent 感知层 API Intent intent agentClient.perceive(tokens, request.getUserId()); // 4. 生成 Solr 查询语句 String solrQuery solrQueryBuilder.build(intent, tokens); return new ParseResult(solrQuery, intent); } }关键点riskService.filter()是同步调用超时设为 200ms失败则跳过不阻塞主流程。Recall ServiceGo 1.19高性能召回用 Go 是因为其并发模型天然适配多数据源召回func Recall(ctx context.Context, req *RecallRequest) (*RecallResponse, error) { // 启动 goroutine 并行调用 Solr、向量库、MySQL var wg sync.WaitGroup ch : make(chan *RecallResult, 3) wg.Add(1) go func() { defer wg.Done(); ch - solrRecall(req) }() wg.Add(1) go func() { defer wg.Done(); ch - vectorRecall(req) }() wg.Add(1) go func() { defer wg.Done(); ch - mysqlRecall(req) }() go func() { wg.Wait(); close(ch) }() // 汇总结果去重合并 results : mergeResults(ch) return RecallResponse{Items: results}, nil }性能实测单实例 QPS 1200P99 耗时 180ms比 Java 版本低 40%。Ranking ServicePython 3.9 LightGBM特征工程从 Redis 实时读取用户画像最近 3 天点击品类、平均客单价从 MySQL 读取商品静态特征类目、品牌、评分模型训练用 LightGBM特征重要性排序前三是user_click_rate用户对该类目点击率、item_review_score商品评分、category_sales_rank类目内销量排名部署用 Flask Gunicorn模型文件.txt格式加载启动时预热避免首次请求冷加载。实操心得Go 写 Recall Service 时一定要用context.WithTimeout控制每个数据源调用超时。我们曾因向量库响应慢3s导致整个召回耗时飙升后来给每个 goroutine 加ctx, cancel : context.WithTimeout(context.Background(), 800*time.Millisecond)超时自动放弃该路召回保障整体 P99 稳定。4.3 Search Agent 部署与策略配置Agent 是系统“大脑”部署必须稳、准、快部署步骤拉取镜像docker pull search-agent:v1.2.0镜像含 ONNX Runtime Drools Kafka Client创建配置config.yaml包含 Kafka 地址、Nacos 地址、模型路径、策略包 URL启动容器docker run -d \ --name search-agent \ -v /path/to/config.yaml:/app/config.yaml \ -v /path/to/models:/app/models \ -p 8081:8081 \ --restartalways \ search-agent:v1.2.0策略包配置JSON 格式{ version: 20240520, strategies: [ { name: default_search, recall: [template_hot, template_new], ranking_weights: { sales: 0.25, review_score: 0.4, freshness: 0.15, brand_power: 0.2 }, fallback_rule: rule_simple_sort } ], rules: [ { name: rule_simple_sort, condition: intent 找商品, action: sort_by_sales_desc } ] }策略包上传到 Nacos 的dataIdsearch-agent-strategyAgent 启动时自动拉取变更时监听配置更新。上线验证 checklist[ ] Agent 能正常消费 Kafka 用户行为日志查看日志INFO - Flink job started[ ] 模型推理接口POST /agent/perceive返回{intent:找商品,confidence:0.92}[ ] 策略包加载成功日志INFO - Loaded strategy version 20240520[ ] 执行指令下发到 Recall Service用 Wireshark 抓包验证 HTTP 请求。关键技巧策略包版本管理用 Git Tag不是时间戳。git tag -a v1.2.0 -m 618大促策略然后 CI/CD 自动打包上传。这样回滚时git checkout v1.1.0即可避免时间戳冲突。5. 常见问题排查与避坑指南血泪总结的 12 个实战陷阱5.1 Solr 相关问题速查问题现象根本原因排查步骤解决方案Solr 查询偶尔超时但监控显示 CPU/内存正常JVM GC 频繁但监控未捕获短时 GC1.jstat -gc pid查看 YGC 次数/耗时2.jstack pid看线程是否卡在IndexWriter调整mergeFactor默认 10为 5减少 segment 合并频率增加maxMergeMBForOptimize搜索结果出现重复商品Solr 分片路由策略错误同一商品被索引到多个分片1.curl http://solr:8983/solr/collection/select?qid:SKU12345查各分片2. 检查router.field配置改用compositeId路由确保相同id落在同一分片或启用distribfalse强制单分片查询高亮显示错乱如“苹果”高亮成“苹”和“果”分词器与高亮器不匹配1.curl http://solr:8983/solr/collection/analysis/field?analysis.fieldvalue苹果手机analysis.fieldnametitle查分词结果2. 对比hl.fl字段的分词器统一高亮字段的analyzer和queryAnalyzer或改用UnifiedHighlighter踩坑实录我们曾因 Solr 的hl.fragsize设为 100导致长商品描述被截断用户看不到完整信息。后来改成hl.fragsize0不限制片段长度用hl.maxAnalyzedChars10000控制分析字符数既保全文又防 OOM。5.2 微服务通信故障诊断问题Recall Service 调用 Solr 偶发 500但 Solr 日志无报错排查用tcpdump抓包发现Recall 发送的 HTTP 请求头Content-Length为 0但 body 有数据Solr 拒绝解析原因Recall 用 OkHttp未显式设置Content-Length而 Solr 的 Jetty 版本较老严格校验解决OkHttp 添加拦截器强制计算并设置Content-Length。问题Sentinel 限流不生效QPS 超过阈值仍放行排查curl http://localhost:8080/actuator/sentinel查流控规则发现resource名称与代码中SentinelResource(search:query)不一致原因Spring Cloud Alibaba Sentinel 默认资源名是HTTP_METHOD:URL如GET:/search而注解指定了自定义名解决统一用SentinelResource(search_query)并在 Sentinel 控制台创建同名规则。问题Knife4j 文档中枚举值显示为ENUM_1, ENUM_2而非热销, 新品排查Swagger 的ApiModel未加ApiModelProperty的example属性解决在枚举类上加ApiEnum注解或在 Controller 方法参数上用ApiParam(example 热销)。5.3 Search Agent 决策异常处理问题Agent 对同一 query 连续两次决策不同如第一次召回 hot第二次召回 new排查查 Kafka 日志发现用户行为流有延迟Agent 第一次收到旧行为数据第二次收到新数据解决在 Flink 中加watermark延迟 30 秒确保行为数据按事件时间有序Agent 决策时加event_time时间戳丢弃延迟超 60 秒的数据。问题模型置信度突然暴跌从 0.9 降到 0.3但模型文件未更新排查查模型输入发现 Query Parser 输出的 token 流中混入了 HTML 标签如script模型从未见过解决在 Parser 中加Jsoup.clean(query, Whitelist.none())过滤所有 HTML 标签只留纯文本。问题策略包更新后Agent 未生效排查curl http://agent:8081/actuator/env查配置发现nacos.config.group配置错误读取了 test 环境的策略解决统一用nacos.config.groupSEARCH_GROUP并在 Nacos 中严格按 group 管理配置。最后分享一个小技巧给 Search Agent 加个/debug/decision接口输入 query 和 user_id返回完整决策链路包括感知层输出、规则匹配过程、最终指令。这个接口不开给前端只给运维和算法同学用排查问题效率提升 70%。上线三个月我们靠它定位了 90% 的决策异常比翻日志快得多。
返回列表