
做Java后端这些年我参与过好几个电商类项目每次遇到商品搜索这个环节总会发现一个规律数据库LIKE查询在数据量小的时候看着挺美一旦商品表到了几百万行、用户搜索词又带着各种同义词和拼音缩写那响应速度和对搜索结果的精准度基本就是一场灾难。所以当我接手《黑马商城》这个典型的分布式微服务项目时第一个想到的就是把ElasticSearch单独拎出来做一套商品搜索服务。这篇博文就把我在这个项目里从环境搭建、索引建模、数据同步到搜索接口开发、最后部署上线的完整过程拆开来讲里面包括我踩过的坑和一些常规文档不会写的东西希望能给正在做类似项目的朋友省点时间。这篇内容适合两类人看一类是把黑马商城作为面试项目、准备写在简历上的Java开发另一类是正在做分布式电商项目、想在现有业务里接入ES搜索的团队。无论你是第一次接触ES还是已经用过一段时间但没系统梳理过这篇文章都会给你一个可以照着做的完整路径。1. 内容整体设计与思路拆解1.1 为什么商城项目必须单独做搜索引擎在动手敲代码之前得先想清楚一个问题ES到底在这个分布式架构里扮演什么角色。很多Java项目一开始都是MySQL一把梭商品表加个索引用户搜“手机”就LIKE %手机%数据少的时候没问题可一旦商品数量上来你会发现几个典型痛点一是查询速度下降尤其是模糊匹配没法走索引全表扫描必然慢二是相关性排序没法做MySQL只能按时间、价格、销量这种固定字段排用户最关心的“哪个结果更匹配”反而排不出来三是分词能力完全没有“苹果手机”和“iPhone”在MySQL看来就是两个东西但对用户来说可能是同一个需求。ES在分布式商城里的定位就是专职处理搜索和筛选它通过倒排索引把每个词映射到对应的文档ID查询时不需要全表扫描直接按词查索引就能拿到结果。再加上它天然支持分布式索引可以分片存储在多台机器上数据量大了就横向加节点不需要像MySQL分库分表那样改业务代码。这种“专业的事交给专业的引擎”的思路在微服务架构里尤其重要——商品服务只管增删改查和写数据库搜索服务只管读索引和返回结果两者通过消息队列解耦互不拖累。1.2 黑马商城搜索模块的整体架构和选型思路黑马商城本身是典型的SpringCloud微服务项目拆成了商品服务、订单服务、用户服务等好多模块。我在给它接ES的时候特意避开了两种极端做法一种是把ES客户端直接塞进商品服务里这样商品服务和搜索逻辑耦合太深以后搜索需求一变商品服务也得跟着发版另一种是过度设计一上来就搞独立的搜索中台小项目根本没必要。我的最终方案是单独建一个search-service微服务模块对外提供搜索接口对内通过RabbitMQ接收商品变更消息再调用ES的Java API完成索引的增删改。商品服务那边不需要关心ES怎么用它只要在商品上架、下架、更新的时候往MQ里丢一条消息就行。数据同步分成两条链路全量同步用于初次上线时把MySQL里的存量数据一次性灌进ES增量同步用MQ消息实时更新索引。查询入口统一走search-serviceController层做参数校验和DTO转换Service层拼DSL查询条件Repository层用Spring Data Elasticsearch操作索引。搜索技术选型上我直接选了最新稳定版ElasticSearch 7.15.2配合Kibana 7.15.2做可视化调试。SpringBoot用的2.6.x因为Spring Data Elasticsearch对ES 7.x的支持最成熟8.x虽然也兼容但相对不够稳定。IK分词器用2021版支持自定义词典能处理商城商品名里那些专业词汇。这套组合实测下来非常稳社区资料也最多遇到问题基本都能搜到解决方案。1.3 用Kibana调试DSL查询到底有多省事这里多说一句Kibana的作用。很多新手习惯直接写Java代码调ES结果DSL写错了报错信息又看不懂只能在代码里来回改、反复重启服务效率极低。我的习惯是在Kibana的Dev Tools里先把查询语句调通确认返回的数据没问题再翻译成Java代码。因为Kibana里能看到完整的查询响应、命中数和每个文档的分值还能直接测试分词器的分词结果这在排查“为什么搜不到”“为什么排序不对”这类问题时是神器。后面我写搜索接口时几乎每一条复杂查询都是先在Kibana里验证过再粘到代码里的。2. 环境准备与ElasticSearch安装2.1 Windows下安装ES和Kibana的几个前置条件虽然项目最后部署在Linux服务器上但开发阶段在Windows上把环境跑起来效率最高。我踩过的一个典型坑是很多人直接把ES压缩包解压后双击elasticsearch.bat结果窗口一闪而过然后打开logs/elasticsearch.log才发现一堆错误。ES 7.x要求JDK 11以上虽然ES自带了一个JDK但如果你在JAVA_HOME里配的是JDK 8它还是会用你系统里的版本然后报UnsupportedClassVersionError。所以第一步就是确认环境里有没有JDK 11或更高版本没有的话要么升级要么在config/jvm.options里指定自带JDK路径。我这里是开发机直接用了ES自带的JDK省去了环境变量冲突的麻烦。另外一个高频坑是Windows下启动ES时报bootstrap check failure提示max file descriptors和max virtual memory areas不够。在Linux里这是标准的调优项需要改/etc/security/limits.conf和/etc/sysctl.conf但Windows下不要慌ES默认在Windows开发模式下不会触发完整检查你只要把config/elasticsearch.yml里的discovery.type: single-node打开单机开发就不会报那些生产环境的严苛限制了。2.2 启动ES和Kibana的详细步骤具体步骤我整理成清单你照着走就行从官网下载ES 7.15.2的Windows zip包解压到一个没有空格的纯英文路径比如D:\elasticsearch-7.15.2路径里有空格会导致启动时解析配置出错。编辑config/elasticsearch.yml设置cluster.name: heima-es-clusternode.name: node-1network.host: 127.0.0.1http.port: 9200并确保discovery.type: single-node被打开。进入bin目录双击elasticsearch.bat启动。看到started的日志输出就说明成功了此时浏览器访问http://127.0.0.1:9200能看到一个包含cluster_name和version的JSON返回就算正常。下载Kibana 7.15.2务必和ES版本一致解压后编辑config/kibana.yml设置server.port: 5601和elasticsearch.hosts: [http://127.0.0.1:9200]运行bin/kibana.bat启动。浏览器打开http://127.0.0.1:5601左侧菜单找到Dev Tools输入GET /_cat/indices?v验证是否能连上ES。这里有个很容易被忽略的版本一致性要求Kibana和ES的大版本必须完全匹配7.15.2的ES搭配7.15.2的Kibana别用7.10的Kibana连7.15的ES会报incompatible version错误。2.3 生产部署时的Linux配置思路开发完要部署到Linux服务器时很多配置要改。首先ES不能用root用户运行必须创建一个专用用户比如esuser然后把解压后的目录chown给这个用户。其次要把/etc/security/limits.conf里加两行esuser soft nofile 65536和esuser hard nofile 65536修改文件描述符上限否则后续压力测试时会出现文件打开过多的错误。还要调/etc/sysctl.conf里的vm.max_map_count默认值是65530ES启动时会要求至少262144不改的话节点会直接拒绝启动。改完之后执行sysctl -p让它生效。内存方面config/jvm.options里建议把-Xms和-Xmx设成一样的值避免运行时动态扩容引发full GC机器内存如果是8G设成4g就行不要贪多因为还要留内存给操作系统页缓存。如果上生产还要考虑集群模式至少要三台节点分别配置node.master和node.data的角色。黑马商城项目的演示环境我是单节点部署的但索引分片我依然设了3个主分片和1个副本分片这样后续加机器时可以直接做水平扩展不需要重建索引。3. 索引建模与中文分词方案3.1 商品索引的字段该怎么设计ES的索引相当于MySQL的数据库表但设计思路完全不一样。MySQL表结构讲究范式化减少冗余ES的索引讲究“宽表”就是把查询时需要的字段都冗余到索引里查询时尽量一次性返回所有需要的信息减少回表操作。我在黑马商城里设计的商品索引叫heima_goods核心字段包括id商品IDkeyword类型、title商品标题text类型指定IK分词器、categoryName分类名称textkeyword双类型、price价格integer类型或scaled_float、brandName品牌keyword类型、image图片URLkeyword类型、saleCount销量integer、specs规格参数用nested嵌套类型。这几个字段基本覆盖了商城搜索和筛选的全部场景。这里有个关键设计细节title字段我设置了type: text并指定了analyzer: ik_max_wordsearch_analyzer: ik_smart。这意味着写入索引时用IK的最大粒度分词尽可能分出更多的词组合而搜索时用更细的smart模式保证查询精度和性能的平衡。举个例子“黑色高跟鞋”写入时会被拆成“黑色/高跟/高跟鞋/鞋子/鞋”等词搜索时用户输入“高跟鞋”smart模式拆成“高跟鞋”一词就能精确匹配。3.2 IK分词器的安装和自定义词典ES自带的标准分词器对中文非常不友好它会按单个汉字切分“苹果手机”被拆成“苹/果/手/机”搜索时相关性一团糟。IK分词器是目前中文搜索的事实标准有两种拆分模式ik_max_word是最大粒度拆分把一段话拆得最细ik_smart是智能拆分按最合理的语义边界切分。安装IK分词器时要注意版本严格匹配ES 7.15.2对应的是elasticsearch-analysis-ik-7.15.2.zip。把zip包下载下来放到ES的plugins/ik目录下解压然后重启ES。验证方法是在Kibana Dev Tools里执行GET /_analyze { analyzer: ik_max_word, text: 黑马商城苹果手机 }如果输出了一堆分词结果就说明IK装好了。注意IK只能识别它词库里的词电商场景下有很多专业词汇和品牌词比如“海尔”“美的”“云南白药”这种品牌名以及“连衣裙”“冲锋衣”这种品类词IK默认词库里可能没有或不够全。解决办法是修改IK的配置文件IKAnalyzer.cfg.xml在自定义词典里追加词条也可以扩展ext_dict指向你自己的词典文件。我在黑马商城项目里加了一个自定义词典ext.dic里面放了几百个商城高频词。有个细节值得一说修改词典文件后并不需要重启ESIK会定时检测文件变化并自动加载但速度会有一点延迟如果急着看效果手动重启也行。3.3 用拼音搜索和自动补全提升搜索体验除了中文分词我还给商品名加了拼音搜索能力。这个需求源于一个很现实的场景用户搜“huawei”的时候希望能搜出华为手机搜“苹果”的时候也要能匹配“Apple”。拼音搜索的实现方式是给title字段增加一个子字段title.pinyin类型为text分析器用pinyin。具体做法是安装elasticsearch-analysis-pinyin插件然后在索引映射里为title加上fields配置{ title: { type: text, analyzer: ik_max_word, fields: { pinyin: { type: text, analyzer: pinyin } } } }这样搜索“huawei”或“hw”时ES会去title.pinyin字段匹配拼音索引。实测下来搜索“xiaomi 12”“ipad”甚至“acer”这类含拼音或英文缩写的词都能快速命中。不过拼音搜索有个坑全拼搜索时会产生很多无效匹配比如“王子”的拼音wangzi可能搜出“王者”的内容。解决办法是在查询时把拼音字段的权重降低比如中文标题匹配的权重是10拼音匹配权重是2这样中文字符匹配优先拼音只作为兜底补充。4. 数据同步从MySQL到ES的两条链路4.1 全量同步的思路和实现方式上线ES的第一步是把MySQL商品表里的存量数据同步过来。我采用的方式是写一个定时任务每天凌晨跑一次全量同步把商品数据批量刷进ES。当然这种同步不能影响正常业务所以数据库表加了version字段作为乐观锁标记同步任务只处理version超过上次同步记录的数据。全量同步的具体实现是写一个FullSyncJob类用Scheduled(cron 0 0 3 * * ?)注解定时触发。任务内部用MyBatis-Plus按ID分页查询商品表每批查500条避免一次性加载全部数据导致内存溢出。查出来的每一条商品数据组装成ES的IndexRequest然后用Bulk批量写入。这里有个我踩过的坑Bulk批量写入的批次大小不是越大越好。刚开始我一次性提交5000条结果ES直接报TooManyRequestsException因为单批次太大导致ES线程池排队严重。后来我把批次拆成每500条一次重试机制也统一处理了一遍速度不仅没慢反而稳定了很多。核心原因是ES批量写入的瓶颈往往在协调节点本身的CPU和网络不是数据量大小。4.2 增量同步基于RabbitMQ的消息驱动方案增量同步是整个数据同步方案的灵魂也是面试被问得最多的地方。黑马商城本身就有RabbitMQ在传递订单、消息等业务数据我直接复用了这套消息基础设施而不是再引入一套新的MQ。具体流程是这样的商品服务在商品上架、下架、修改时会把对应的商品变更事件发送到交换机goods.exchange路由键分别是goods.create、goods.update、goods.delete。Search服务监听同一个交换机通过RabbitListener注解绑定队列search.queue收到消息后根据消息中的商品ID调用商品服务的Feign接口获取最新的商品详情然后更新到ES索引里。RabbitListener(queues search.queue) public void handleGoodsMessage(GoodsMessage message) { if (message.getType() GoodsMessage.Type.CREATE || message.getType() GoodsMessage.Type.UPDATE) { Goods goods goodsClient.queryGoodsById(message.getGoodsId()); if (goods ! null) { goodsRepository.save(GoodsDocument.from(goods)); } } else if (message.getType() GoodsMessage.Type.DELETE) { goodsRepository.deleteById(message.getGoodsId()); } }这个方案的优点很明显商品服务和搜索服务完全解耦商品服务完全不感知ES的存在即使搜索服务宕机了MQ消息也会积压等服务恢复后继续消费不会丢数据。缺点是引入了消息中间件增加了运维复杂度需要额外监控MQ积压情况。我在生产环境下游量小即使消息积压几十万条消费完也就几秒钟的事所以完全能接受。4.3 如何保证数据同步的一致性数据一致性是分布式系统里永恒的话题也是面试官最爱深挖的点。ES和MySQL属于两个独立的数据源做不到强一致只能尽量做到最终一致。我在黑马商城里做了三层保障第一层是MQ消息确认机制。RabbitMQ默认是自动确认模式消费端收到消息还没处理完就ack了万一处理中崩溃消息就丢了。我在Search服务里改成了手动确认模式只有ES写入成功后才basicAck失败了就basicNack并把消息重新入队或者投递到死信队列留待人工处理。第二层是兜底检查。即使MQ消息传递和消费都正常也可能出现商品服务接口返回了旧数据的情况。所以我每天凌晨的全量同步任务其实就是一道兜底程序用MySQL最新的数据强制覆盖ES里过期的旧数据。日常增量的实时性和夜间全量的完整性互补这样即使增量链路出了漏最迟第二天凌晨也会自动修正。第三层是失败重试和监控。消息消费失败时我加了RetryTemplate默认重试3次每次间隔2秒。超过重试次数就丢到死信队列search.queue.dlx然后写一个告警任务去扫描死信队列长度超过阈值就发钉钉通知。这套机制上线以后我基本没有再为数据不同步的事半夜爬起来过。5. 搜索核心功能开发与DSL查询实战5.1 从需求到DSL的拆解方法搜索接口看起来简单无非是接收关键词、查ES、返回结果但实际做起来需求细节一个比一个多。我在黑马商城把搜索需求拆成了几个维度关键词匹配、分类过滤、品牌过滤、价格区间过滤、销量排序、价格排序、分页、高亮。每个维度对应DSL里的一块结构组合起来就是完整的查询语句。一个准确的拆解方法是从用户视角倒推用户在搜索框输入“华为手机”页面上可能还有筛选栏选了“分类手机数码”“品牌华为”“价格3000-5000”然后点“按销量排序”。这个场景翻译成DSL就是multi_match查询在title和categoryName字段上匹配关键词“华为手机”用户输入的搜索词再叠加term过滤分类和品牌range过滤价格区间sort按销量降序最后分页返回。我强烈建议所有查询都在Kibana的Dev Tools里先验证一把确认返回结果符合预期后再往Java代码里翻译。这样能避免非常多无谓的联调过程而且Kibana里能看到每个命中的_score相关度分数和命中的字段高亮片段排查问题一目了然。5.2 复杂查询的DSL实现我拿一个真实的搜索场景来演示DSL这个查询在黑马商城上线后一直稳定运行。需求是用户搜索“手机”要求品牌必须是华为分类是“手机数码”价格在2000到6000之间按综合相关度排序同时返回标题高亮片段。GET /heima_goods/_search { query: { bool: { must: [ { multi_match: { query: 手机, fields: [title^10, categoryName^5, brandName^2], type: best_fields } } ], filter: [ { term: { brandName: 华为 } }, { term: { categoryName: 手机数码 } }, { range: { price: { gte: 2000, lte: 6000 } } } ] } }, highlight: { pre_tags: [em], post_tags: [/em], fields: { title: {} } }, from: 0, size: 20, sort: [ { _score: desc }, { saleCount: desc } ] }这里我要解释几个关键点。第一是multi_match的fields权重设计title权重最高10categoryName其次5brandName最低2因为用户搜索时最关心的是商品标题是否匹配分类和品牌只是辅助判断。第二是filter和must的区别bool查询里的filter子句不影响打分只做过滤性能比must高很多因为ES可以对过滤结果做缓存。所以分类、品牌、价格这些只是条件的筛选一律放进filter里。第三是highlight。高亮的前后标签我用的是em而不是默认的tag因为前端展示时直接渲染富文本就能显示高亮效果不需要额外写转换逻辑。如果没有highlight前端拿到的只是完整的标题用户根本看不出哪里匹配了关键词搜索体验会大打折扣。5.3 写Java查询代码的几个实用技巧Kibana里的DSL验证通过后我用Spring Data Elasticsearch写Java代码。这里有个习惯值得养成不要把DSL硬编码在Service里而是单独建一个GoodsRepository继承ElasticsearchRepository复杂查询用Query注解写在接口方法上或者直接用NativeSearchQuery构建。我构建查询用的是NativeSearchQueryBuilder配合BoolQueryBuilder、MatchQueryBuilder、TermQueryBuilder这些类。代码大概长这样public PageResultGoodsDocument search(String keyword, String brand, String category, Integer minPrice, Integer maxPrice, int page, int size) { BoolQueryBuilder boolQuery QueryBuilders.boolQuery(); if (StringUtils.hasText(keyword)) { MultiMatchQueryBuilder multiMatch QueryBuilders.multiMatchQuery(keyword, title^10, categoryName^5, brandName^2); boolQuery.must(multiMatch); } if (StringUtils.hasText(brand)) { boolQuery.filter(QueryBuilders.termQuery(brandName, brand)); } if (StringUtils.hasText(category)) { boolQuery.filter(QueryBuilders.termQuery(categoryName, category)); } if (minPrice ! null maxPrice ! null) { boolQuery.filter(QueryBuilders.rangeQuery(price).gte(minPrice).lte(maxPrice)); } NativeSearchQueryBuilder builder new NativeSearchQueryBuilder() .withQuery(boolQuery) .withPageable(PageRequest.of(page - 1, size)); // ... sort and highlight SearchHitsGoodsDocument hits elasticsearchRestTemplate.search(builder.build(), GoodsDocument.class); // ... convert to PageResult }写Java代码的时候有一个我特别想提醒的坑ES的from size分页方式offset如果太大比如超过10000会报错。默认的max_result_window是10000商城场景下用户一般也翻不了1000页所以这个限制实际影响不大。但如果以后要做“下拉加载更多”这种无限滚动场景一定要换search_after或scroll游标方式我后面在部署篇会专门提到这个问题。6. 部署上线与生产环境问题排查6.1 从Windows开发环境到Linux生产部署的差异ES最麻烦的地方在于Windows下开发环境非常宽松而Linux生产环境有一堆系统参数要提前调好。我在部署黑马商城到测试服务器时用的是一台4核8G的CentOS 7机器单节点部署但该做的调优一个都不能少。首先是启动用户。ES不能root启动是它从设计层面就不允许的因为ES会动态生成脚本执行一些系统调用如果以root权限运行会有安全隐患。我按前面说的创建了esuser并把ES安装目录的所有权给了它。其次是内存设置。默认的JVM堆大小是1G对商品索引这种数据量来说根本不够分片一多就直接OOM。我在jvm.options里设了-Xms4g和-Xmx4g但8G的机器留4G给JVM、剩下的给操作系统缓存跑商品搜索绰绰有余。如果你要同时跑Kibana和ES内存分配要更保守Kibana本身也吃1-2G内存。最后是参数调整。按我前面说的vm.max_map_count262144和nofile 65536都要设好。还有一个容易漏的ES会自动绑定到localhost还是所有网卡取决于network.host配置。生产环境我设成0.0.0.0然后通过防火墙只放行指定IP访问9200端口不让外网直接访问安全和可用性都兼顾。6.2 索引快照备份与恢复数据实操数据备份和恢复这件事网上资料比较少但恰恰是生产环境最容易出事的环节。我发生过一次比较痛的经历测试环境手滑执行了DELETE /heima_goods整个索引瞬间清空如果事前没做快照那全量同步又得跑一遍。所以后来我第一时间就把ES快照机制研究透了。ES的快照是把索引数据备份到共享文件系统或对象存储里恢复时可以直接整索引恢复。配置方式是在elasticsearch.yml里加path.repo: [/data/es-backup]然后重启ES创建快照仓库PUT /_snapshot/my_backup { type: fs, settings: { location: /data/es-backup } }给指定索引做快照PUT /_snapshot/my_backup/snapshot_20250101 { indices: heima_goods, ignore_unavailable: true, include_global_state: false }恢复的时候执行POST /_snapshot/my_backup/snapshot_20250101/_restore { indices: heima_goods }恢复速度取决于索引大小和磁盘性能我这边35GB的索引恢复到新节点上大约用了8分钟。快照策略我建议每天凌晨配合全量同步之后执行一次保留最近7天这样即使线上出了问题最多也只丢一天的增量数据而且可以通过MQ死信队列的消息补偿回来。6.3 高频坑点排查和面试考察点速查接触ES的过程中新手和老手之间最大的差距就体现在排查问题的速度上。这类问题在开发部署中实在太常遇见我直接整理成一张速查表方便你遇到问题时顺手翻翻。报错或现象根本原因解决办法max virtual memory areas vm.max_map_count [65530] is too lowLinux系统参数不满足ES要求sysctl -w vm.max_map_count262144filedata too large节点JVM堆内存不足增加Xmx或减少分片数量、清理fielddata缓存too many open files文件描述符上限太低设置ulimit -n 65536Watermark [85%] exceeded磁盘空间不足触发ES写入保护清理磁盘或添加新节点必要时调低watermark阈值NoNodeAvailableException客户端连不上ES节点检查network.host、防火墙端口、http.port是否正常Result window is too largefromsize分页超过10000限制改用search_after或scroll中文搜索搜不到IK分词器未安装或未生效确认插件版本匹配用_analyze测试分词效果拼音搜索无效pinyin插件版本不匹配删除旧插件重新安装对应版本后重启面试官问ES相关问题套路也比较固定一是问索引结构和分词器考察你有没有实际构建过索引、有没有处理中文分词的实践二是问数据同步和一致性对应的就是我在第4章写的MQ消息驱动方案和一致性兜底三是问深分页和性能优化只要你能说出search_after和scroll的区别、倒排索引的原理、批量写入的批次控制这些点基本就能把问题答到位。7. 经验总结与排查实战记录再分享一些我在整个项目过程中积累的个人经验。我最想强调的是ES的一个使用习惯任何对线上索引做结构变更的操作都要先在测试环境完全模拟一遍。比如给索引加一个字段你以为只是改mapping实际上ES不支持直接修改已有字段的type只能通过新建索引、reindex数据、切换别名的方式完成。这种操作如果不提前演练线上执行时稍有不慎就会出事故。关于别名和索引版本管理我后来强制自己养成了一个习惯线上索引一律带版本号后缀比如heima_goods_v1然后通过别名heima_goods_search指向当前版本。这样如果结构变更出了问题只要把别名切回旧版本索引用户无感知回滚成本极低。这个思路和蓝绿发布是一个道理在数据库表设计上没法这么灵活但ES给了我们这种能力不用就可惜了。另外关于分片数量我吃过一个亏一开始图省事建索引时只设了1个分片数据量增长到一定程度后查询性能明显下降而分片数量在建索引后就不能修改了只能重建索引。所以建索引之前一定要结合数据增长预期规划好分片数。我的经验公式是单分片容量控制在30-50GB以内分片总数量尽量等于集群节点数的整数倍。黑马商城这种数据量3个主分片加1副本后续扩容到6个节点也能支撑得很好。最后聊聊性能调优的实测数据。黑马商城的商品索引上线后包含约50万商品文档在4核8G的单节点上关键词搜索的平均响应时间在30ms左右复杂过滤场景大概70msBulk批量写入速度每秒约5000条文档。说实话这个性能表现远超当时MySQL的LIKE查询同量级数据下动不动就200ms以上。我还试过把价格区间过滤和品牌过滤全加上的场景因为用了filter缓存性能波动非常小。如果后续要继续扩展这个搜索模块我觉得有几个方向值得尝试一是把聚合分析用起来在搜索结果页面按分类、品牌展示聚合计数用户每选一个筛选项就知道还有多少商品可选二是引入竞价排名或个性化推荐比如给某些品牌加权、根据用户历史点击行为调整权重三是做搜索建议和热词下拉提示。这些功能的底层能力ES都具备只要索引结构和查询逻辑设计得够好扩展起来不会伤筋动骨。