什么是Elasticsearch:核心概念与2024新特性(ESRE与向量搜索)

很多刚接触后端开发或者数据处理的同学,一听到"搜索引擎"这四个字,脑子里蹦出来的可能就是那种能搜网页的Google或者百度。其实咱们程序员口中的Elasticsearch(后面简称ES),虽然底层逻辑跟那些大厂搜索引擎差不多,但它是专门给咱们的应用系统提供搜索能力的。可以这么理解,它就是一个基于Lucene构建的分布式、RESTful风格的搜索和分析引擎。

咱们先聊聊它的老本行。ES最核心的能力就是全文检索。这玩意儿放在十年前可能觉得挺高大上,但现在已经是很多系统的标配了。它不光能存数据,还能让你以极快的速度查数据。而且它现在不仅仅是文本搜索了,根据AI知识库的信息,到了2024年,ES 8.13.0(发布于2024年4月11日)这个版本,它已经进化成了一个支持多模态数据分析的怪兽。除了文本,它还能玩地理位置(Geo-point),甚至现在最火的向量(Vector)搜索

这就不得不提2024年的一个大趋势:AI与向量搜索的深度融合。ES现在搞了个叫 Elasticsearch Relevance Engine (ESRE) 的东西。这玩意儿干嘛用的?换个角度看,就是为了让ES能更好地支持现在的RAG(检索增强生成)架构。你想想,现在做大模型应用,光靠大模型自己那点知识库肯定不够,你得从外部塞数据进去。ES现在不仅能做传统的BM25文本匹配,还能把数据转成稠密向量或者稀疏向量存起来,然后做语义相似度搜索。这意味着,用户搜"苹果多少钱",ES不仅能搜到包含"苹果"这个词的商品,还能通过向量检索理解用户可能是在找"水果"或者"手机",这体验就完全不一样了。

除了搜索,ES的聚合分析能力也是一绝。如果你有一堆日志数据,想统计一下最近一周哪个接口报错最多,或者哪个地区的用户访问量最大,用ES配合Kibana,几秒钟就能给你画出来。这也是为什么它是ELK Stack(Elasticsearch, Logstash, Kibana)的核心组件,搞运维监控、日志分析的同学对它肯定不陌生。

还有个不得不提的点,就是安全性。以前老版本装完ES,默认是没密码的,谁都能访问,这在生产环境就是个定时炸弹。现在的版本,特别是8.x系列,默认就开启了安全配置。你装完之后,它会自动生成密码和证书,省去了咱们手动配置的麻烦,但也给新手带来了一些连接上的"小门槛",这个咱们后面环境搭建的时候会细说。

⚡ 效率提示:如果你是奔着做AI应用或者RAG去的,千万别只盯着传统的文本搜索看。在学ES的时候,重点去研究一下dense_vector类型字段和knn查询,这是2024年ES最吃香的功能,也是面试或者实际项目中的加分项。

环境搭建:Docker部署Elasticsearch 8.13与Kibana安全配置

咱们搞开发,环境搭不起来,后面啥都白搭。现在都2024年了,谁还去服务器上手动下载压缩包解压啊?那都是老黄历了。现在最舒服的方式就是用Docker。咱们直接上最新的 Elasticsearch 8.13.0(2024年4月11日发布的那个版本),配合Kibana一起搞。

打个比方,用Docker Compose是最省心的。咱们直接定义一个docker-compose.yml文件。这里有个实际案例点要提醒大家:ES 8.x版本对内存和虚拟内存有要求,如果你是在本地虚拟机或者云服务器上跑,记得先把vm.max_map_count调大,不然容器肯定起不来。

咱们直接看代码,这是一份可以直接跑的配置:

version: '3.8' services: elasticsearch: image: elasticsearch:8.13.0 container_name: es01 environment: - discovery.type=single-node - xpack.security.enabled=true - xpack.security.http.ssl.enabled=false - "ES_JAVA_OPTS=-Xms1g -Xmx1g" volumes: - es_data:/usr/share/elasticsearch/data ports: - "9200:9200" networks: - elastic kibana: image: kibana:8.13.0 container_name: kibana01 depends_on: - elasticsearch environment: - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 ports: - "5601:5601" networks: - elastic volumes: es_data: driver: local networks: elastic: driver: bridge

把上面这段代码保存成docker-compose.yml,然后在同目录下执行docker compose up -d

等容器跑起来之后,咱们得去拿那个默认的超级用户密码。因为8.13版本默认开启了安全配置,虽然我在配置里把SSL关了(为了本地开发方便),但密码还是得有的。执行下面这行命令:

docker exec -it es01 bin/elasticsearch-reset-password -u elastic -a

系统会给你打印出elastic用户的密码。记下来,后面登录Kibana和调用API都要用。

接着咱们去浏览器访问 http://localhost:5601。这时候Kibana会让你输入用户名和密码。用户名填elastic,密码填刚才你拿到的那个。

登录进去之后,如果你看到的是英文界面,想换成中文(虽然建议看英文,但新手可能中文更亲切),可以在Kibana的配置里加个环境变量I18N_LOCALE=zh-CN,不过咱们这里为了演示,就不折腾了。

注意:很多新手在本地跑ES,为了省事会把xpack.security.enabled设置成false。我强烈建议你千万别这么干。既然是学8.13版本,就要适应它的安全机制。学会怎么用账号密码或者API Key去调用ES的API,这才是生产环境该有的样子。

💡 经验总结:如果你本地机器内存不大,那个ES_JAVA_OPTS=-Xms1g -Xmx1g可以改成512m,虽然跑起来可能稍微慢点,但至少能跑起来。另外,记得给Docker分配足够的内存资源,至少4G以上,不然ES这个吃内存大户分分钟把你机器卡死。

核心原理解析:倒排索引、BM25评分与分片机制

咱们不能只做个"调包侠",知道怎么装、怎么用API还不够,得稍微懂点底层的原理,这样出了问题才知道去哪儿查。ES之所以快,核心就在于它的倒排索引(Inverted Index)

咱们拿个例子说事儿。假设你有三句话:

在传统的数据库(比如MySQL)里,它是按行存的。你要搜"数据",它得一行一行扫,看哪行包含"数据"。这叫全表扫描,数据量大了就完蛋。

但ES不一样,它建了一个倒排索引。可以这么理解,它先把所有文本分词(比如分成:Elasticsearch、是、个、好、东西、能、搜、数据、很、重要),然后记录每个词出现在哪篇文档里。

大概长这样:

当你搜"数据"的时候,ES直接去这个表里查,发现"数据"对应文档2和3,瞬间就返回了。这比一行行扫快了不是一点半点。这里面还有两个概念:Term Dictionary(词项字典,就是上面那个表)和 Posting List(倒排列表,就是后面的文档ID列表)。

有了倒排索引,数据找到了,那怎么排序呢?这就涉及到BM25算法。这是ES默认的相关性评分算法。简单来说,它不仅仅看这个词出没出现,还要看这个词出现的频率(TF)和在整个文档集合中出现的频率(IDF)。如果一个词在所有文档里都出现(比如"的"、"是"),那它的权重就低;如果一个词只在少数文档里出现,那它的权重就高。BM25就是算出一个分数(_score),分数越高,说明越相关,排得越靠前。

再聊聊分片(Shard)。ES是分布式的,能处理PB级数据。怎么做到的呢?靠分片。一个索引(Index)可以被切成多个分片,分布在不同节点上。比如你有一个100G的索引,你可以设置5个分片,每个分片大概20G。这样查询的时候,可以并行在5个分片上查,最后汇总结果,速度就上去了。

分片还分主分片(Primary Shard)副本分片(Replica Shard)。主分片负责写数据,副本分片是主分片的拷贝,负责读数据。这不仅能提高读取吞吐量,还能保证高可用。万一某个节点挂了,上面的主分片不可用了,副本分片可以升级为主分片,保证数据不丢。

🔧 实战技巧:在创建索引的时候,分片数一旦设定就不能直接修改(除非Reindex),所以一定要提前规划好。对于大多数中小型应用,如果你不确定,主分片数设置为1或者3就够用了,别动不动就搞几十个分片,那会增加集群的负担。副本数一般设置为1,保证有一份备份就行。

实战演练:使用RESTful API构建商品搜索与Ingest Pipeline预处理

理论说了这么多,咱们来点真格的。咱们现在要做一个简单的商品搜索功能。咱们不用什么花里胡哨的客户端,直接用ES最原生的RESTful API,通过JSON交互。这才是ES的精髓。

咱们先创建一个索引,叫products。这里咱们不仅要定义字段类型,还要稍微玩点新的,比如加个向量字段,为以后的AI搜索做准备(虽然现在可能用不上,但咱们得有这个意识,毕竟是2024年的教程)。

curl -X PUT "http://localhost:9200/products" -u elastic:你的密码 -H 'Content-Type: application/json' -d' { "settings": { "number_of_shards": 1, "number_of_replicas": 1 }, "mappings": { "properties": { "name": { "type": "text", "analyzer": "standard" }, "description": { "type": "text" }, "price": { "type": "double" }, "tags": { "type": "keyword" }, "location": { "type": "geo_point" }, "embedding": { "type": "dense_vector", "dims": 3 } } } }'

注意看,我这里加了一个embedding字段,类型是dense_vector,维度是3(实际用的时候可能是768或者1536,咱们这里演示用3维)。还有locationgeo_point,用来存经纬度。

接下来,咱们要往里面塞数据。但是,咱们的数据可能不干净,比如价格可能是字符串,或者描述里有HTML标签。这时候就得用上 Ingest Pipeline 了。这是ES提供的一个数据预处理管道,比Logstash轻量,直接在ES内部处理。

咱们定义一个Pipeline,把价格转成浮点数,顺便给描述加个前缀:

curl -X PUT "http://localhost:9200/_ingest/pipeline/product_pipeline" -u elastic:你的密码 -H 'Content-Type: application/json' -d' { "description": "商品数据预处理", "processors": [ { "convert": { "field": "price_str", "type": "double", "target_field": "price", "ignore_failure": true } }, { "set": { "field": "description", "value": "商品详情:{{description}}" } } ] }'

现在咱们用这个Pipeline来插入一条数据:

curl -X POST "http://localhost:9200/products/_doc?pipeline=product_pipeline" -u elastic:你的密码 -H 'Content-Type: application/json' -d' { "name": "iPhone 15 Pro", "description": "苹果最新旗舰手机,性能强悍", "price_str": "8999", "tags": ["手机", "数码", "苹果"], "location": "39.9,116.3", "embedding": [0.1, 0.2, 0.3] }'

你会发现,虽然咱们传的是price_str,但ES里存的是price数字类型。这就是Pipeline的威力。

最后,咱们来搜一下。咱们搜"苹果手机",并且价格要低于10000。

curl -X GET "http://localhost:9200/products/_search" -u elastic:你的密码 -H 'Content-Type: application/json' -d' { "query": { "bool": { "must": [ { "match": { "description": "苹果" } } ], "filter": [ { "range": { "price": { "lt": 10000 } } } ] } } }'

这里用了bool查询,must子句负责相关性匹配(会算分),filter子句负责精确过滤(不评分,速度快,且能缓存)。

📌 要点提醒:在实际开发中,尽量多用filter上下文。因为filter不计算相关性分数,而且结果会被缓存,性能比直接用must或者should要好得多。特别是那种范围查询(比如时间范围、价格范围),一定要放在filter里。另外,Ingest Pipeline非常适合做数据清洗,别把脏数据直接扔进ES,那样索引会很难看,查询也费劲。

5. 进阶查询:ES|QL语法入门与向量检索(RAG场景应用)

其实,如果你还在用老一套的 Query DSL 写复杂的嵌套查询,那种括号匹配能把人逼疯。Elasticsearch 8.13 版本里,社区讨论最火的一个特性就是 ES|QL (Elasticsearch Query Language)。这玩意儿是专为数据分析打造的一种管道式查询语言,写起来有点像 Linux 的管道符 |,处理起数据来比传统的 JSON 查询体感要好太多。

ES|QL:告别复杂JSON嵌套

ES|QL 的核心逻辑是“数据管道”。你先指定从哪张表(索引)拿数据,然后通过 | 把数据扔给下一个处理单元,比如过滤、排序或者聚合。

咱们直接看代码,感受一下这种语法的清爽。假设我们有一个电商产品的索引 products,传统 DSL 可能要写一堆 bool 查询,但在 ES|QL 里,你可以这么玩:

// 使用 ES|QL 查询价格大于 100 的电子产品,按价格排序,并只返回名称和价格 POST /_query?format=json { "query": """ FROM products | WHERE price > 100 AND category == "Electronics" | SORT price DESC | LIMIT 10 | KEEP name, price, stock """ }

看到没?这种写法简直就是大白话。FROM 指定索引,WHERE 过滤,SORT 排序,LIMIT 限制条数,KEEP 选择返回的字段。对于习惯了 SQL 或者 Shell 脚本的同学来说,这简直是降维打击,完全不需要去数 JSON 的大括号了。

向量检索与 RAG 场景实战

现在 AI 这么火,ES 在 8.x 版本里对向量搜索的支持已经到了丧心病狂的地步。特别是 Elasticsearch Relevance Engine (ESRE) 的推出,让 ES 不仅仅是全文检索,更是 RAG(检索增强生成)架构里的核心数据库。

可以这么理解,RAG 的过程就是:把文档切成块 -> 转成向量(Embedding)存进 ES -> 用户提问时也转成向量 -> 在 ES 里找最相似的向量块 -> 把结果喂给大模型。

在 8.13 版本中,我们可以用 dense_vector 类型来存储这些向量。咱们来实操一下,创建一个支持向量检索的索引,并插入一条数据:

// 1. 创建索引,定义向量字段 PUT /company_knowledge_base { "mappings": { "properties": { "content": { "type": "text" }, "content_vector": { "type": "dense_vector", "dims": 3, "index": true, "similarity": "cosine" } } } } // 2. 写入一条带向量的数据(这里用3维向量做演示,实际通常是768或1536维) POST /company_knowledge_base/_doc/1 { "content": "Elasticsearch 8.13 版本于 2024年4月11日发布,增强了对 AI 的支持。", "content_vector": [0.1, 0.2, 0.3] } // 3. 使用 KNN 检索进行向量搜索 POST /company_knowledge_base/_search { "knn": { "field": "content_vector", "query_vector": [0.1, 0.2, 0.25], "k": 1, "num_candidates": 10 }, "_source": ["content"] }

📌 要点提醒:在做向量检索时,别一上来就直接查全量 KNN。如果数据量大,一定要结合 filter 使用。比如先过滤出“某个部门”的文档,再在这个范围内做向量检索,这样能极大降低 HNSW 算法的计算量,提升查询速度。

混合检索:BM25 + 向量

现在的趋势是“混合检索”。单纯靠向量有时候不准(比如数字、专有名词),单纯靠 BM25 又没法理解语义。ES 8.13 完美支持这种组合。你可以在一个查询里同时使用 matchknn,ES 会自动帮你做结果融合(Reciprocal Rank Fusion)。这在构建企业级知识库时非常管用,能显著解决大模型“幻觉”问题,因为它检索到的上下文更精准了。

6. 性能优化与避坑:解决深度分页与集群写一致性问题

做后端开发,最怕的就是数据量一上来,查询就崩,或者数据写进去丢了。ES 在这方面有两个经典的“坑”,也是面试必问的:深度分页写一致性。咱们今天不聊虚的,直接看怎么解决。

深度分页:别再用 from + size 了

很多新手写分页,习惯性地用 fromsize。比如 from: 9900, size: 10。简单来说,这在 ES 里是个自杀式操作。

为啥呢?ES 的查询是分布式的。你要拿第 10000 条数据,协调节点需要去每个分片把前 10000 条数据都拿回来,然后在内存里排序,最后再给你吐出这 10 条。数据量小还好,一旦翻页深了,内存直接爆掉,集群响应会变慢甚至挂掉。

在 8.13 版本里,解决这个问题的最佳实践是使用 search_after。它利用上一页的排序值来定位下一页的起点,不需要维护内存中的排序状态。

看看代码对比,这才是生产环境该用的写法:

// 错误示范:深度分页,性能极差 POST /logs/_search { "from": 9900, "size": 10, "sort": [ { "timestamp": "desc" } ] } // 正确示范:使用 search_after // 第一步:查询第一页,记录最后一条数据的 sort 值 POST /logs/_search { "size": 10, "sort": [ { "timestamp": "desc" }, { "_id": "asc" } ] } // 假设上一页最后一条数据的 sort 值是 [1712755200000, "abc123"] // 第二步:基于这个值查询下一页 POST /logs/_search { "size": 10, "sort": [ { "timestamp": "desc" }, { "_id": "asc" } ], "search_after": [1712755200000, "abc123"] }

📌 要点提醒:如果你需要导出全量数据(比如导成 CSV),千万别用分页循环。直接用 Scroll API 或者使用 Elasticsearch 8.13 推荐的 Point in Time (PIT) 配合 search_after。PIT 能保留索引数据的快照视图,避免导出过程中因为数据刷新导致的数据不一致。

集群写一致性与 Quorum 机制

再说说写数据。你有没有想过,当你发送了一条写入请求,ES 怎么保证数据不丢?默认情况下,ES 是异步复制的,但如果你对数据一致性要求极高,就不能用默认配置。

ES 的写入一致性主要通过 consistency 参数(虽然在新版本中更多是通过 wait_for_active_shards 来控制)来保证。换个角度看,就是写入操作必须等待多少个副本分片(Replica)激活后才算成功。

假设你设置了 1 个主分片,1 个副本分片(共 2 个拷贝)。为了保证强一致性,你可以这样写:

// 写入数据并指定等待所有活跃分片(主分片+副本)同步 PUT /orders/_doc/1001?wait_for_active_shards=all { "order_id": "1001", "status": "paid", "amount": 299.99 }

这里的 wait_for_active_shards=all 意味着,必须等到主分片和副本分片都确认写入了,这个请求才返回 200。如果副本分片挂了,这个写入请求就会报错(比如 5xx),从而避免了数据只写在主分片上而副本丢失的风险。

注意:wait_for_active_shards 不能替代副本故障后的数据恢复,它只是在写入的那一刻保证尽可能多的分片存活。在生产环境中,通常设置为 wait_for_active_shards=2(假设 1 主 1 副),这样能平衡性能和数据安全性。

另外,面试常问的 quorum 机制,本质上是说:在分片数较多时,写入操作需要保证大多数分片(超过一半)可用。ES 内部通过 translog(事务日志)来保证数据即使在断电情况下也不会丢失,写入内存的同时会刷盘到 translog,这一点非常关键。

7. 总结与展望:Serverless架构与AI融合趋势

写到这里,咱们其实已经把 ES 8.13 的核心玩法摸了个大概。但作为一个有 5 年经验的开发者,我得带大家跳出代码,看看 Elasticsearch 在未来的 2024 到 2026 年到底要往哪儿走。这对于咱们做技术选型、系统架构设计至关重要。

Serverless:云原生的终极形态

以前咱们搭 ES 集群,得关心几个节点?多大内存?磁盘用 SSD 还是 HDD?扩容的时候还得手动改配置,贼麻烦。现在的趋势是 Elastic Cloud Serverless

打个比方,Serverless 就是“按用量计费 + 自动扩缩容”。你只需要往里面灌数据、查数据,底层的资源调度全交给 Elastic 官方或者云厂商。ES 8.13 在这个方向上做了很多优化,比如更细粒度的资源隔离。

对于咱们开发者来说,这意味着运维成本的断崖式下跌。特别是对于那些流量波动很大的业务(比如做活动时的日志突增),Serverless 架构能自动帮你扛住压力,活动结束自动缩容,不用像以前那样为了峰值预留大量闲置资源。如果你是新项目,我强烈建议直接考虑 Elastic Cloud 的 Serverless 方案,别再自己折腾裸机部署了。

AI 深度融合:从搜索到生成

另一个大趋势就是 AI 与向量搜索的深度融合。ES 不再只是一个“文本搜索引擎”,它现在是一个“AI 应用数据库”。

特别是 ESRE (Elasticsearch Relevance Engine) 的不断完善,它把传统的 BM25 全文检索、稀疏向量(Sparse Vector)检索和稠密向量(Dense Vector)检索无缝结合在了一起。

咱们回顾一下前面提到的 RAG(检索增强生成)场景。在 2024 年,这已经是构建企业知识库的标配了。ES 8.13 对向量检索的性能优化(比如 HNSW 算法的调优)使得它处理亿级向量数据也不在话下。

未来的开发模式会变成这样:

🔧 实战技巧:如果你正在做 Java 开发,注意 ES 8.x 之后,官方已经废弃了 High Level Rest Client,全面转向了新的 Java API Client。这个客户端是强类型的,支持 Lambda 表达式,用起来比老版本舒服很多,而且它原生支持 Vector 和 ES|QL 的查询构建。赶紧迁移吧,别抱着老代码不放了,不然以后升级版本(比如升到 9.x)的时候,重构成本会让你哭出来。

可观测性与安全

最后提一嘴,ES 在 可观测性 方面也在疯狂发力。整合 OpenTelemetry 标准已经是大势所趋。以后你的 APM、日志、基础设施监控数据,全都可以统一扔进 ES,用 Kibana 看。而且 8.13 默认开启安全配置,再也不用担心裸奔被勒索病毒攻击了。

总之,Elasticsearch 正在从一个单纯的搜索引擎,进化成一个集搜索、分析、AI 数据支撑于一体的超级平台。作为开发者,咱们得跟上这个节奏,别只盯着倒排索引看了,向量和 AI 才是接下来的星辰大海。