第一部分:Elasticsearch & Kibana 核心概念

1. 什么是 Elasticsearch (ES)?

Elasticsearch 是一个基于 Apache Lucene 构建的分布式、RESTful 风格的搜索和数据分析引擎。

  • 核心功能
    • 全文搜索:类似 Google,快速在海量文本中检索关键词。
    • 结构化搜索:查找特定字段(例如:查找“价格 > 100”且“库存 > 0”的商品)。
    • 数据分析:聚合统计(例如:计算“上个月各品牌的平均销售额”)。
  • 核心概念对比 (ES vs 关系型数据库):

为了方便理解,我们可以将 ES 的概念映射到 MySQL 中:

Elasticsearch 概念

关系型数据库 (MySQL)

描述

Index (索引)

Database / Table

文档的集合,数据存储的地方。

Document (文档)

Row (行)

存储数据的基本单元,JSON 格式。

Field (字段)

Column (列)

文档中的属性(Key-Value)。

Node (节点)

Server Instance

单台运行 ES 的服务器。

Cluster (集群)

Database Cluster

多台节点协同工作,保证高可用。

2. 什么是 Kibana?

Kibana 是 ES 的“脸面”。如果说 ES 是后端引擎,Kibana 就是前端操作台。

  • 核心用途
    • Dev Tools (开发工具):开发人员神器,可直接编写 DSL 语句与 ES 交互。
    • 数据可视化:生成饼图、柱状图、热力图等。
    • 集群监控:查看索引状态、磁盘与内存使用率。

第二部分:下载与安装 (v8.x 快速上手版)

环境说明:本教程基于 Elasticsearch 8.14.0 和 Kibana 8.14.0。

重要前提:ES 和 Kibana 版本必须严格一致。

1. 官方下载地址

请根据你的操作系统(Windows/macOS/Linux)下载对应版本:

2. Elasticsearch 安装与配置

ES 8.x 默认开启了强安全模式(HTTPS + SSL + 密码),这对本地开发调试非常繁琐。为了快速跑通代码,我们采用免密 HTTP 模式

第一步:解压并修改配置

进入解压后的 config 目录,编辑 elasticsearch.yml。请直接在文件末尾添加或修改以下关键配置:

# ======================== 核心配置 (本地开发用) ========================

# 1. 关闭安全认证 (核心:关闭后连接无需账号密码,避免证书报错)
xpack.security.enabled: false

# 2. 关闭自动注册流程
xpack.security.enrollment.enabled: false

# 3. 禁用 HTTP SSL 加密 (核心:允许使用 http://localhost:9200 访问)
xpack.security.http.ssl:
  enabled: false

# 4. 允许跨域 (可选:方便第三方可视化工具连接)
http.cors.enabled: true
http.cors.allow-origin: "*"

第二步:启动 ES

  • Windows: 双击 bin\elasticsearch.bat
  • Mac/Linux: 终端运行 ./bin/elasticsearch

第三步:验证

浏览器访问 http://localhost:9200。

如果看到一段包含 "tagline": "You Know, for Search" 的 JSON 文本,说明启动成功!

3. Kibana 安装与配置

第一步:解压并修改配置

进入 config 目录,编辑 kibana.yml:

# Kibana 端口
server.port: 5601

# 指向 ES 地址 (因为上面禁用了 SSL,这里用 http)
elasticsearch.hosts: ["http://localhost:9200"]

# 设置中文界面 (可选)
i18n.locale: "zh-CN"

第二步:启动 Kibana

  • Windows: 双击 bin\kibana.bat
  • Mac/Linux: 终端运行 ./bin/kibana

第三步:验证

浏览器访问 http://localhost:5601。


第三部分:安装 IK 中文分词器 (必装插件)

ES 默认分词器会将中文拆成单字(例如“华为手机”拆成“华、为、手、机”),这不符合中文搜索习惯。IK 分词器支持智能中文切分。

  1. 安装命令

在 Elasticsearch 安装根目录下打开终端/CMD,执行:

./bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.14.0

(如果提示 [y/N],输入 y 确认安装)

  1. 重启生效 (重要)

安装完成后,必须重启 Elasticsearch 服务,插件才会生效。

第四部分:讲解Elasticsearch的查询

1. 索引与映射管理 (Index & Mapping)

在 Elasticsearch 8.x 中,显式定义 Mapping 依然是最佳实践,特别是为了区分全文检索字段和精确匹配字段。

1.1 创建索引 (Create Index)

定义索引结构,包括分片设置、字段类型以及 8.x 的向量字段定义。

JSON

PUT /products
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "name": { 
        "type": "text",            // 文本:分词,用于全文检索
        "analyzer": "standard" 
      },
      "sku": {
        "type": "keyword"          // 关键字:不分词,用于精确匹配/聚合
      },
      "price": { "type": "double" },
      "created_at": { "type": "date" },
      "status": { "type": "keyword" },
      "feature_vector": {          // 8.x Dense Vector 字段
        "type": "dense_vector",
        "dims": 3,                 // 向量维度
        "index": true,             // 允许被索引用于搜索
        "similarity": "cosine"     // 相似度算法:cosine, l2_norm, dot_product
      }
    }
  }
}
1.2 查看与删除 (View & Delete)
  • 查看 Mapping:GET /products/_mapping
  • 删除索引:DELETE /products

2. 文档 CRUD 操作 (Document Operations)

2.1 写入文档 (Index)

指定 ID 写入(如果 ID 存在则覆盖,版本号增加)。

JSON

POST /products/_doc/101
{
  "name": "Sony Noise Cancelling Headphones",
  "sku": "WH-1000XM5",
  "price": 349.99,
  "status": "active",
  "created_at": "2024-06-01T12:00:00Z",
  "feature_vector": [0.1, 0.5, 0.9]
}
2.2 局部更新 (Update)

仅修改文档中的部分字段,无需重新提交整个文档。

JSON

POST /products/_update/101
{
  "doc": {
    "price": 299.99,
    "status": "on_sale"
  }
}
2.3 查询单条与删除 (Get & Delete)
  • 根据 ID 获取:GET /products/_doc/101
  • 根据 ID 删除:DELETE /products/_doc/101

3. 基础查询 DSL (Basic Queries)

3.1 精确查询 (Term Level)

适用于 keyworddateboolean 和数字类型。切勿对 text 字段使用此类查询。

Term (单值):

  • JSON
GET /products/_search
{
  "query": {
    "term": { "status": "active" }
  }
}

Range (范围):

  • JSON
GET /products/_search
{
  "query": {
    "range": {
      "price": { "gte": 100, "lte": 500 } // >= 100 且 <= 500
    }
  }
}

IDs (主键查询):

  • JSON
GET /products/_search
{
  "query": { "ids": { "values": ["101", "102"] } }
}
3.2 全文检索 (Full Text)

适用于 text 类型,查询词会被分词器处理。

Match (标准匹配):

  • JSON
GET /products/_search
{
  "query": {
    "match": { "name": "sony headphones" } 
    // 查找包含 "sony" 或 "headphones" 的文档
  }
}

Match Phrase (短语匹配):

  • JSON
GET /products/_search
{
  "query": {
    "match_phrase": { 
      "name": "noise cancelling" 
      // "noise" 和 "cancelling" 必须相邻且顺序一致
    }
  }
}

4. 复合查询 (Compound Queries)

4.1 Bool 查询 (Boolean)

这是 ES 中最核心的组合逻辑,包含 must (AND), should (OR), must_not (NOT), 和 filter (AND, 无算分)。

JSON

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "headphones" }}  // 必须匹配,贡献算分
      ],
      "filter": [
        { "term": { "status": "active" }},    // 必须匹配,不贡献算分(高性能缓存)
        { "range": { "price": { "lt": 1000 }}}
      ],
      "must_not": [
        { "term": { "sku": "OLD-MODEL-001" }} // 排查特定款
      ],
      "should": [
        { "term": { "sku": "WH-1000XM5" }}    // 如果匹配,得分更高
      ]
    }
  }
}

5. 聚合分析 (Aggregations)

用于数据统计、报表生成。

5.1 桶聚合 (Bucket Aggregations)

类似于 SQL 的 Group By。

JSON

GET /products/_search
{
  "size": 0, // 不返回具体文档
  "aggs": {
    "status_distribution": {
      "terms": {
        "field": "status",
        "size": 10
      },
      "aggs": {
        // 子聚合:计算每种状态下的平均价格
        "avg_price_per_status": {
          "avg": { "field": "price" }
        }
      }
    }
  }
}
5.2 日期直方图 (Date Histogram)

按时间维度统计(如:按月统计销量)。

JSON

GET /products/_search
{
  "size": 0,
  "aggs": {
    "monthly_additions": {
      "date_histogram": {
        "field": "created_at",
        "calendar_interval": "month"
      }
    }
  }
}

6. 8.14.0 向量搜索与高级特性 (Advanced & Vector)

6.1 KNN 向量搜索 (k-Nearest Neighbors)

ES 8.x 将 KNN 提升为一级查询参数,支持与传统 DSL 混合使用。

JSON

GET /products/_search
{
  "knn": {
    "field": "feature_vector",
    "query_vector": [0.1, 0.5, 0.9], // 待查询向量
    "k": 5,                          // 返回最近的5个
    "num_candidates": 50             // 每个分片搜索的候选数量
  },
  "_source": ["name", "price"]
}
6.2 排序与深度分页 (Sort & Pagination)
  • 基础分页: 使用 fromsize

排序:

  • JSON
GET /products/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "price": { "order": "desc" }},
    { "_score": { "order": "desc" }}
  ]
}
  • Search After (深度分页推荐): 用于处理超过 10,000 条数据的导出场景,需配合 sort 结果中的 values 使用。
6.3 搜索高亮 (Highlighting)

在结果中用 HTML 标签包裹匹配词。

JSON

GET /products/_search
{
  "query": { "match": { "name": "sony" }},
  "highlight": {
    "pre_tags": ["<em>"],
    "post_tags": ["</em>"],
    "fields": { "name": {} }
  }
}

7. 辅助工具与 SQL (Tools & SQL)

7.1 SQL 查询支持

适合熟悉 SQL 但不熟悉 DSL 的用户。

JSON

POST /_sql?format=txt
{
  "query": "SELECT name, price FROM products WHERE price > 300 ORDER BY price DESC LIMIT 5"
}
7.2 Explain API (排查算分)

理解为什么某个文档被搜出来,或者为什么排名靠前。

JSON

GET /products/_explain/101
{
  "query": {
    "match": { "name": "headphones" }
  }
}

第五部分:Spring Boot 整合 Elasticsearch (代码实战)

1. 添加 Maven 依赖 (pom.xml)

注意:这是之前文档缺失的部分,必须添加依赖才能运行代码。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>co.elastic.clients</groupId>
        <artifactId>elasticsearch-java</artifactId>
        <version>8.14.0</version> </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 配置文件 (application.properties)

elasticsearch.host=localhost
elasticsearch.port=9200
# 如果在 elasticsearch.yml 中设置了 xpack.security.enabled: false
# 下面的账号密码其实是不生效的,但在代码中保留可以方便后续开启安全认证
elasticsearch.username=elastic
elasticsearch.password=MurroR2T0*OmihFJH3C2

3. 配置类 (ElasticsearchConfig.java)

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticsearchConfig {

    @Value("${elasticsearch.host}")
    private String host;

    @Value("${elasticsearch.port}")
    private int port;

    @Value("${elasticsearch.username}")
    private String username;

    @Value("${elasticsearch.password}")
    private String password;

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        // 1. 创建凭证提供者
        // (注:如果 ES 关闭了 Security,这部分其实不会被服务端验证,但保留无妨)
        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
                AuthScope.ANY,
                new UsernamePasswordCredentials(username, password)
        );

        // 2. 创建 RestClient(使用 HTTP,无 SSL)
        RestClient restClient = RestClient
                .builder(new HttpHost(host, port, "http"))
                .setHttpClientConfigCallback(httpClientBuilder ->
                        httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
                )
                .build();

        // 3. 创建 Transport 和 Client
        ElasticsearchTransport transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper());

        return new ElasticsearchClient(transport);
    }
}

4. 测试类 (EsTest.java)

1. 索引管理 (Index Management)
创建索引
@Test
void contextLoads() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest.Builder().index(EsUtils.EsIndex).build();

    CreateIndexResponse response = elasticsearchClient.indices().create(request);
    System.out.println(response);
}
检查索引是否存在
@Test
void exitEs() throws IOException {
    ExistsRequest request = new ExistsRequest.Builder().index(EsUtils.EsIndex).build();

    BooleanResponse exists = elasticsearchClient.indices().exists(request);
    System.out.println(exists.value());
}
删除索引
@Test
void deleteIndex() throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest.Builder().index(EsUtils.EsIndex).build();
    DeleteIndexResponse response = elasticsearchClient.indices().delete(request);
    System.out.println(response);
}

2. 文档基础操作 (CRUD)
添加文档
@Test
void addDocument() throws IOException {
    User user = new User("zhangsan", 11);
    IndexRequest<User> request = IndexRequest.of(builder ->
            builder
                    .id("1")
                    .index(EsUtils.EsIndex)
                    .document(user)
    );
    IndexResponse index = elasticsearchClient.index(request);
    System.out.println(index);
}
获取文档
@Test
void getDocument() throws IOException {
    GetRequest request = GetRequest.of(builder -> builder.index(EsUtils.EsIndex).id("1"));
    GetResponse<User> userGetResponse = elasticsearchClient.get(request, User.class);
    System.out.println(userGetResponse);
    System.out.println(userGetResponse.source());
}
更新文档
@Test
void updateDocument() throws IOException {
    User user = new User("zhangsan", 14);
    UpdateRequest<User, Object> request = UpdateRequest.of(builder -> builder.index(EsUtils.EsIndex).id("1").doc(user));
    UpdateResponse<User> update = elasticsearchClient.update(request, User.class);
    System.out.println(update);
    System.out.println(update.result());
}
删除文档
@Test
void deleteDocument() throws IOException {
    DeleteRequest request = DeleteRequest.of(builder -> builder.index(EsUtils.EsIndex).id("1"));
    DeleteResponse delete = elasticsearchClient.delete(request);
    System.out.println(delete);
    System.out.println(delete.result());
}
批量添加文档 (Bulk)
@Test
void addBulkDocument() throws IOException {
    ArrayList<User> users = new ArrayList<>();
    users.add(new User("zhangsan", 13));
    users.add(new User("wangwu", 15));
    users.add(new User("lisi", 14));
    
    BulkRequest.Builder request = new BulkRequest.Builder();
    for (User user : users) {
        request.operations(operationBuilder -> 
            operationBuilder.index(index -> index.index(EsUtils.EsIndex).document(user))
        );
    }
    
    BulkResponse bulk = elasticsearchClient.bulk(request.build());
    System.out.println(bulk);
    System.out.println("耗时: " + bulk.took());
    System.out.println("项详情: " + bulk.items());
}

3. 搜索与查询 (Search Queries)
Match 查询 (分词模糊匹配)
@Test
void searchByMatch() throws IOException {
    SearchRequest request = SearchRequest.of(s -> s
            .index("zwz")
            .query(q -> q
                    .match(m -> m
                            .field("name")
                            .query("zhangsan")
                    )
            )
    );

    SearchResponse<User> response = elasticsearchClient.search(request, User.class);
    response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
Term 查询 (精确匹配)
@Test
void searchByTerm() throws IOException {
    SearchResponse<User> response = elasticsearchClient.search(s -> s
                    .index("zwz")
                    .query(q -> q
                            .term(t -> t
                                    .field("age")
                                    .value(14)
                            )
                    ),
            User.class
    );

    response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
Range 范围查询
@Test
void searchByRange() throws IOException {
    SearchResponse<User> response = elasticsearchClient.search(s -> s
                    .index("zwz")
                    .query(q -> q
                            .range(r -> r
                                    .field("age")
                                    .gte(JsonData.of(10))
                                    .lte(JsonData.of(20))
                            )
                    ),
            User.class
    );

    response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
Bool 组合查询
@Test
void searchByBool() throws IOException {
    SearchResponse<User> response = elasticsearchClient.search(s -> s
                    .index("zwz")
                    .query(q -> q
                            .bool(b -> b
                                    .must(m -> m.match(t -> t.field("name").query("zhangsan")))   // 必须匹配
                                    .filter(f -> f.range(r -> r.field("age").gte(JsonData.of(10)))) // 过滤不打分
                            )
                    ),
            User.class
    );

    response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
分页与排序
@Test
void searchWithPaging() throws IOException {
    SearchResponse<User> response = elasticsearchClient.search(s -> s
                    .index("zwz")
                    .from(0)   // offset
                    .size(10)  // limit
                    .sort(so -> so.field(f -> f.field("age").order(SortOrder.Desc)))
                    .query(q -> q.matchAll(m -> m)),
            User.class
    );

    System.out.println("总命中: " + response.hits().total().value());
    response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}
高亮显示
@Test
void searchWithHighlight() throws IOException {
    SearchResponse<User> response = elasticsearchClient.search(s -> s
                    .index("zwz")
                    .query(q -> q.match(m -> m.field("name").query("zhangsan")))
                    .highlight(h -> h
                            .fields("name", hf -> hf.preTags("<em>").postTags("</em>"))
                    ),
            User.class
    );

    response.hits().hits().forEach(hit -> {
        System.out.println("Source: " + hit.source());
        System.out.println("Highlight: " + hit.highlight());
    });
}
查询所有数据
@Test
void searchAll() throws IOException {
    SearchResponse<User> response = elasticsearchClient.search(s -> s
                    .index("zwz")
                    .query(q -> q.matchAll(m -> m)),
            User.class
    );

    System.out.println("总数: " + response.hits().total().value());
    response.hits().hits().forEach(hit -> System.out.println(hit.source()));
}

注意事项

不同的版本对应的Java操作可能不一样,我这边使用的elasticsearch8.14.0版本的

Git地址

https://gitee.com/jamescrx/elasticsearch.git

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐