Elasticsearch 学习笔记
一、为什么要用 Elasticsearch?(别再让 MySQL 哭了)
想象一下你在淘宝上搜"手机",结果 MySQL 在后台翻遍所有商品表、用户表、评论表......像在字典里一页一页找"手"字一样,慢得让人想砸电脑!
Elasticsearch(简称 ES)就是来拯救世界的!它专为全文检索 而生,支持多字段模糊匹配、高性能搜索、近实时响应。
MySQL:我负责存数据;
ES:我负责让你秒搜到!
二、什么是 Elasticsearch?
- 本质:基于 Lucene 的分布式全文检索服务器。
- 接口:提供 RESTful API(用 HTTP 就能操作,超友好)。
- 定位:不是数据库!是搜索引擎!别拿它当主库用(除非你想半夜被报警电话叫醒)。
三、ES 的核心原理:倒排索引(从"目录"找内容)
正排索引(传统方式):
文档 → 关键词
比如:《新华字典》正文 → 你一页一页翻找"奥利给"
倒排索引(ES 的魔法):
关键词 → 文档
比如:先看目录,"奥利给"在第 888 页 → 直接跳过去!
倒排索引表长这样:
| Term(关键词) | Document ID(文档ID) |
|---|---|
| 手机 | 1, 3, 5 |
| Python | 2 |
| 奥利给 | 4 |
分词规则(很讲究):
- 不重复:同一个词只存一次。
- 停用词过滤:"的、地、得、a、an、the"直接扔掉(谁会搜"的"?)。
- 非搜索字段不分词 :比如
image字段只存图片路径,不用分词。
✅ 搜索时,ES 先在 Term 列表里匹配关键词,再返回对应的文档!
四、ES 客户端怎么连?
| 客户端类型 | 状态 | 说明 |
|---|---|---|
TransportClient |
已淘汰 | 8.0+ 版本彻底移除,别用了! |
RestHighLevelClient |
✅ 官方推荐 | 通过 HTTP 调用,简单安全 |
⚠️ 注意:Spring Data Elasticsearch 没有注解版 !别指望
@Document自动同步(那是 MongoDB 的玩法)。
Spring Boot 启动器
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
五、Elasticsearch 安装指南(Linux 虚拟机版)
💡 前提:虚拟机内存 >1.5G,否则 ES 启动直接报错!
步骤 1:创建专用用户(安全第一!)
bash
groupadd es
useradd admin
passwd admin # 设置密码
usermod -G es admin # 加入 es 组
su admin # 切换用户
groups # 查看组(应包含 es)
步骤 2:授权目录
bash
su root
chown -R admin:es /usr/upload
chown -R admin:es /usr/local
步骤 3:上传并解压
bash
su admin
cd /usr/upload
tar -zxvf elasticsearch-6.2.3.tar.gz -C /usr/local
步骤 4:配置 elasticsearch.yml
yaml
cluster.name: power_shop
node.name: power_shop_node_1
network.host: 0.0.0.0 # 允许外网访问
http.port: 9200
transport.tcp.port: 9300
discovery.zen.ping.unicast.hosts: ["192.168.61.135:9300", "192.168.61.136:9300"]
path.data: /usr/local/elasticsearch-6.2.3/data
path.logs: /usr/local/elasticsearch-6.2.3/logs
http.cors.enabled: true # 允许跨域(前端调试必备)
http.cors.allow-origin: /.*/
步骤 5:修改系统限制(否则启动失败!)
bash
# 文件句柄数
echo "* soft nofile 65536" >> /etc/security/limits.conf
echo "* hard nofile 65536" >> /etc/security/limits.conf
# 虚拟内存映射
echo "vm.max_map_count=655360" >> /etc/sysctl.conf
sysctl -p # 生效
步骤 6:启动 & 测试
bash
su admin
cd /usr/local/elasticsearch-6.2.3/bin
./elasticsearch # 后台运行加 &
关闭:
bash
ps -ef | grep elasticsearch
kill -9 <pid>
测试访问:
👉 http://192.168.61.135:9200/
看到 JSON 返回?恭喜!ES 已上线!
六、ES 入门三板斧:Index、Type、Document
📌 注意:ES 7+ 已废弃 Type,但 6.x 还在用(本文基于 6.2.3)
1. Index 管理(相当于数据库)
创建 Index
json
PUT /java06
{
"settings": {
"number_of_shards": 2, // 主分片数(提升并发处理能力)
"number_of_replicas": 0 // 副本数(单机必须设为 0!否则 yellow/red)
}
}
🔥 重要 :主分片数一旦创建不可修改 !因为文档分配靠
hash(id) % shards。
修改副本数(可以改)
json
PUT /java06/_settings
{
"number_of_replicas": 1
}
删除 Index
json
DELETE /java06
2. Type 管理(相当于表)
json
POST /java06/_mapping/course
{
"properties": {
"name": { "type": "text" },
"description": { "type": "text" },
"studymodel": { "type": "keyword" }
}
}
💡
text用于全文搜索,keyword用于精确匹配(如状态码、标签)。
3. Document 管理(相当于行数据)
新增
json
POST /java06/course/1
{
"name": "Python从入门到放弃",
"description": "人生苦短,我用Python",
"studymodel": "201002"
}
修改(全量覆盖)
json
PUT /java06/course/1
{
"name": "Python从入门到精通",
"description": "人生苦短,我用Python",
"studymodel": "201002"
}
删除 & 查询
json
DELETE /java06/course/1
GET /java06/course/1
七、IK 分词器:让中文搜索不再"智障"
默认 ES 对中文按单字分词("手机" → "手"、"机"),搜不到!
解决方案:安装 IK 分词器!
安装步骤:
- 下载
elasticsearch-analysis-ik-6.2.3.zip - 解压到
/usr/local/elasticsearch-6.2.3/plugins/ik - 重启 ES
自定义词典(让"奥利给"也能搜!)
- 配置文件:
IKAnalyzer.cfg.xml - 扩展词典:
main.dic→ 添加奥利给 - 停用词典:
stopword.dic→ 添加的、地、得
⚠️ 文件必须 UTF-8 编码!否则乱码!
两种分词模式:
| 模式 | 用途 | 示例("中华人民共和国") |
|---|---|---|
ik_smart |
搜索时用(粗粒度) | 中华人民共和国 |
ik_max_word |
写入时用(细粒度) | 中华、华人、人民、共和国...... |
使用示例:
json
PUT /news
{
"settings": {
"analysis": {
"analyzer": "ik_max_word"
}
},
"mappings": {
"article": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}
}
}
八、Field 字段详解(设计表结构的关键)
1. 数据类型
- 文本 :
text(分词)、keyword(不分词,精确匹配) - 数字 :
integer,long,float,double - 日期 :
date - 布尔 :
boolean
2. 关键属性
| 属性 | 作用 | 示例 |
|---|---|---|
type |
数据类型 | "type": "text" |
analyzer |
写入时分词器 | "analyzer": "ik_max_word" |
search_analyzer |
搜索时分词器 | "search_analyzer": "ik_smart" |
index |
是否建立索引 | "index": false(不参与搜索) |
_source |
是否存储原始文档 | 可设置 excludes 隐藏敏感字段 |
3. 设计原则(灵魂三问):
- 这个字段需要分词吗? → 决定
type是text还是keyword - 这个字段会被搜索吗? → 决定
index是否为true - 这个字段需要返回给前端吗? → 决定是否放入
_source.excludes
九、文档搜索:从简单查询到高亮显示
准备工作:先往索引里塞点数据!
9.1 准备测试数据
我们向 java06/course 索引中插入三条课程数据:
json
PUT /java06/course/1
{
"name": "Bootstrap开发",
"description": "Bootstrap是由Twitter推出的一个前台页面开发css框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长css页面开发的程序人员)轻松的实现一个css,不受浏览器限制的精美界面css效果。",
"studymodel": "201002",
"price":38.6,
"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}
PUT /java06/course/2
{
"name": "java编程基础",
"description": "java语言是世界第一编程语言,在软件开发领域使用人数最多。",
"studymodel": "201001",
"price":68.6,
"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}
PUT /java06/course/3
{
"name": "spring开发基础",
"description": "spring 在java领域非常流行,java程序员都在用。",
"studymodel": "201001",
"price":88.6,
"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}
9.2 简单搜索(URI Search)
通过 URL 直接传参查询,适合简单场景。
语法:
GET /index/type/_search?q=字段:值&sort=字段:asc/desc
示例:
json
GET /java06/course/_search?q=name:spring&sort=price:desc
⚠️ 缺点:条件复杂时 URL 会爆炸!比如"价格1000~5000、销量>500、分页第2页每页40条"------根本写不下!
Java 客户端查询单文档:
java
@Test
public void getDoc() throws IOException {
GetRequest getRequest = new GetRequest("java06","course","1");
GetResponse getResponse = restHighLevelClient.get(getRequest);
boolean exists = getResponse.isExists();
System.out.println(exists);
String source = getResponse.getSourceAsString();
System.out.println(source);
}
9.3 DSL 搜索(生产首选!)
DSL(Domain Specific Language)是 ES 推荐的 JSON 格式查询方式,功能强大、结构清晰。
基本语法:
json
GET /index/type/_search
{
"query": { ... },
"from": 0,
"size": 10,
"sort": [...],
"highlight": { ... }
}
9.3.1 match_all 查询(查所有)
API:
json
GET /java06/course/_search
{
"query": {
"match_all": {}
}
}
Java 客户端:
java
@Test
public void testMatchAll() throws IOException {
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchAllQuery());
SearchRequest request = new SearchRequest("java06");
request.types("course");
request.source(builder);
SearchResponse response = restHighLevelClient.search(request);
for (SearchHit hit : response.getHits()) {
System.out.println(hit.getSourceAsString());
}
}
9.3.2 分页查询
API:
json
GET /java06/course/_search
{
"query": { "match_all": {} },
"from": 1, // 从第1条开始(0起始)
"size": 3, // 返回3条
"sort": [{ "price": "asc" }]
}
Java 客户端:
java
@Test
public void testSearchPage() throws Exception {
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchAllQuery())
.from(1)
.size(5)
.sort("price", SortOrder.ASC);
searchRequest.source(builder);
searchResponse = restHighLevelClient.search(searchRequest);
}
9.3.3 match 查询(全文检索)
将搜索词分词后匹配,支持 operator 控制逻辑。
API 示例:
json
// OR 逻辑(默认):包含"spring"或"开发"即可
GET /java06/course/_search
{
"query": {
"match": {
"name": "spring开发"
}
}
}
// AND 逻辑:必须同时包含"spring"和"开发"
GET /java06/course/_search
{
"query": {
"match": {
"name": {
"query": "spring开发",
"operator": "and"
}
}
}
}
Java 客户端:
java
@Test
public void testMatchQuery() throws Exception {
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchQuery("name", "spring开发")
.operator(Operator.AND));
searchRequest.source(builder);
searchResponse = restHighLevelClient.search(searchRequest);
}
9.3.4 multi_match 查询(多字段匹配)
在多个字段中搜索同一个关键词。
API:
json
GET /java06/course/_search
{
"query": {
"multi_match": {
"query": "开发",
"fields": ["name", "description"]
}
}
}
Java 客户端:
java
@Test
public void testMultiMatchQuery() throws Exception {
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.multiMatchQuery("开发", "name", "description"));
searchRequest.source(builder);
searchResponse = restHighLevelClient.search(searchRequest);
}
9.3.5 bool 查询(组合条件)
用 must、should、must_not 组合复杂逻辑。
API 示例:
json
// 必须 name 包含"开发" 且 price 在 50~100 之间
GET /java06/course/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "开发" } },
{ "range": { "price": { "gte": 50, "lte": 100 } } }
]
}
}
}
Java 客户端:
java
@Test
public void testBooleanMatch() throws IOException {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", "开发"))
.must(QueryBuilders.rangeQuery("price").gte(50).lte(100));
SearchSourceBuilder builder = new SearchSourceBuilder().query(boolQuery);
searchRequest.source(builder);
SearchResponse response = restHighLevelClient.search(searchRequest);
}
9.3.6 filter 查询(高效过滤)
filter 不计算相关度分数,性能更高,适合范围、精确匹配等场景。
API 对比:
json
// 普通查询(计算分数)
"must": [ { "range": { "price": { "gte": 10, "lte": 100 } } } ]
// filter(不计算分数,更快)
"filter": { "range": { "price": { "gte": 10, "lte": 100 } } }
完整示例:
json
GET /java06/course/_search
{
"query": {
"bool": {
"must": [ { "match": { "name": "开发" } } ],
"filter": { "range": { "price": { "gte": 1, "lte": 100 } } }
}
}
}
Java 客户端:
java
@Test
public void testFilterQuery() throws IOException {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", "开发"))
.filter(QueryBuilders.rangeQuery("price").gte(10).lte(100));
SearchSourceBuilder builder = new SearchSourceBuilder().query(boolQuery);
searchRequest.source(builder);
searchResponse = restHighLevelClient.search(searchRequest);
}
9.3.7 highlight 高亮显示
让搜索关键词在结果中高亮显示,提升用户体验!
API:
json
GET /java06/course/_search
{
"query": { "match": { "name": "开发" } },
"highlight": {
"pre_tags": ["<font color='red'>"],
"post_tags": ["</font>"],
"fields": { "name": {} }
}
}
Java 客户端(查询 + 遍历):
java
@Test
public void testHighLightQuery() throws Exception {
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchQuery("name", "spring"));
HighlightBuilder highlight = new HighlightBuilder();
highlight.preTags("<font color='red'>")
.postTags("</font>")
.field("name");
builder.highlighter(highlight);
searchRequest.source(builder);
searchResponse = restHighLevelClient.search(searchRequest);
}
@After
public void displayDoc() {
for (SearchHit hit : searchResponse.getHits()) {
System.out.println("原始数据: " + hit.getSourceAsString());
HighlightField nameHighlight = hit.getHighlightFields().get("name");
if (nameHighlight != null) {
System.out.println("高亮结果: " + nameHighlight.getFragments()[0]);
}
}
}
💡 高亮返回的是片段(fragments) ,不是整个字段!记得取
fragments[0].toString()。
十、ES 集群搭建(高可用不是梦)
步骤:
-
复制一份 ES 到另一台机器(或同一台不同目录)
-
删除
data目录(避免冲突) -
修改
elasticsearch.yml:yamlnode.name: power_shop_node_2 network.host: 0.0.0.0 http.port: 9201 transport.tcp.port: 9301
集群健康状态:
| 颜色 | 含义 | 说明 |
|---|---|---|
| 🟢 Green | 完美 | 所有主分片和副本都正常 |
| 🟡 Yellow | 警告 | 主分片正常,副本缺失(单节点常见) |
| 🔴 Red | 危险 | 主分片丢失,数据不完整! |
实验验证:
- 设置
replicas=1→ 两节点 → Green - 关掉一个节点 → Yellow(副本没了,但数据还在)
- 设
replicas=0再关节点 → Red(主分片没了!)
十一、总结:ES 使用 Checklist
✅ 单机开发:replicas=0
✅ 中文搜索:必装 IK 分词器
✅ 字段设计:问清"是否搜索""是否展示"
✅ 集群部署:至少 3 节点防脑裂
✅ 不要拿 ES 当数据库!它不保证事务!
✅ 复杂查询用 DSL ,别用 URI!
✅ 范围/精确过滤用 filter ,性能更高!
✅ 关键词高亮用 highlight,用户体验拉满!