比ES快5倍的Meilisearch:轻量搜索引擎选型与实战
1. 搜索场景选型闲聊为什么我会盯上“比ES快”这回事1.1 ES很好但我为什么要找替代一聊到搜索引擎很多人的第一反应就是ES也就是Elasticsearch。ES确实能打但在实战里它的内存占用、查询延迟、配置复杂度会让你怀疑人生。如果你做的不是日志分析而是面向用户的实时搜索那完全可以换一个更轻、更快的方案。我今天想聊的就是最近在项目里用到的一台“小钢炮”——Meilisearch。它在不少场景下实测性能对比ES能快好几倍网上有社区测试给出过“快5倍”的结论我自己的压测也基本落在三到五倍这个区间。先说清楚我的使用背景。之前我维护过一个电商商城的站内搜索商品SKU大约二十万条不算大但ES集群常年占用三十多GB内存搜索接口的平均响应在100毫秒到200毫秒之间浮动大促期间一翻页就涨到500毫秒以上。内容团队一直跟我抱怨“搜个东西转圈”。我去查集群状态CPU不算高但GC频繁段合并也在后台折腾索引查询路径上的开销非常大。后来我把这套面向用户的前台搜索彻底换成了Meilisearch内存占用降到不到4GB接口响应普遍在20到40毫秒。所以这篇文章不是想跟你争论“谁才是天下第一”而是想分享一个真实体感当搜索场景是“用户输入关键字、立刻看到结果”的时候ES这类重型搜索引擎越到后期越像杀鸡用牛刀。Meilisearch这类轻量搜索引擎的价值恰恰体现在你不需要庞大集群也能拿到毫秒级响应。这篇内容适合正在做网站搜索、商城搜索、SaaS产品内搜索、知识库检索的人也适合那些被ES运维搞到头疼、想在架构上做减法的人。1.2 市面上几个“ES替代品”横评在正式聊Meilisearch之前我先把手边几个常被拿来和ES对比的搜索引擎拉出来盘一遍。这个横评不是为了分高下而是帮你搞清楚哪类产品适合哪类场景。搜索引擎定位擅长场景主要短板Elasticsearch分布式全文检索分析引擎日志分析、复杂聚合、海量数据、ELK生态运维重、内存开销大、查询延迟偏高Meilisearch即搜即得型搜索引擎站内搜索、应用内搜索、模糊搜索、前端即时搜索不适合复杂聚合分析分布式能力弱Typesense低延迟搜索引擎类似Meilisearch轻量、快、支持向量搜索中文社区资料相对少生态还在积累ZincSearch轻量日志检索替代ELK做日志查看和简单分析功能边界明显不适合高并发前台搜索Quickwit云原生日志搜索引擎对象存储为底层的海量日志检索查询场景偏日志不适合商品/内容搜索Meilisearch、Typesense、ZincSearch这三类我都试用过最终在商城搜索里落地的是Meilisearch。原因是它对“边输入边搜索”这个交互模式的支持非常自然有默认的模糊匹配前端SDK也齐全。Typesense的速度和稳定性也很强但当时中文文档和社区方案没有Meilisearch成熟。ZincSearch更偏日志场景拿来当商城搜索就有点跑偏。还有一点要提醒标题里说“比ES快5倍”这个数字不要理解成所有场景下都成立。ES在做聚合分析、日志检索、大规模分布式查询的时候有它自己的优势Meilisearch快是因为它轻、专注、把大量能力做在内存里。选型之前一定要先想清楚自己的业务是真的需要搜索还是需要分析这两个需求经常被混在一起。2. 这台“小钢炮”为什么快核心机制拆解2.1 前缀搜索的优化思路很多人第一次用Meilisearch都会惊讶我还没把关键词打完整结果已经出来了。这背后不是玄学是它对“前缀搜索”这个场景做了专门的底层优化。我们知道ES底层是Lucene索引数据被拆成很多段每次查询需要到对应段里查倒排索引做完合并之后还要对结果做评分排序。冷段和热段的缓存策略不同查询一多就可能出现缓存未命中性能波动明显。Meilisearch则把大量数据尽量放在内存里并且在索引结构上对前缀匹配做了专门的优化。它内部维护了一种类似trie树的结构来处理前缀项用户输入“手机”系统能快速定位到所有以“手机”开头或相关的词项不需要在Term Dictionary里做范围扫描。这就好比你去图书馆查一本书ES的做法是先去目录柜翻卡片再跑到书库每个书架找Meilisearch的做法则是把热门书的索引贴在门口顺手就能拿到。当然没有银弹。Meilisearch把资源花在“前缀搜索”和“模糊匹配”上就意味着它在复杂条件过滤、嵌套对象查询、深度聚合这类场景上不如ES灵活。这也解释了为什么它适合做前台搜索不适合做后台BI分析。搜索引擎在设计之初就已经用脚做了投票选型的人别再踩这个坑。2.2 查询路径更短少做的事就是优势ES之所以慢很大一部分原因不是查询本身慢而是它承载了太多能力。查询请求进来之后要先解析DSL构建查询语法树执行计划优化然后分发到各个分片每个分片执行Lucene查询再把结果汇总到协调节点做合并、排序、二次聚合。整个过程功能强大代价是路径长、开销大。Meilisearch的设计哲学是“少做事情”。它没有复杂DSL查询参数就是q、filter、sort、limit、offset这些请求进来之后直接映射到内部的检索器。我可以负责任地说当你只需要“关键字筛选条件排序”的时候Meilisearch的执行路径比ES短了不止一个量级。这就像一个是多功能瑞士军刀一个是专业厨师刀你切菜的时候专用刀肯定更快。我在压测里还发现一个细节Meilisearch对请求的响应时间分布非常稳定P99和P50之间的差距很小。ES则容易受到后台合并、GC停顿的影响触发一次Full GC之后请求延迟会突然拉高到一两秒。对面向用户的产品来说最怕的不是慢而是忽快忽慢。稳定有时候比平均延迟更重要。2.3 写入与索引的轻量化设计说到写入之前用ES同步业务数据的时候我得精心调bulk大小、线程池、refresh_interval写太快了会撞集群瓶颈写太慢了搜索又查不到新数据。大促期间尤其痛苦经常半夜被数据同步任务报警吵醒。Meilisearch默认把写入请求先放进内存缓冲再异步批量建索引配合主键去重和更新策略对业务方来说基本不用关心“实时搜索”这层逻辑。但轻量化也有代价。Meilisearch的定位不是海量日志导入你非要一次性灌几亿条日志它并不会比ES更舒服。它更适合业务数据在千万条级别以内、更新频率可控、要求前台秒级反映的场景。官方也明确建议大数据量场景下优先考虑分片和更重的分布式引擎。这句话不是虚的是帮你规避架构风险。另外我在项目里对比了索引体积同样的二十万条商品数据ES索引文件加上分片副本大概占8GB磁盘Meilisearch的data.ms目录占不到1GB。索引小了冷启动的加载速度自然也快。重启ES集群JVM堆合并加段恢复经常要几分钟Meilisearch重启之后基本秒级恢复只加载对应内存映射文件就够了。运维压力完全不同。3. 亲手搭一套5分钟体验毫秒级搜索3.1 安装并初始化Meilisearch一条命令的事想快速体验的话直接用Docker跑最省事。我这里以Meilisearch v1.6版本为例docker run -d \ --name meilisearch \ -p 7700:7700 \ -e MEILI_MASTER_KEYyour_strong_master_key \ -v $(pwd)/meili_data:/meili_data \ getmeili/meilisearch:v1.6这里有一个关键参数MEILI_MASTER_KEY它是主密钥所有管理端操作都需要带上它。很多人第一次测试时不设置这个环境变量结果服务起来后任何请求都能直接操作索引这在公网环境里等于裸奔。生产环境一定要设置强密钥并且把7700端口限制在内网或经过网关鉴权之后再暴露出去。启动完成后访问http://localhost:7700能看到一个简洁的健康检查页面。Meilisearch默认提供了一层Web控制台集成了API Key管理和任务监控对我这种习惯命令行的人来说已经很够用了。这里要说一下新版Meilisearch把默认生成的主密钥打印在服务日志里如果你用命令行方式启动注意看日志输出的MASTER KEY。3.2 建索引和数据导入核心操作习惯要建立建索引和数据导入我是用curl直接调API完成的简单直接也方便你在脚本里复用。先创建一个商品索引并指定主键curl -X POST http://localhost:7700/indexes \ -H Authorization: Bearer your_strong_master_key \ -H Content-Type: application/json \ --data {uid: products, primaryKey: id}uid是索引唯一标识primaryKey是文档主键。这里主键必须提前指定否则后续导入文档或者更新操作会因为没有唯一标识而出问题。我见过有人在导入数据时忘了指定主键结果Meilisearch自动生成了一个后面更新文档的时候才发现每一条都变成了重复插入折腾半天。导入商品数据直接POST一个JSON数组curl -X POST http://localhost:7700/indexes/products/documents \ -H Authorization: Bearer your_strong_master_key \ -H Content-Type: application/json \ --data-binary products.jsonproducts.json是一个JSON数组每条记录包含id、name、category、brand、price、description这些字段。传完之后Meilisearch会立刻返回一个taskId导入是异步执行的。你可以通过下面的地址查看任务状态curl http://localhost:7700/tasks/0 \ -H Authorization: Bearer your_strong_master_key很多新手都会忽略这个Task机制以为请求返回200就代表数据可搜了。其实文档导入要等后台索引任务真正完成才会出现在搜索结果里数据量大时尤其明显。所以建议在测试阶段写一个把任务状态轮询到succeeded的简单脚本避免业务代码里出现“刚插入就查不到”的尴尬。接下来设置可搜索属性、可筛选属性和排序属性。这一步非常关键直接影响搜索结果的质量。我用下面这些配置# 设置哪些字段参与搜索 curl -X PUT http://localhost:7700/indexes/products/settings/searchable-attributes \ -H Authorization: Bearer your_strong_master_key \ -H Content-Type: application/json \ --data [name, category, brand, description] # 设置哪些字段可以筛选 curl -X PUT http://localhost:7700/indexes/products/settings/filterable-attributes \ -H Authorization: Bearer your_strong_master_key \ -H Content-Type: application/json \ --data [category, brand, price] # 设置哪些字段可以排序 curl -X PUT http://localhost:7700/indexes/products/settings/sortable-attributes \ -H Authorization: Bearer your_strong_master_key \ -H Content-Type: application/json \ --data [price, created_at]坦白讲这些设置在官方文档里都是一句话带过但实际项目里不规划好后面就会很难受。比如filterable-attributes如果漏配了price搜索页面想做价格区间筛选就会直接报错还得回头重建索引。还有searchable-attributes字段顺序就是搜索时的权重顺序name排在category前面意味着搜索命中name的记录会比命中category的记录排得更前。这个顺序一定得按业务优先级来别懒。3.3 接入前端搜索框一套能直接抄走的代码服务端起来之后前端接入非常快。我用的是官方提供的meilisearch-js SDK加一个简单的搜索框。以下是核心代码!DOCTYPE html html langzh-CN head meta charsetUTF-8 title电商搜索测试/title script srchttps://cdn.jsdelivr.net/npm/meilisearch0.35.0/dist/browser/meilisearch.umd.min.js/script /head body input idsearch-box placeholder搜索商品 / ul idresult-list/ul script const client new Meilisearch({ host: http://localhost:7700, apiKey: your_search_key }); const index client.index(products); const searchBox document.getElementById(search-box); const resultList document.getElementById(result-list); searchBox.addEventListener(input, async (e) { const query e.target.value; if (!query) { resultList.innerHTML ; return; } const results await index.search(query, { limit: 20, attributesToHighlight: [name], highlightPreTag: em, highlightPostTag: /em }); resultList.innerHTML results.hits.map(hit { const name hit._formatted.name || hit.name; return li${name} —— ${hit.price}/li; }).join(); }); /script /body /html这里有一个容易踩的坑前端SDK不应该使用主密钥MASTER KEY而应该到Meilisearch后台创建一个只有搜索权限的API Key。官方控制台里可以创建key并指定index和action我只给它授权了products索引的search权限。这样即使前端代码被人扒走攻击者也只能搜索不能删库也不能看所有数据。如果你遇到浏览器跨域问题需要在启动Meilisearch时配置MEILI_ENVproduction并设置允许的域名或者通过反向代理统一管理。我在本地测试时直接放了CORS头但生产环境建议所有请求都走自己的网关把Meilisearch藏到内网既解决跨域又能做统一鉴权。3.4 实测数据做个不严谨但真实的压测为了搞明白“快5倍”到底是怎么回事我用之前的二十万条商品数据做了一次对比压测。先声明这次压测不是严谨的基准测试条件有限环境是8C16G单机ES和Meilisearch部署在同一台机器查询之前都没有做额外的缓存预热。目的不是写论文只是想验证“体感差异”是否存在。查询场景ES平均耗时Meilisearch平均耗时差距完全匹配“华为手机Nova12”48ms14ms约3.4倍前缀搜索“华为手”122ms29ms约4.2倍模糊搜索“为手机”215ms46ms约4.7倍带价格区间过滤搜索170ms38ms约4.5倍我还用hey做了简单并发测试5000个请求、50并发Meilisearch的P99基本维持在60毫秒以内ES在我这个配置下P99飙到500毫秒以上。所以社区里说“快5倍”我认为在“前缀搜索、即搜即得”这个场景下是可以复现的。但换个场景如果让Meilisearch去做大范围聚合统计它可能直接就拉胯了这种事我劝你别试。压测命令也很简单需要提前安装hey工具hey -n 5000 -c 50 -m POST \ -H Authorization: Bearer your_search_key \ -H Content-Type: application/json \ -d {q:手机,limit:20} \ http://localhost:7700/indexes/products/search通过输出里的Average、P95、P99指标基本就能判断服务表现。我还习惯在压测时观察服务端的内存占用和CPUMeilisearch表现稳定后大概保持在3GB左右ES跑完压测之后堆内存还在缓慢上升。这里并不是说ES不行只是它需要更大的内存预算和更细的调优才可能达到接近的效果。4. 选搜索引擎不能只看速度边界与适用场景4.1 Meilisearch适合什么不适合什么我在好几个项目里都见过类似的误判团队被某篇性能对比文章吸引直接把ES换成了轻量搜索引擎结果发现业务要的不只是搜索还要报表分析最后又加回了一套分析引擎。所以“适合什么”比“跑多快”更重要。Meilisearch适合的是站内搜索、应用内搜索、商城商品搜索、知识库全文检索、文档标题搜索、地址联想以及“用户已经在搜索框里开始打字需要实时补全和结果预览”的场景。这类场景的要求高度一致查询语法简单、结果要求毫秒级返回、数据量级通常在百万以内、运维希望尽量省心。不适合的场景也很明确日志实时分析、复杂聚合报表、多表关联查询、超大规模分片集群、需要精确控制相关度评分的专业搜索领域。比如你有一堆业务日志要按时间窗口聚合统计错误码分布、接口响应耗时趋势这种活就老老实实上ES加Kibana别折腾轻量搜索引擎。数据量一旦到亿级Meilisearch单机内存会非常大成本反而不如ES集群来得可控。另一个容易被忽略的是团队技能栈。ES虽然重但很多运维和开发都接触过出了问题能百度到大量方案。Meilisearch社区在快速成长但国内中文资料还是比ES少不少遇到冷门的问题可能得翻英文Issue去查。选型的时候要把团队的维护能力算进去。4.2 什么时候还是要老老实实用ES我自己没有把ES完全踢出技术栈相反在数据分析通道里还在大量使用ES。商城搜索切到Meilisearch之后用户行为日志、订单统计、库存分析这些后台任务依然走ES。为什么因为这里要的不是“快”而是“能查能算”。ES那一套Range聚合、Terms聚合、Date Histogram聚合配好索引映射之后分析师直接在Kibana里拖拽就能出图表这种效率是Meilisearch给不了的。更直白地说ES和Meilisearch是解决两个不同问题的工具。一个是搜索数据库另一个是分析引擎。面向用户的实时搜索对延迟极其敏感但对复杂分析的需求很弱后台分析对吞吐和聚合能力要求高但对几十毫秒的延迟并不敏感。把这两个职责混在一起结果就是两边都做不好。如果你还在犹豫我建议用双跑方案验证。同一份数据分别往ES和Meilisearch里导一份然后把线上真实的搜索请求收集一批分别压测对比P50和P99再让前端同学做一次盲测。数据不会骗人业务团队也更认可这种选型依据而不是你拍脑袋说“谁快谁准”。4.3 我在选型时用的三条判断标准我把这几年做搜索选型的经验浓缩成三句话不一定适用所有人但至少能帮你少走弯路。第一先分清业务要的是“搜索”还是“分析”。面向最终用户即时反馈的是搜索面向内部团队查数和报表的是分析这两个诉求尽量拆成独立服务。第二用小数据集做全链路验证不要只看Benchmark。拉上前端、后端、运维用真实业务数据和真实交互流程完整走一遍重点看P95、P99、内存增长趋势和异常出现频率。第三算全量成本包括集群机器成本、运维成本、开发成本、问题排查成本。ES集群一年下来的机器开销和维护工时往往比表面看起来大得多这不是ES的错而是它的定位本来就重。我踩过最深的坑就是一开始把搜索和分析都塞在同一个ES集群里结果前台搜索查询把集群负载拉高后台统计任务又把磁盘IO吃满两边互相拖累。后来把前台搜索迁到Meilisearch一台4C8G的机器就扛住了原来三台ES节点的流量而且延迟还更低。架构上做减法效果有时超出想象。5. 生产环境落地要注意的坑5.1 中文搜索不准怎么办这大概是中文开发者使用Meilisearch时遇到最多的问题。默认情况下Meilisearch的中文分词是“按字切分”的也就是说搜索“手机壳”的时候它可能会把三个字拆开处理结果相关性并不理想。英文用户没有这个烦恼因为英文天然按空格分词中文必须靠词典和语义模型。官方其实已经给出了解决方案。在v1.6之后的版本里可以开启实验性的中文分词功能。具体做法是在启动时设置环境变量MEILI_EXPERIMENTAL_ENABLE_CHINESE_SEGMENTATIONtrue重启后索引需要重新处理中文搜索效果会好很多。我实测下来商品名里带“手机壳”“运动鞋”这类词时搜索结果的相关性和默认按字切分相比有明显提升。如果项目里的中文场景比较复杂比如医疗、法律、金融这些专业领域建议在业务接入层单独做分词。常见方案是在搜索请求前用jieba之类的分词库把用户输入拆成词项再传给Meilisearch或者干脆把需要精准匹配的字段在写入前就预先分词用数组类型保存。无论哪种方案都要在测试环境先把中文分词策略定下来。搜索相关性的优化越早做后面返工的成本越低。5.2 内存与持久化规划Meilisearch快的一个前提是数据尽量在内存里所以内存规划直接决定它的性能表现。官方建议内存至少是索引体积的两倍这句话我在生产环境验证过基本靠谱。但要注意如果你的索引包含大量全文长文本索引体积会膨胀很快。我有个客户把几万篇长文档塞进一个索引索引文件直接冲到3GB机器内存只有8GB结果查询一多就开始GC表现反而不如ES。持久化方面Meilisearch的数据都存在data.ms目录里。Docker部署时一定要挂载这个目录到宿主机否则容器一删数据全没。升级版本前务必备份整个data.ms目录我遇到过小版本升级后索引版本不兼容导致启动失败的情况最后是靠备份恢复的。还有一个小细节如果使用Snapshot功能做定期快照记得给快照目录单独规划磁盘空间。Meilisearch的默认配置不会自动清理旧快照时间一长磁盘会被占满。最好写个定时任务删除几天前的快照文件避免“服务没挂、磁盘挂了”的尴尬。5.3 权限、多租户与安全默认情况下没有任何密钥保护的Meilisearch就像一台不设密码的数据库谁都知道端口就能删数据。我见过有人把测试环境直接暴露到公网第二天起来索引里全是垃圾数据这种事故真的会让人头皮发麻。生产环境一定至少做到三点设置强主密钥、创建最小权限的搜索Key、只在内网或通过网关暴露服务。如果你做的是SaaS产品需要对不同客户的数据做隔离可以试试Meilisearch的Tenant Token机制。它允许你用主密钥签发一个带租户标识的搜索Token这个Token只能搜索到该租户自己的文档。我是在一个多租户项目里用上的每个租户在索引文档里带上tenant_id字段前端请求搜索时携带对应该租户生成的TokenMeilisearch会自动加上过滤条件不需要业务层再手动拼接filter省了很多事。不过要注意Tenant Token是对搜索端的能力限制它不解决文档一级的字段权限。如果要求某个租户看不到另一个租户的字段名你还是需要在写入数据前就把敏感字段过滤掉或者做成不同索引隔离。权限设计这个东西搜索引擎能帮你一部分但核心的安全边界还得自己守住。5.4 数据迁移与双跑方案从ES迁到Meilisearch最直接的痛点是文档模型不同。ES里你可能有嵌套对象、父子关系、复杂的mappingMeilisearch更偏向扁平JSON文档嵌套数据结构虽然也支持但查询和过滤的便利性不如扁平字段好。建议迁移前先做一次字段梳理把ES里需要查询、筛选、排序、展示的字段单独拎出来改造成扁平结构再灌进Meilisearch。为了降低切换风险我推荐用“双跑”的方式过渡。线上同时保留ES和Meilisearch两套搜索服务写操作双写读操作先用小流量打到Meilisearch做对比等结果稳定后再逐步切流量。这里有一个经验两边搜索结果不可能完全一样ES和Meilisearch的相关度排序逻辑差异很大你会发现同一关键词返回的结果顺序和数量都不同。不要追求完全一致关键是用户可接受的搜索结果是否合理。可以先让产品同学试用Meilisearch的搜索结果主观体验过关了再继续切流量。我经历过一次比较顺利的迁移前后花了两周整体思路是先导出ES数据做清洗再一次性导入Meilisearch同时保留ES作为后备跑了一周的双写双读确认Meilisearch的查询延迟和结果质量都稳定之后才把ES集群从搜索链路中摘掉。整个过程没有发生线上事故关键就是没有“一刀切”地切换。6. 常见问题排查速查表6.1 高频问题一览下面这份表格是我在实际项目中整理出来的高频问题先给结论和排查方向再补充一条重要提醒。现象可能原因解决办法中文搜索不准结果乱默认按字切分没开中文分词开启MEILI_EXPERIMENTAL_ENABLE_CHINESE_SEGMENTATION或外部预分词导入文档后搜索不到异步导入任务还没完成轮询/tasks接口确认任务状态为succeeded请求报401 UnauthorizedAPI Key权限不足或密钥错误检查Authorization请求头确认Key的索引权限数据库启动后端口被占用7700端口被其他进程占用换端口或停掉占用进程检查docker映射查询时filter报错filterable-attributes未配置到settings接口配置filterable-attributes排序无效sortable-attributes未配置配置sortable-attributes并确保索引文档有对应字段内存占用一直涨索引数据量与内存不匹配扩容内存或拆分索引定期删除无用文档升级后无法启动数据版本与新版不兼容备份data.ms后回滚版本或按官方迁移文档操作6.2 排查建议所有搜索服务的问题我第一反应永远是先看日志。Meilisearch的日志默认打印到标准输出Docker部署可以直接用docker logs命令查看里面会明确显示请求路径、任务执行结果、错误码。遇到问题不要急着改代码先确认你的请求是不是真的打到服务上了。遇到API报错时把响应体里的errorCode记录下来对照官方文档查含义。Meilisearch的错误信息写得相对友好比如invalid_search_attributes、invalid_filter这些code基本能直接定位到是索引配置问题还是查询参数问题。我见过很多人报错之后连响应体都不看直接把问题甩到技术群其实自己花一分钟看下errorCode就能解决。最后补一条压测时候的提醒压测结果只能用于横向对比不要追求绝对数字。每台机器的CPU、内存、磁盘类型不同结果差异非常大。我用的是SSD如果你在机械硬盘上跑写入和查询性能都会明显下降。搜索引擎的性能优化本质上是硬件、数据模型、查询设计三者匹配的结果指望换个软件就解决所有性能问题那是不现实的。