Elasticsearch

Elasticsearch

Lucene,Solr,ElasticSearch 比较

Lucene, Solr, 和 Elasticsearch 是三个与搜索相关的开源项目,它们之间存在紧密的联系,但又有一些区别。以下是它们的比较:

  1. Lucene:
    • Lucene 是一个 Java 编写的全文检索引擎库,提供了强大的文本搜索和索引功能。
    • 它是一个基础库,提供了创建搜索应用所需的核心功能,包括索引构建、搜索和分析等。
    • Lucene 可以被其他项目作为底层搜索引擎来使用,但它本身并不提供完整的搜索应用解决方案。
  2. Solr:
    • Solr 是一个构建在 Lucene 之上的搜索平台,使用 Java 编写,提供了基于 HTTP 的 RESTful API。
    • Solr 提供了一系列的功能,包括分布式搜索、复杂查询、实时索引、缓存、扩展性等。
    • 它提供了方便的配置和管理工具,使得构建搜索应用更加容易。
    • Solr 适用于需要快速构建搜索应用的情况,尤其是对于企业级应用或者需要自定义搜索逻辑的场景。
  3. Elasticsearch:
    • Elasticsearch 也是基于 Lucene 构建的搜索引擎,但它不仅仅是一个搜索引擎,还是一个分布式文档存储和分析引擎。
    • Elasticsearch 提供了简单的 RESTful API,并且具有更广泛的用途,包括日志和事件分析、数据可视化、实时搜索等。
    • 它具有强大的分布式能力,支持水平扩展,可以处理大规模数据和高并发请求。
    • Elasticsearch 还提供了丰富的插件和生态系统,使得它更容易与其他工具和系统集成。

总的来说,Lucene 提供了搜索引擎的核心功能,Solr 是构建在 Lucene 之上的搜索平台,提供了更多的功能和方便的管理工具,而 Elasticsearch 则是一个更加广泛用途的分布式搜索和分析引擎,具有强大的分布式能力和丰富的生态系统。选择其中一个取决于你的需求和项目的规模。

本次演示以Elasticsearch为例

服务端

Docker Desktop 安装最新的Elasticsearch镜像并启动容器,注册端口:9200

http://localhost:9200/ 访问不了,学习演示的话,需要修改es配置文件elasticsearch.yml,关闭ssl安全访问权限控制,重启容器即可。

客户端

web界面安装

修改完成重启容器浏览器访问:http://localhost:9200/ 返回Elasticsearch 实例的详细信息如下:

{
  "name" : "00d7fda22075",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "WeZtDCeNS0-Spxw_Uc79VQ",
  "version" : {
    "number" : "8.12.2",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "48a287ab9497e852de30327444b0809e55d46466",
    "build_date" : "2024-02-19T10:04:32.774273190Z",
    "build_snapshot" : false,
    "lucene_version" : "9.9.2",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

如果需要可视化界面需要安装chrome浏览器插件(简单易用,没有界面样式):Multi Elasticsearch Head

点击插件即可访问界面如下图所示:

也可以安装Kibana组件(功能强大)。

spring-boot项目使用示例

以maven spring-boot-starter-data-elasticsearch插件使用为例,添加spring-boot-starter-data-elasticsearch和elasticsearch-rest-high-level-client包(Rest API)。

maven配置添加如下包

	<!--spring整合elasticsearch包-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
		</dependency>
		<dependency>
			<groupId>org.elasticsearch.client</groupId>
			<artifactId>elasticsearch-rest-high-level-client</artifactId>
			<version>7.10.0</version>
		</dependency>

项目启动,构建初始化数据,初始化示例代码如下所示:

	@PostConstruct
		private void init() {
			// 创建文档
			Book book1 = new Book("1", "Java Programming Smith", "John Doe");
			Book book2 = new Book("2", "Spring Boot in Action", "Jane Smith");
			Book book3 = new Book("3", "Spring Boot es", "cao Smith");
			Book book4 = new Book("4", "Spring Boot mongodb Smith", "lili Smith");

			bookRepository.save(book1);
			bookRepository.save(book2);
			bookRepository.save(book3);
			bookRepository.save(book4);

			// 创建 Brand 对象
			Brand brand = new Brand();
			brand.setId("1");
			brand.setName("华为");
			brand.setCountry("中国");

			// 创建 Product 对象并设置嵌套的 Brand 对象
			Product product = new Product();
			product.setId("1");
			product.setName("手机");
			product.setPrice(1001.0);
			product.setBrand(brand);

			// 保存 Product 对象到 Elasticsearch 中
			elasticsearchOperations.save(product);
		}

其中Brand为Product的内嵌对象,JPA构建es索引代码如下:

package guru.springframework.domain;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;

@Data
@Document(indexName = "products")
public class Product {
    @Id
    private String id;
    private String name;
    private double price;

    @Field(type = FieldType.Nested)
    private Brand brand;

}

部分查询代码示例:

package guru.springframework.services;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class BookService {
    @Autowired
    private RestHighLevelClient elasticsearchClient;
    @Autowired
    private BookRepository bookRepository;
    private final String index = "library";

    public Page<Book> searchBooksByKeyword(String keyword, int page, int size) {
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .should(QueryBuilders.matchQuery("title", keyword))
                .should(QueryBuilders.matchQuery("author", keyword));
        sourceBuilder.query(boolQuery);
        sourceBuilder.from(page * size);
        sourceBuilder.size(size);
        searchRequest.source(sourceBuilder);
        try {
            SearchResponse response = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
            List<Book> books = extractBooksFromSearchResponse(response);
            long totalHits = response.getHits().getTotalHits().value;
            return new PageImpl<>(books, PageRequest.of(page, size), totalHits);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<Book> extractBooksFromSearchResponse(SearchResponse response) {
        List<Book> books = new ArrayList<>();
        for (SearchHit hit : response.getHits().getHits()) {
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            String title = (String) sourceAsMap.get("title");
            String author = (String) sourceAsMap.get("author");
            // 构造Book对象
            Book book = new Book();
            book.setTitle(title);
            book.setAuthor(author);
            books.add(book);
        }
        return books;
    }

    public Page<Book> searchBooksByKeyword2(String keyword, int page, int size) {
       // return bookRepository.findByTitleContainingOrAuthorContaining(keyword,keyword,PageRequest.of(page, size));
        return bookRepository.findByTitleOrAuthorCustomQuery(keyword,PageRequest.of(page, size));
    }

}

package guru.springframework.services;;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;
    @Autowired
    private RestHighLevelClient elasticsearchClient;

    private final String index = "products";

    @Autowired
    private ObjectMapper objectMapper;

    public Page<Product> getAllProducts(int pageNumber, int pageSize) {
        Pageable pageable = PageRequest.of(pageNumber, pageSize);
        return productRepository.findAll(pageable);
    }
    public Page<Product> searchProductsByBrandName(String brandName, int pageNumber, int pageSize) throws IOException {
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.matchQuery("brand.name", brandName));
        sourceBuilder.from(pageNumber * pageSize);
        sourceBuilder.size(pageSize);
        searchRequest.source(sourceBuilder);

        SearchResponse response = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
        List<Product> products = extractProductsFromSearchResponse(response);

        return new PageImpl<>(products, PageRequest.of(pageNumber, pageSize), response.getHits().getTotalHits().value);
    }

    private List<Product> extractProductsFromSearchResponse(SearchResponse response) {
        return Arrays.stream(response.getHits().getHits())
                .map(hit -> {
                    try {
                        return objectMapper.readValue(hit.getSourceAsString(), Product.class);
                    } catch (IOException e) {
                        throw new RuntimeException("Failed to parse search response", e);
                    }
                })
                .collect(Collectors.toList());
    }

}

  • RestHighLevelClient 提供一些接口,可以通过抽象方式构建查询等一些操作。面向对象编程,但是写法反而更麻烦,代码更冗余。
  • JPA方式,有了ChatGPT后,其实JPA写法更快,就一行代码,之前这种手写查询语句的方法不好写或者不好维护,现在不用维护,直接扔给ChatGPT,生成查询语句。在BookRepository查询方法上添加@Query注解查询语句。BookRepository代码如下所示:
package guru.springframework.repositories;

import guru.springframework.domain.Book;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface BookRepository extends ElasticsearchRepository<Book, String> {
    Page<Book> findByTitleContainingOrAuthorContaining(String title, String author, Pageable pageable);
    @Query("{\"bool\": {\"should\": [{\"match\": {\"title\": \"?0\"}}, {\"match\": {\"author\": \"?0\"}}]}}")
    Page<Book> findByTitleOrAuthorCustomQuery(String keyword, Pageable pageable);
}

数据查询结果格式如下图所示:

项目源码及调试

Github下载地址:https://github.com/caohuajin/Elasticsearch

ApiPost在线接口:https://console-docs.apipost.cn/preview/d900735c0fcb6588/d644ecf613c746f4

posted @ 2024-03-16 20:04  曹化金  阅读(24)  评论(0编辑  收藏  举报