Elasticsearch-实战案例

一、没有使用Elasticsearch的查询速度698ms

**1.**数据库模糊查询不走索引,在数据量较大的时候,查询性能很差。需要注意的是,数据库模糊查询随着表数据量的增多,查询性能的下降会非常明显,而搜索引擎的性能则不会随着数据增多而下降太多。目前仅10万不到的数据量差距就如此明显,如果数据量达到百万、千万、甚至上亿级别,这个性能差距会非常夸张。

**2.**功能单一

数据库的模糊搜索功能单一,匹配条件非常苛刻,必须恰好包含用户搜索的关键字。而在搜索引擎中,用户输入出现个别错字,或者用拼音搜索、同义词搜索都能正确匹配到数据。

综上,在面临海量数据的搜索,或者有一些复杂搜索需求的时候,推荐使用专门的搜索引擎来实现搜索功能。

二、 Elasticsearch使用

1.依赖

版本:因为SpringBoot默认的ES版本是7.17.10,所以我们需要覆盖默认的ES版本:

复制代码
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

  <properties>
      <maven.compiler.source>11</maven.compiler.source>
      <maven.compiler.target>11</maven.compiler.target>
      <elasticsearch.version>7.12.1</elasticsearch.version>
  </properties>

启动es服务---测试是否连接es成功:

java 复制代码
package com.itfly;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

public class IndexTest {

    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://localhost:9200")
        ));
    }

    @Test
    void testConnect() {
        System.out.println(client);
    }

    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

成功连接

2、创建索引库

实现搜索功能需要的字段包括三大部分:

  • 搜索过滤字段

    • 分类

    • 品牌

    • 价格

  • 排序字段

    • 默认:按照更新时间降序排序

    • 销量

    • 价格

  • 展示字段

    • 商品id:用于点击后跳转

    • 图片地址

    • 是否是广告推广商品

    • 名称

    • 价格

    • 评价数量

    • 销量

代码分为三步:

  • 1)创建Request对象。

    • 因为是创建索引库的操作,因此Request是CreateIndexRequest
  • 2)添加请求参数

    • 其实就是Json格式的Mapping映射参数。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
  • 3)发送请求

    • client.``indices``()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。例如创建索引、删除索引、判断索引是否存在等
java 复制代码
    @Test
    void testCreateIndex() throws IOException {
        // 1.创建Request对象
        CreateIndexRequest request = new CreateIndexRequest("goods");
        // 2.准备请求参数
        request.source(MAPPING_TEMPLATE, XContentType.JSON);
        // 3.发送请求
        client.indices().create(request, RequestOptions.DEFAULT);
    }

    static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\": { \"type\": \"long\" },\n" +
            "      \"name\": { \"type\": \"text\", \"analyzer\": \"ik_smart\" },\n" +
            "      \"description\": { \"type\": \"text\", \"analyzer\": \"ik_smart\" },\n" +
            "      \"price\": { \"type\": \"scaled_float\", \"scaling_factor\": 100 },\n" +
            "      \"stock\": { \"type\": \"integer\" },\n" +
            "      \"status\": { \"type\": \"integer\" },\n" +
            "      \"image\": { \"type\": \"keyword\", \"index\": false },\n" +
            "      \"created_at\": { \"type\": \"date\", \"format\": \"yyyy-MM-dd HH:mm:ss\" },\n" +
            "      \"updated_at\": { \"type\": \"date\", \"format\": \"yyyy-MM-dd HH:mm:ss\" }\n" +
            "    }\n" +
            "  }\n" +
            "}";

判断索引库是否存在

java 复制代码
@Test
void testExistsIndex() throws IOException {
    // 1.创建Request对象
    GetIndexRequest request = new GetIndexRequest("goods");
    // 2.发送请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    // 3.输出
    System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}

RestClient操作文档

索引库准备好以后,就可以操作文档了。为了与索引库操作分离,我们再次创建一个测试类,做两件事情:

java 复制代码
package com.itfly;


import com.itfly.service.IProductsService;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

@SpringBootTest(properties = "spring.profiles.active=local")
public class DocumentTest {

    private RestHighLevelClient client;
    @Autowired
    private IProductsService productsService;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://localhost:9200")
        ));
    }
    
    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }
}

新增文档

我们需要将数据库中的商品信息导入elasticsearch中,而不是造假数据了。

语法:

java 复制代码
POST /{索引库名}/_doc/1
{
    "name": "Jack",
    "age": 21
}

可以看到与索引库操作的API非常类似,同样是三步走:

  • 1)创建Request对象,这里是IndexRequest,因为添加文档就是创建倒排索引的过程

  • 2)准备请求参数,本例中就是Json文档

  • 3)发送请求

变化的地方在于,这里直接使用client.xxx()的API,不再需要client.indices()了。

java 复制代码
    @Test
    void testAddDocument() throws IOException {
        // 1.根据id查询商品数据,批量新增文档
        productsService.list().forEach(item -> {
            IndexRequest indexRequest = new IndexRequest("goods");
            indexRequest.id(item.getId().toString());
            indexRequest.source(JSONUtil.parseObj(item), XContentType.JSON);
            try {
                client.index(indexRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("添加成功");
        });
        //
    }

把所有的数据加入索引库,并创建文档

RestClient查询



  • 第一步,创建SearchRequest对象,指定索引库名

  • 第二步,利用request.source()构建DSL,DSL中可以包含查询、分页、排序、高亮等

    • query():代表查询条件,利用QueryBuilders.matchAllQuery()构建一个match_all查询的DSL
  • 第三步,利用client.search()发送请求,得到响应

java 复制代码
@Test
void testMatchAll() throws IOException {
    // 1.创建Request
    SearchRequest request = new SearchRequest("goods");
    // 2.组织请求参数
    request.source().query(QueryBuilders.matchAllQuery());
    // 3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.解析响应
    handleResponse(response);
}

private void handleResponse(SearchResponse response) {
    SearchHits searchHits = response.getHits();
    // 1.获取总条数
    long total = searchHits.getTotalHits().value;
    System.out.println("共搜索到" + total + "条数据");
    // 2.遍历结果数组
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        // 3.得到_source,也就是原始json文档
        String source = hit.getSourceAsString();
        // 4.反序列化并打印
        ItemDoc item = JSONUtil.toBean(source, ItemDoc.class);
        System.out.println(item);
    }
}

查询

java 复制代码
package com.itfly.controller;

import com.itfly.DTO.ProductsSearchDTO;
import com.itfly.resp.ResultData;
import com.itfly.service.IProductsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.Map;

/**
 * @author yyf
 * description
 * @date 2025/3/27 18:33
 */
@RestController
@RequestMapping("/es")
@Api(tags = "商品")
public class SearchController {
    @Autowired
    private IProductsService productsService;
    @PostMapping("/list")
    @ApiOperation(value = "查询所有商品")
    public ResultData list(@RequestBody ProductsSearchDTO productsSearchDTO) {
        // 创建客户端
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(new HttpHost("localhost", 9200))
        );
        try {
            // 构建查询请求
            SearchRequest searchRequest = new SearchRequest("goods");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(QueryBuilders.matchQuery("name",productsSearchDTO.getName() ));
            sourceBuilder.size(10);
            searchRequest.source(sourceBuilder);

            // 执行查询
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);

            // 处理结果
            for (SearchHit hit : response.getHits().getHits()) {
                // 获取文档内容,返回ResultData
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                return ResultData.success(sourceAsMap);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                client.close(); // 关闭客户端
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

}

总结:

数据库模糊查询不走索引,在数据量较大的时候,查询性能很差。需要注意的是,数据库模糊查询随着表数据量的增多,查询性能的下降会非常明显,而搜索引擎的性能则不会随着数据增多而下降太多。目前仅10万不到的数据量差距就如此明显,如果数据量达到百万、千万、甚至上亿级别,这个性能差距会非常夸张。

其次,功能单一

数据库的模糊搜索功能单一,匹配条件非常苛刻,必须恰好包含用户搜索的关键字。而在搜索引擎中,用户输入出现个别错字,或者用拼音搜索、同义词搜索都能正确匹配到数据。

综上,在面临海量数据的搜索,或者有一些复杂搜索需求的时候,推荐使用专门的搜索引擎来实现搜索功能。

相关推荐
懒羊羊不懒@6 分钟前
Java基础语法—最小单位、及注释
java·c语言·开发语言·数据结构·学习·算法
Apache Flink7 分钟前
Flink Agents 0.1.0 发布公告
大数据·flink
ss27310 分钟前
手写Spring第4弹: Spring框架进化论:15年技术变迁:从XML配置到响应式编程的演进之路
xml·java·开发语言·后端·spring
DokiDoki之父21 分钟前
MyBatis—增删查改操作
java·spring boot·mybatis
兩尛38 分钟前
Spring面试
java·spring·面试
Java中文社群1 小时前
服务器被攻击!原因竟然是他?真没想到...
java·后端
Full Stack Developme1 小时前
java.nio 包详解
java·python·nio
零千叶1 小时前
【面试】Java JVM 调优面试手册
java·开发语言·jvm
代码充电宝1 小时前
LeetCode 算法题【简单】290. 单词规律
java·算法·leetcode·职场和发展·哈希表
li3714908901 小时前
nginx报400bad request 请求头过大异常处理
java·运维·nginx