你是小阿巴,刚入职的后端程序员。
这天,产品经理给你安排任务:阿巴阿巴,咱们网站要加一个文章搜索功能。
你心想:简单,直接写一句 SQL 查询数据库就搞定了~
SELECT * FROM article WHERE title LIKE '%关键词%'
结果上线没几天,就收到了大量用户的投诉!
怎么什么搜索结果都没有啊?
搜索结果乱七八糟,我想找的那篇内容竟然排在最后面?
搜索一次竟然要等好几秒才出结果?什么破系统!
你汗流浃背了:明明 SQL 写对了啊,难道是 MySQL 数据库不行?
这时,号称 "后端之狗" 的鱼皮路过。他瞄了一眼你的代码,嘲笑道:肯定要用 Elasticsearch 来做搜索功能啊!
你一脸懵:Elasticsearch?那是啥?
鱼皮:Elasticsearch 简称 ES,是一个专门为搜索而生的分布式数据库,也叫 搜索引擎数据库。
它能存储和管理大量文本数据,提供快速、准确、灵活的全文检索功能。你刚才用 LIKE 查询搞不定的那些问题,用 ES 都能轻松解决。
你挠了挠头:真有这么神?
鱼皮:当然。打个比方,MySQL 就像图书馆的书架,书按照分类整整齐齐地摆放着,你想找某本书得自己一排一排去翻;而 ES 就像图书馆的电子检索系统,你输入关键词,它立刻就能告诉你书在哪儿,还会把最相关内容的排在最前面。
像全文搜索、日志分析、数据统计这些需要搜索能力的场景,ES 都能轻松搞定。
你眼前一亮:听起来有点儿夯啊,那我赶紧装一个试试。
第二阶段:实战应用 安装 Elasticsearch
机智如你,直接打开 ES 官网 下载了安装包:
并且成功安装运行:
你:安装好之后,我怎么操作它呢?
鱼皮:ES 本身提供了 RESTful API,默认在 9200 端口提供服务,你可以用 curl 命令或者 Postman 等接口测试工具直接发 HTTP 请求来操作它。
不过对新手来说,更推荐先安装一个官方的可视化工具 Kibana。
有了它,你可以直观地查看分析数据、对数据进行操作。
只需要到官网下载安装包并运行,启动之后访问本机的 5601 端口,就能打开 Kibana 的管理界面了。在开发工具控制台里,你可以直接输入查询语句,能够立刻看到结果,非常方便。
下面我来带你实操一波 ES 的基本操作。
1)首先是 创建索引。ES 的 索引(Index) 相当于 MySQL 里的表,是存放数据的容器。
创建索引的时候,还要定义 Mapping(映射),类似 MySQL 的表结构,用来规定每个字段的类型、是否需要分词、使用什么分词器等等。
在 Kibana 开发工具中输入这段代码:
PUT /article
{
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "standard" },
"content": { "type": "text" },
"tags": { "type": "keyword" },
"viewCount": { "type": "long" },
"isPublished": { "type": "boolean" },
"createTime": { "type": "date" }
}
}
}
这段代码创建了一个叫 article 的索引。其中 text 类型表示需要分词的文本字段,适合做全文检索;keyword 类型不会分词,适合存标签、状态这种需要精确匹配的内容。其他的类型就比较好理解了,long 存数字,boolean 存 true 或者 false,date 存日期。设计索引的时候要根据业务需求合理选择字段类型。
2)然后是 插入文档。文档(Document) 相当于 MySQL 里的一行数据。ES 的文档是用 JSON 格式存储的,不需要像 MySQL 那样提前定义好所有字段,而是随时可以加新字段,非常灵活。
POST /article/_doc/1
{
"title": "鱼皮的 Elasticsearch 入门教程",
"content": "鱼皮带你学习 ES",
"cover": "封面图地址",
"tags": ["ES", "搜索"],
"viewCount": 1000,
"isPublished": true,
"createTime": "2025-01-30"
}
3)有了数据之后,就可以体验 ES 最核心的能力 搜索文档。比如在 article 索引中搜索标题包含 "鱼皮教程" 的文章:
GET /article/_search
{
"query": {
"match": { "title": "鱼皮教程" }
}
}
你执行完这条查询,惊喜地发现:搜 "鱼皮教程" 居然能匹配到 "鱼皮的 ES 入门教程" 这篇文章!
鱼皮点点头:虽然标题里并没有 "鱼皮教程" 这 4 个连着的字,但因为 ES 会自动分词,把 "鱼皮" 和 "教程" 拆开分别匹配,所以就搜到了。
你感叹道:哇,这才是搜索该有的样子啊!
查询语法 DSL
鱼皮:没错,ES 的搜索能力非常灵活强大。刚才你写的那些操作语句,其实用的就是 ES 的 DSL(Domain Specific Language 领域特定语言)。就像学数据库要学 SQL 一样,学 ES 就得学 DSL。不管是创建索引、插入文档,还是搜索查询,都是用这套 JSON 格式的语法来描述的。
其中最常用的就是查询语法,常见的查询类型有这么几种:
match是全文检索,会对搜索词分词之后再匹配term是精确匹配,不分词,适合查 id、状态这种bool可以组合多个条件,用must(必须满足)、should(最好满足)、must_not(必须不满足)来灵活控制range用来做范围查询,比如查某个时间段内的数据。
你不需要背这些语法,用到的时候问 AI 或者查文档就行,多写几次就熟了。
用代码操作 ES
你皱了皱眉:感觉写这些 JSON 格式的 DSL 还是有点麻烦啊,我用 Java 代码操作 ES 的时候,总不会也要手动拼这堆 JSON 吧?
鱼皮:当然不用!ES 官方提供了各种语言的客户端。比如你用 Java 语言,对应的是 Java API Client,支持链式调用和类型安全。
对于 Spring 项目来说,更推荐用 Spring Data Elasticsearch,它可以让你像用 MyBatis-Plus 操作 MySQL 一样操作 ES。只需要定义一个实体类,加上 @Document 注解指定要操作的索引,再写个 Repository 接口继承依赖包内置的 ES 操作接口。
@Document(indexName = "article")
public class Article {
@Id
private Long id;
private String title;
private String content;
}public interface ArticleRepository extends ElasticsearchRepository {
// 根据标题搜索
ListfindByTitle(String title);
}
框架会根据方法名自动生成查询逻辑,基本的增删改查方法就自动实现了。
// 使用示例
// 插入文档
articleRepository.save(article);
// 根据 id 查询
articleRepository.findById(1L);
// 根据标题搜索
articleRepository.findByTitle("鱼皮");
// 删除文档
articleRepository.deleteById(1L);
你感叹道:这才是人写的代码啊!优雅,真是优雅~ 我这就给 Java 代码整上 ES!
鱼皮:要注意,ES 版本更新很快,你用的客户端版本要跟安装的 ES 服务保持一致,不然会出各种奇奇怪怪的 Bug。
学会了基本操作之后,你兴冲冲地把 MySQL 数据库里的文章数据全部导入到了 ES,然后把网站的搜索功能改成从 ES 查询。上线后效果立竿见影,搜索又快又准,用户好评如潮。
你非常开心:阿巴,俺可真厉害!
鱼皮:不错不错,你已经掌握了 ES 的基本操作,算是学会 80% 了。不过 ES 还有很多值得学习的实用特性,进一步优化你的搜索功能。
倒排索引
鱼皮:先来考考你,你知道为什么 ES 能搜得又快又准么?
你挠挠头:阿巴阿巴……
鱼皮笑道:关键在于它使用了 倒排索引 来存储数据,这是 ES 最核心的特性。
举个例子,假设咱们要存 3 篇博客文档,用 MySQL 数据库的话,存储结构是这样的:
文档 id
文档内容
1
感谢关注鱼皮
2
鱼皮是一名程序员
3
感谢关注编程导航
这种结构下,如果用户搜 “鱼皮程序员”,MySQL 会傻乎乎地把它当成一整个词去匹配,结果可能啥也搜不到。
而 ES 的做法不一样。它会先把文档内容按照单词进行切分,这个过程叫 分词。然后再构建 单词到文档 id 的映射关系,也就是 倒排索引。
有了上述的倒排索引,当用户搜索 “鱼皮程序员” 时,搜索引擎数据库会先对搜索词进行分词,得到 “鱼皮” 和 “程序员”,然后根据这两个词汇就能找到文档 id 1、2 了。不用再一行一行遍历表内所有的数据,实现了更灵活、快速的 模糊搜索 。
你两眼放光:原来如此,牛啊牛啊!
但是 ES 怎么知道一句话该拆成哪些词呢?
分词器
鱼皮:好问题,这就要靠 分词器 了,它负责把一段文本拆成一个个词。
ES 内置了标准分词器,它基于 Unicode 文本分割算法设计,会按空格和标点符号等来切分文本。但这个规则只适合英文,对中文基本是一个字一个字地拆,效果很差。
所以如果你要做中文搜索,必须安装 IK 分词器。它是专门为中文设计的,能够智能识别中文词汇的边界,把句子正确地拆分成有意义的词语。
IK 提供了两种分词模式:
ik_smart是智能分词,尽量把词分得少一点,比如 "好学生" 就只会拆成 "好学生" 一个词ik_max_word是最大化分词,能拆的都拆,"好学生" 会被拆成 "好学生"、"好学"、"学生" 三个词。
一般建议索引的时候用 ik_max_word 尽可能多分词,搜索的时候用 ik_smart 提高精确度。
此外,IK 还支持自定义词典。比如你想让 “程序员鱼皮” 作为一个完整的词不被拆开,加到词典里就行了。
你好奇道:既然 ES 能分词,那能不能在搜索结果中把命中的关键词标红啊?
鱼皮:当然可以,ES 支持 高亮显示 功能。只需要在查询里加个 highlight 参数,指定要高亮的字段就行:
GET /article/_search
{
"query": {
"match": { "title": "鱼皮教程" }
},
"highlight": {
"fields": { "title": {} }
}
}
返回结果里,命中的关键词会自动被 标签包起来,前端拿到之后加个颜色样式就搞定了。
你两眼放光:这也太方便了吧!
不过还有个问题,现在虽然能够搜索到内容了,但怎么把最相关的结果排到前面呢?
相关性评分
鱼皮:好问题。ES 会给每个搜索结果计算一个分数,放到 _score 字段中,分数高的排在前面。
你好奇道:这个分数是怎么算的呢?
鱼皮:ES 默认用的是 BM25 算法,主要考虑三个因素:
词频 ,关键词在文档里出现的次数越多,分数越高。这很好理解,一篇文章里反复提到 "鱼皮",说明它很可能就是在讲鱼皮相关的内容。
文档长度 ,同样出现一次关键词,在短文档里占的比例更大,所以短文档的分数会更高一点。
稀有度 ,如果一个词在所有文档里都很常见,比如 "的"、"是",那它对搜索结果的区分度就不大。反过来,如果一个词很少见,只在少数文档里出现,那命中这个词的文档就更有价值,分数也更高。
鱼皮:除了搜索,ES 还有个很实用的功能叫 聚合分析,有点像 MySQL 的 GROUP BY 分组查询。
比如你想统计每个标签下有多少篇文章,写个聚合查询就行:
GET /article/_search
{
"size": 0,
"aggs": {
"tag_count": {
"terms": { "field": "tags" }
}
}
}
除了分组统计数量,ES 的聚合还能做求和、求平均值、找最大最小值、甚至多层嵌套聚合,能够满足开发各类数据报表的需求。
用了一段时间 ES 后,你开始有点儿飘了。
没事儿就对着新来的实习生阿坤吹牛皮:ES 我闭着眼睛都能写!什么分词、高亮、聚合,我都玩得贼溜儿~
结果没多久,老板黑着脸找到你:有用户投诉,说明明改了自己文章的标题,但是搜索出来还是旧的,怎么回事?
你排查后发现:原来是 ES 里的数据和 MySQL 数据库里的不一样!当初俺只是把数据一次性导入 ES,后来文章在数据库里更新了,但 ES 里还是旧数据。
你有些头大:唉,ES 和 MySQL 是两套独立的系统,数据不会自动同步啊,咋办啊?
这时,旁边的阿坤突然鸡叫起来:我来!
数据同步方案
阿坤一边打篮球一边说:MySQL 和 ES 的数据同步,一般有这么几种方案。
1)定时任务
每隔几分钟扫一遍数据库,把最近更新的数据同步到 ES。优点是实现简单,缺点是有一定延迟。适合数据更新不频繁、对实时性要求不高的场景。
2)双写
每次把数据写入 MySQL 的时候顺便也写一份到 ES。优点是能做到实时同步,缺点是会影响写入性能,而且如果 ES 写失败了还得处理数据不一致的问题。适合数据写入量不大的场景。
3)用 Logstash
它是 ES 官方提供的数据收集工具,可以配置从 MySQL 定时拉取数据同步到 ES。优点是不用写代码,全靠配置驱动,缺点是需要额外部署组件,灵活性也有限。
4)用 Canal 监听数据库
Canal 是阿里开源的一个工具,它会伪装成 MySQL 的从库,实时监听数据库的变更日志。数据库一有改动,Canal 立刻就能感知到,然后同步到 ES。优点是能做到实时同步,缺点是部署和运维相对麻烦一点。
像咱们这个文章系统,更新又不频繁,用户也能接受几分钟的延迟,用定时任务就完全够了。如果以后做电商那种对实时性要求高的系统,再考虑上 Canal。
集群部署
鱼皮走过来拍了拍阿坤的肩膀:不错不错,我再考考你们,如果 ES 服务器挂了怎么办?
你支支吾吾:重…… 重启?
鱼皮摇头:用户等得起吗?
阿坤:生产环境肯定不能只部署一台 ES 啊,得搭建 集群。
ES 集群中有几种角色的节点。主节点 负责管理集群的状态,比如哪些节点在线、索引的元数据等等。数据节点 负责存储实际的数据,处理读写请求。一般生产环境至少部署 3 个节点,保证高可用。
鱼皮追问:那如果数据量特别大,一个节点存不下怎么办?
你眼前一亮,终于等到自己会的问题了,抢答道:删除数据!
阿坤用看流浪狗的眼神看了你一眼,回答道:这就要说到 分片 了。分片就是把一个索引的数据拆成多份,分别存到不同的节点上。这样单个节点存不下的海量数据,也能通过多节点分担。而且多个节点可以并行处理查询请求,性能也更好。
你有些不服气:那万一某个节点挂了,上面的数据不就丢了?
阿坤:所以还需要 副本。副本就是分片的备份。每个分片可以配置若干个副本,存在其他节点上。万一某个节点挂了,副本可以顶上,这样数据就不会丢失,服务也不会中断。
鱼皮拍了拍阿坤的肩膀:小伙子年轻有为啊!
这些都是 ES 在生产环境必须考虑的问题,此外还要学习:
深度分页问题:ES 默认只允许查询前 10000 条数据,再往后翻就会报错。这是为了保护集群性能。如果确实需要给用户深度翻页,推荐使用更高效的 search_after。如果需要导出全量数据,可以结合 Point-in-Time API 使用。
性能调优技巧:合理设计 Mapping,该用 keyword 的别用 text;查询的时候多用 filter 少用 query,因为 filter 会缓存结果;还有控制返回字段的数量,别动不动就查全部字段。
ELK 日志方案:ES 最经典的应用场景之一就是做日志系统。ELK 是三个组件的缩写,E 是 Elasticsearch 负责存储和搜索日志,L 是 Logstash 负责收集和处理日志,K 是 Kibana 负责可视化展示。大厂排查线上问题,基本都靠这一套。
你羞愧地抬不起头:我以为自己已经掌握了 ES,原来只是学了个皮毛……
鱼皮:小阿巴,你还要好好跟阿坤学习啊。
被连环拷问后,你主动找到阿坤:坤哥,我想深入学习 ES 的底层原理,你是怎么学的?
阿坤有些惊讶:咦?你不背八股文的么?去 面试刷题网站 - 面试鸭 刷刷题就好了呀!
你震惊了:现在的实习生,竟然恐怖如斯!
鱼皮笑了笑:阿坤你别逗他了。其实可以带着问题去学习,比如 ES 为什么这么快?
你抢答道:因为倒排索引!
鱼皮:没错,但这只是一方面。ES 底层是基于 Lucene 搜索引擎库的,它的倒排索引结构经过了高度优化。另外 ES 会把常用的数据缓存在内存里,查询时优先从内存读取,速度自然快。再加上 ES 是分布式的,可以把数据分片存储到多个节点,并行处理查询请求,几方面加起来,性能就上去了。
再比如数据是怎么写入的、查询请求是怎么执行的?
从这些问题出发,去阅读相关的文章,或者像阿坤说的刷一刷 ES 高频面试题,就能快速学会很多核心知识点。
如果想系统学习,推荐看 ES 官方文档,因为 ES 的更新太快了,很多书籍可能已经跟不上节奏了。
若干年后,你已经成为了公司的 ES 搜索专家。不仅能熟练使用 ES 解决各种搜索问题,搭个集群架构也是手拿把掐的。
你也像鱼皮当时一样,耐心地给新人分享学习 ES 的经验,让他们谨记一句话:ES 是实战型技术,一定要多动手实践!
再次遇到鱼皮是在一条昏暗的小巷,此时的他年过 35,灰头土脸。你什么都没说,只是给他点了个赞,投了 2 个币。
不打扰,是你的温柔~
一些对大家有用的资源:
100+ 编程学习路线 / 实战项目 / 求职指导
100+ 简历模板
300+ 企业面试题库 mianshiya.com
500+ AI 资源大全
1 对 1 模拟面试
动画学算法教程
热门跟贴