Elasticsearch

曹化金發表於2024-03-16

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

相關文章