1. 基础篇
1.1 起源:Lucene
- Lucene:Apache 开源的 Java 全文搜索引擎类库。
- 优势:易扩展、高性能(纯 Java,可嵌入)。
- 缺点:使用复杂,需处理索引创建、查询解析等底层细节,无分布式支持。
- Elasticsearch 基于 Lucene 构建,提供分布式、易用的 RESTful API。
1.2 技术栈(ELK)
- Elasticsearch:存储、搜索和分析引擎。
- Logstash:服务器端数据处理管道,采集、转换数据后发送至 ES。
- Kibana:可视化平台,制作图表、仪表板,管理 ES。
- Beats:轻量型数据采集器,发送到 Logstash 或 ES。
1.3 使用 Docker 安装
Elasticsearch(单节点):
docker run -d --name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network testNet \
-p 9200:9200 -p 9300:9300 \
elasticsearch:7.12.1
- 9200:HTTP API 端口
- 9300:内部节点通信端口
discovery.type=single-node:单节点模式(测试用)
Kibana:
docker run -d --name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=testNet \
-p 5601:5601 \
kibana:7.12.1
1.4 倒排索引(Inverted Index)
- 正向索引:文档 ID → 文档内容。适合根据 ID 精确查找,但做模糊搜索需遍历所有文档,效率低。
- 倒排索引:
- 文档(Document):ES 中存储的一条 JSON 数据。
- 词条(Term):文档经过分词后的最小单元。
- 结构:词条 → 文档 ID 列表(及位置、频率等)。
- 优点:快速定位包含某个词条的所有文档,实现高效全文搜索。
1.5 分词器(Analyzer)
将文本拆分为词条(term)的组件,由三部分组成:
- Character Filter:预处理(如去除 HTML 标签)。
- Tokenizer:按规则分词。
- Token Filter:对词条再加工(转小写、去除停用词)。
IK 分词器:最常用的中文分词插件。
- 安装:下载对应版本 zip 放到
plugins/ik目录,重启 ES。 - 两种模式:
ik_smart:最粗粒度拆分。ik_max_word:最细粒度拆分。
- 自定义词典:修改
IKAnalyzer.cfg.xml,添加ext.dic(新词)、stopword.dic(停用词)。
1.6 基础概念
- 索引(Index):相同类型文档的集合,类似数据库中的“表”。
- 文档(Document):ES 存储的基本单元,序列化为 JSON。
- 映射(Mapping):定义文档字段的类型、分词器等,类似数据库中的“Schema”。
Mapping 常用字段类型:
- 字符串:
text(分词)、keyword(精确匹配,不分词)。 - 数值:
long,integer,short,byte,double,float。 - 日期:
date。 - 布尔:
boolean。 - 地理:
geo_point(经纬度)、geo_shape(区域)。 - 对象:
object(嵌套字段)。 - 数组:不专门定义,ES 自动支持。
2. 索引库操作(RESTful API)
所有操作通过 HTTP API 发送 JSON 格式数据。
2.1 创建索引(Create)
PUT /索引名
{
"settings": {
"number_of_shards": 3, // 分片数
"number_of_replicas": 2 // 副本数
},
"mappings": {
"properties": {
"字段名": {
"type": "text", // 字段类型
"analyzer": "ik_max_word" // 分词器
}
}
}
}
2.2 查看索引(Read)
GET /索引名
GET /索引名/_mapping
GET /_cat/indices?v # 查看所有索引
2.3 修改索引(Update)
- 全量修改文档:PUT 请求带全量 JSON 数据,相当于先删除旧文档再创建。
- 局部更新文档:POST 请求使用
_updateAPI。
POST /索引名/_update/文档ID
{
"doc": {
"字段": "新值"
}
}
Mapping 更新限制:字段类型一旦创建,多数不可修改(如 text 改为 keyword),但可增加新字段。若需修改类型,需重建索引。
2.4 删除索引(Delete)
DELETE /索引名
2.5 批量操作(Bulk)
一条请求完成多个文档的增删改。
POST /_bulk
{"index":{"_index":"idx","_id":"1"}}
{"title":"Doc 1"}
{"delete":{"_index":"idx","_id":"2"}}
{"update":{"_index":"idx","_id":"3"}}
{"doc":{"title":"Updated"}}
从数据库表设计 Mapping 的实践:
- MySQL 的
varchar→ 需全文搜索用text,需精确查询/排序用keyword。 - 数字类型对应
integer/long/float。 - 日期类型统一用
date,可指定格式。 - 涉及经纬度查询用
geo_point。 - 不需要搜索的字段设置
index: false节省空间。
3. Java REST 客户端
官方推荐 Java High Level REST Client(7.x 版本),Elasticsearch Java API Client(8.x+ 推荐)。此处以 7.x 高级客户端为例。
引入依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.12.1</version>
</dependency>
初始化客户端:
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
3.1 索引库操作
// 创建索引
CreateIndexRequest request = new CreateIndexRequest("my_index");
request.settings(Settings.builder()
.put("index.number_of_shards", 3)
.put("index.number_of_replicas", 2));
request.mapping("{\"properties\":{...}}", XContentType.JSON);
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
// 删除索引
DeleteIndexRequest deleteRequest = new DeleteIndexRequest("my_index");
client.indices().delete(deleteRequest, RequestOptions.DEFAULT);
// 存在性检查
GetIndexRequest getRequest = new GetIndexRequest("my_index");
boolean exists = client.indices().exists(getRequest, RequestOptions.DEFAULT);
3.2 文档操作
// 添加文档
IndexRequest indexReq = new IndexRequest("my_index").id("1");
User user = new User("张三", 25);
indexReq.source(JSON.toJSONString(user), XContentType.JSON);
IndexResponse indexResp = client.index(indexReq, RequestOptions.DEFAULT);
// 局部更新
UpdateRequest updateReq = new UpdateRequest("my_index", "1")
.doc("age", 26);
client.update(updateReq, RequestOptions.DEFAULT);
// 批量操作
BulkRequest bulk = new BulkRequest();
bulk.add(new IndexRequest("my_index").id("1").source(...));
bulk.add(new DeleteRequest("my_index", "2"));
client.bulk(bulk, RequestOptions.DEFAULT);
3.3 查询操作
构建查询条件,执行搜索。
SearchRequest searchRequest = new SearchRequest("my_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("title", "elasticsearch"));
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
User user = JSON.parseObject(json, User.class);
}
4. DSL 查询语法
DSL(Domain Specific Language)基于 JSON 构建查询,是 ES 最强大的搜索方式。
4.1 查询分类概览
- 叶子查询:在特定字段查询具体值。
- 全文检索(Full Text):分词后匹配,有相关性算分。
- 精确查询(Term-Level):不分词,直接精确匹配词条。
- 地理查询(Geo):坐标查询。
- 复合查询:组合多个叶子或复合查询,修改算分、过滤等。
- 特殊查询:如
script,exists等。
4.2 全文检索查询
适用于 text 字段,会分词并计算相关性分数 _score。
match:模糊匹配,分词后 OR 关系。
GET /index/_search
{
"query": {
"match": {
"content": "Elasticsearch 入门"
}
}
}
match_phrase:短语匹配,要求分词后顺序一致、连续。
{ "query": { "match_phrase": { "content": "Elasticsearch 入门" } } }
multi_match:多字段匹配。
{ "query": { "multi_match": { "query": "入门", "fields": ["title", "content"] } } }
4.3 精确查询
适用于 keyword、数字、日期、布尔,不计算相关性分数。
term:精确值匹配。
{ "query": { "term": { "status": "active" } } }
range:范围查询。
{
"query": {
"range": {
"price": { "gte": 100, "lte": 500 }
}
}
}
terms:多值精确匹配。
{ "query": { "terms": { "category": ["科技", "教育"] } } }
4.4 地理查询(Geo)
需要字段类型为 geo_point。
// 矩形范围
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": { "lat": 40, "lon": 116 },
"bottom_right": { "lat": 39, "lon": 117 }
}
}
}
}
// 距离范围(圆心半径)
{
"query": {
"geo_distance": {
"distance": "10km",
"location": { "lat": 39.9, "lon": 116.4 }
}
}
}
4.5 复合查询(Compound Queries)
bool 查询:组合多个条件,包含 must(AND)、should(OR)、must_not(NOT)、filter(过滤,不计算分数)。
{
"query": {
"bool": {
"must": [
{ "match": { "title": "手机" } }
],
"filter": [
{ "range": { "price": { "gte": 1000, "lte": 3000 } } },
{ "term": { "brand": "华为" } }
],
"must_not": [
{ "term": { "status": "下架" } }
],
"should": [
{ "term": { "feature": "5G" } },
{ "term": { "feature": "快充" } }
],
"minimum_should_match": 1
}
}
}
filter不参与算分,性能更好,建议首选用于过滤条件。
4.6 排序和分页
排序:
{
"query": { "match_all": {} },
"sort": [
{ "price": "desc" },
{ "_score": "desc" }
]
}
对 text 字段排序需打开 fielddata 或使用 keyword 子字段。
分页:
{
"query": { "match_all": {} },
"from": 0, // 起始偏移量
"size": 10 // 每页文档数
}
深度分页问题:
- 随着
from增大,ES 需要从每个分片取from+size条数据在协调节点排序,内存与耗时急剧增加。 - 解决方案:
- search_after:利用上一页的最后一条文档的排序值,实时查询下一页,无
from开销,但不支持跳页。 - scroll:生成数据快照,适合遍历所有数据(如导出),但非实时。
- 限制分页深度(如
max_result_window: 10000)。
- search_after:利用上一页的最后一条文档的排序值,实时查询下一页,无
4.7 高亮显示(Highlight)
{
"query": { "match": { "content": "elasticsearch" } },
"highlight": {
"fields": {
"content": {
"pre_tags": ["<em>"],
"post_tags": ["</em>"]
}
}
}
}
响应中会额外返回 highlight 字段,包含高亮片段。
4.8 数据聚合(Aggregations)
聚合可从数据中提取统计、分组等分析信息。
聚合分类:
- Bucket(桶聚合):分组,如
terms,range,date_histogram。 - Metric(指标聚合):计算统计值,如
avg,sum,max,min,stats。 - Pipeline(管道聚合):对其他聚合结果再次计算。
DSL 聚合示例:
{
"size": 0, // 不返回文档,只关心聚合结果
"aggs": {
"brand_group": {
"terms": { "field": "brand" },
"aggs": {
"avg_price": { "avg": { "field": "price" } }
}
}
}
}
含义:按品牌分组,计算每组的平均价格。
Java 客户端实现聚合:
SearchRequest searchRequest = new SearchRequest("items");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchAllQuery());
// 聚合构建
TermsAggregationBuilder aggregation = AggregationBuilders
.terms("brand_group")
.field("brand.keyword")
.subAggregation(AggregationBuilders.avg("avg_price").field("price"));
sourceBuilder.aggregation(aggregation);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
Aggregations aggs = response.getAggregations();
Terms brandTerms = aggs.get("brand_group");
for (Terms.Bucket bucket : brandTerms.getBuckets()) {
String brand = bucket.getKeyAsString();
Avg avgPrice = bucket.getAggregations().get("avg_price");
System.out.println(brand + ":" + avgPrice.getValue());
}
5. ES 核心原理补充
5.1 分片与副本
- 分片(Shard):索引被水平拆分为多个分片,每个分片是一个独立的 Lucene 索引。
- 优势:分布式存储,并行搜索,水平扩展容量和吞吐。
- 副本(Replica):每个主分片的拷贝,提供高可用,分担查询压力。
- 分片数在索引创建后不可修改(可通过
_split/_shrinkAPI 调整)。 - 副本数可动态调整。
- 分片数在索引创建后不可修改(可通过
5.2 写入与搜索流程
- 写入:文档被路由到某个主分片,主分片写入后转发给副本,全部完成才返回确认。
- 搜索:协调节点将请求转发给相关分片(主或副本),每个分片返回结果,协调节点汇总排序。
5.3 优化建议
- 避免字段过多、深度聚合。
- 使用
filter代替must减少评分计算。 - 合理设计 Mapping,用
keyword代替text做精确搜索。 - 冷热分离,结合索引生命周期管理(ILM)。