Elasticsearch
Lucene,Solr,ElasticSearch 比較
Lucene, Solr, 和 Elasticsearch 是三個與搜尋相關的開源專案,它們之間存在緊密的聯絡,但又有一些區別。以下是它們的比較:
- Lucene:
- Lucene 是一個 Java 編寫的全文檢索引擎庫,提供了強大的文字搜尋和索引功能。
- 它是一個基礎庫,提供了建立搜尋應用所需的核心功能,包括索引構建、搜尋和分析等。
- Lucene 可以被其他專案作為底層搜尋引擎來使用,但它本身並不提供完整的搜尋應用解決方案。
- Solr:
- Solr 是一個構建在 Lucene 之上的搜尋平臺,使用 Java 編寫,提供了基於 HTTP 的 RESTful API。
- Solr 提供了一系列的功能,包括分散式搜尋、複雜查詢、實時索引、快取、擴充套件性等。
- 它提供了方便的配置和管理工具,使得構建搜尋應用更加容易。
- Solr 適用於需要快速構建搜尋應用的情況,尤其是對於企業級應用或者需要自定義搜尋邏輯的場景。
- 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