在數據量爆炸的今天,傳統數據庫的查詢能力越來越難以滿足復雜的檢索需求。比如電商平臺的商品搜索,需要支持關鍵詞模糊匹配、多條件篩選、熱門度排序等功能,這時候 Elasticsearch(簡稱 ES)就成了最佳選擇。作為一款分布式全文搜索引擎,ES 以其強大的檢索能力、靈活的擴展性被廣泛應用。本文將從 ES 核心概念講起,結合 Java 實戰案例,帶你快速掌握 ES 的應用技巧。
一、Elasticsearch 核心概念與優勢
在使用 ES 前,我們需要先理清它的核心概念 —— 很多人會把 ES 和數據庫類比,這個思路其實很實用。
1.1 ES 與關系型數據庫的概念對應
如果把 ES 比作數據庫,兩者的核心概念可以這樣對應:
關系型數據庫 | Elasticsearch | 說明 |
Database(數據庫) | Index(索引) | 一個索引對應一類數據集合,比如 “商品索引”“用戶索引” |
Table(表) | Type(類型) | 早期 ES 用于區分索引內的不同數據類型,7.x 后已廢棄,一個索引只對應一種類型 |
Row(行) | Document(文檔) | 索引中的一條數據,以 JSON 格式存儲 |
Column(列) | Field(字段) | 文檔中的一個屬性,比如商品的 “名稱”“價格” |
Schema(表結構) | Mapping(映射) | 定義文檔中字段的類型、分詞器等規則 |
1.2 ES 的核心優勢
相比傳統數據庫和其他搜索引擎,ES 的核心競爭力體現在這幾點:
- 全文檢索能力:支持關鍵詞分詞、模糊匹配、同義詞擴展等,比如搜索 “手機” 時能匹配 “智能手機”“移動電話”。
- 分布式架構:天然支持分片和副本,數據自動分片存儲,副本保證高可用,可輕松擴展至海量數據。
- 近實時搜索:數據寫入后秒級可查,兼顧實時性和性能。
- 靈活的聚合分析:支持統計、排序、分組等復雜分析,比如按 “商品分類” 統計銷量 Top10。
二、Elasticsearch 核心操作入門
要使用 ES,首先要掌握其基礎操作。ES 提供 RESTful API 接口,可通過 HTTP 請求直接操作,也可通過客戶端工具(如 Kibana 的 Dev Tools)執行。
2.1 索引相關操作
(1)創建索引及映射
創建一個 “商品索引”,并定義字段映射(類似數據庫建表時定義字段類型):
# 創建商品索引
PUT /product_index
{
"mappings": {
"properties": {
"id": { "type": "keyword" }, // 商品ID,精確匹配,不分詞
"name": {
"type": "text",
"analyzer": "ik_max_word", // 使用IK分詞器(中文分詞必備)
"fields": {
"keyword": { "type": "keyword" } // 用于精確查詢或排序
}
},
"price": { "type": "double" }, // 價格
"category": { "type": "keyword" }, // 分類,精確匹配
"createTime": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" } // 創建時間
}
}
}
這里有兩個關鍵注意點:
- text 與 keyword:text類型用于全文檢索(會分詞),keyword用于精確匹配(不分詞),同一個字段可通過fields同時支持兩種類型。
- 中文分詞:必須使用 IK 分詞器(需提前安裝),ik_max_word會將文本拆分為最細粒度(如 “華為手機” 拆為 “華為”“手機”),ik_smart為粗粒度拆分。
(2)查看索引映射
GET /product_index/_mapping
(3)刪除索引
DELETE /product_index
2.2 文檔相關操作
文檔是 ES 中最小的數據單元,所有操作圍繞文檔展開。
(1)新增文檔
# 新增文檔(指定ID)
PUT /product_index/_doc/1001
{
"id": "1001",
"name": "華為Mate 60 Pro 智能手機",
"price": 6999.00,
"category": "手機",
"createTime": "2024-01-15 09:30:00"
}
# 新增文檔(自動生成ID)
POST /product_index/_doc
{
"id": "1002",
"name": "蘋果iPhone 15 手機",
"price": 7999.00,
"category": "手機",
"createTime": "2024-01-20 14:20:00"
}
(2)查詢文檔
ES 的查詢能力非常強大,這里列舉幾種常用場景:
- 精確查詢(根據分類查詢手機商品):
GET /product_index/_search
{
"query": {
"term": {
"category": { "value": "手機" }
}
}
}
- 全文檢索(搜索包含 “華為” 的商品):
GET /product_index/_search
{
"query": {
"match": {
"name": "華為"
}
}
}
- 組合條件查詢(價格在 5000-8000,且分類為手機):
GET /product_index/_search
{
"query": {
"bool": {
"must": [
{ "term": { "category": "手機" } },
{ "range": { "price": { "gte": 5000, "lte": 8000 } } }
]
}
},
"sort": [{"createTime": "desc"}], // 按創建時間倒序
"from": 0, "size": 10 // 分頁(從第0條開始,取10條)
}
(3)更新與刪除文檔
- 更新文檔(全量更新或局部更新):
# 局部更新(只更新價格)
POST /product_index/_update/1001
{
"doc": {
"price": 6799.00
}
}
- 刪除文檔:
DELETE /product_index/_doc/1001
三、Java 操作 Elasticsearch 實戰
實際開發中,我們很少直接調用 REST API,而是通過官方客戶端工具操作。ES 官方推薦的 Java 客戶端是 Elasticsearch Java Client(7.x 后替代了舊的 Transport Client)。
3.1 環境準備
(1)引入依賴
在 Maven 項目的pom.xml中添加依賴(版本需與 ES 服務器一致,這里以 8.10.4 為例):
<dependencies>
<!-- Elasticsearch Java Client -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.10.4</version>
</dependency>
<!-- 依賴Jackson處理JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- 連接池 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>
</dependencies>
(2)初始化客戶端
創建 ES 客戶端連接(類似數據庫連接池):
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.elasticsearch.client.RestClient;
public class EsClient {
// 單例客戶端
private static ElasticsearchClient client;
static {
// 創建REST客戶端(連接ES服務器,可配置多個節點)
RestClient restClient = RestClient.builder(
new HttpHost("localhost", 9200, "http")
).build();
// 創建傳輸層
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);
// 創建客戶端
client = new ElasticsearchClient(transport);
}
public static ElasticsearchClient getClient() {
return client;
}
}
注意:ES 客戶端版本必須與服務器版本保持一致,否則可能出現兼容性問題。
3.2 核心操作實戰:商品搜索功能
以 “電商商品搜索” 為例,實現文檔新增、條件查詢、分頁排序等核心功能。
(1)定義商品實體類
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class Product {
private String id;
private String name;
private Double price;
private String category;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}
(2)新增商品文檔
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import java.io.IOException;
public class ProductService {
private final ElasticsearchClient client = EsClient.getClient();
// 新增商品到ES
public void addProduct(Product product) throws IOException {
// 構建索引請求(指定索引名、文檔ID和數據)
IndexRequest<Product> request = IndexRequest.of(b -> b
.index("product_index") // 索引名
.id(product.getId()) // 文檔ID(可選,不指定則自動生成)
.document(product) // 文檔數據
);
// 執行請求
IndexResponse response = client.index(request);
System.out.println("新增結果:" + response.result());
}
}
(3)多條件查詢商品
實現一個帶條件、分頁、排序的商品搜索功能:
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.TotalHits;
import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ProductService {
// ... 其他代碼省略
/**
* 搜索商品
* @param keyword 關鍵詞(搜索商品名稱)
* @param category 分類(可選)
* @param minPrice 最低價格(可選)
* @param maxPrice 最高價格(可選)
* @param page 頁碼(從1開始)
* @param size 每頁條數
* @return 商品列表及總條數
*/
public SearchResult<Product> searchProducts(String keyword, String category,
Double minPrice, Double maxPrice,
int page, int size) throws IOException {
// 構建查詢條件
SearchRequest request = SearchRequest.of(b -> b
.index("product_index") // 指定索引
.query(q -> q
.bool(bool -> {
// 拼接查詢條件
if (keyword != null && !keyword.isEmpty()) {
// 全文檢索商品名稱
bool.must(m -> m.match(t -> t.field("name").query(keyword)));
}
if (category != null && !category.isEmpty()) {
// 精確匹配分類
bool.filter(f -> f.term(t -> t.field("category").value(category)));
}
if (minPrice != null || maxPrice != null) {
// 價格范圍過濾
bool.filter(f -> f.range(r -> {
if (minPrice != null) r.gte(minPrice);
if (maxPrice != null) r.lte(maxPrice);
return r.field("price");
}));
}
return bool;
})
)
.sort(s -> s.field(f -> f.field("createTime").order("desc"))) // 按創建時間倒序
.from((page - 1) * size) // 起始位置(分頁)
.size(size) // 每頁條數
);
// 執行查詢
SearchResponse<Product> response = client.search(request, Product.class);
// 處理結果
List<Product> products = new ArrayList<>();
for (Hit<Product> hit : response.hits().hits()) {
products.add(hit.source()); // 獲取文檔數據
}
// 總條數
TotalHits totalHits = response.hits().total();
long total = 0;
if (totalHits != null && totalHits.relation() == TotalHitsRelation.Eq) {
total = totalHits.value();
}
return new SearchResult<>(total, products);
}
// 定義結果封裝類
@Data
public static class SearchResult<T> {
private long total; // 總條數
private List<T> data; // 數據列表
public SearchResult(long total, List<T> data) {
this.total = total;
this.data = data;
}
}
}
這個方法包含了 ES 查詢的核心場景:
- 多條件組合:用bool查詢拼接 “必須滿足(must)” 和 “過濾(filter)” 條件;
- 全文檢索:match查詢用于商品名稱的關鍵詞搜索;
- 精確匹配與范圍過濾:term查詢匹配分類,range查詢過濾價格;
- 分頁與排序:通過from、size實現分頁,sort指定排序規則。
3.3 性能優化技巧
在實際應用中,ES 的性能優化不可忽視,尤其是數據量大或查詢頻繁的場景:
- 合理設計映射:
- 不需要檢索的字段設置index: false(如商品詳情的大文本);
- 精確匹配字段用keyword類型,避免text類型的不必要分詞。
- 優化查詢語句:
- 用filter代替must(filter不計算評分,且可緩存結果);
- 避免wildcard前綴匹配(如name:*手機),會導致全表掃描;
- 分頁查詢時,深分頁(如from:10000)改用search_after。
- 索引分片優化:
- 初始分片數設置為 “節點數 ×1~3”(如 3 個節點可設 5 個分片);
- 分片大小控制在 20GB~50GB 之間,過大影響查詢性能。
- 批量操作:
// 批量新增示例
BulkRequest.Builder bulk = new BulkRequest.Builder();
for (Product product : productList) {
bulk.operations(op -> op
.index(idx -> idx
.index("product_index")
.id(product.getId())
.document(product)
)
);
}
client.bulk(bulk.build());
- 批量新增 / 更新用BulkRequest,減少網絡請求次數;
四、常見問題與解決方案
在 ES 應用中,新手容易遇到一些 “坑”,這里總結幾個高頻問題:
- 中文分詞效果差:
- 原因:未安裝 IK 分詞器,默認分詞器會把中文拆成單字;
- 解決:在 ES 插件目錄安裝 IK 分詞器(版本與 ES 一致),并在映射中指定analyzer: "ik_max_word"。
- 查詢結果與預期不符:
- 檢查字段類型:如果用term查詢text類型字段,可能因分詞導致匹配失敗(需用match查詢);
- 檢查分詞結果:通過GET /product_index/_analyze測試字段分詞效果。
- 寫入 / 查詢性能慢:
- 寫入慢:檢查分片副本數(初期可設 1 個副本)、是否開啟批量寫入;
- 查詢慢:查看是否命中索引(通過explain分析查詢計劃)、是否需要增加節點或優化分片。
五、總結
Elasticsearch 是一款強大的搜索引擎,其核心價值在于全文檢索和分布式擴展能力。本文從基礎概念出發,通過 “理論 + 實戰” 的方式講解了 ES 的核心操作,并結合 Java 案例實現了商品搜索功能。
要熟練掌握 ES,關鍵在于:
- 理解索引、文檔、映射等核心概念,明確text與keyword的區別;
- 掌握bool組合查詢、范圍查詢、聚合分析等核心語法;
- 結合實際場景優化映射設計和查詢語句,避免性能問題。
如果你在 ES 應用中遇到過特殊場景(如大數據量同步、復雜聚合分析),歡迎在評論區分享你的解決方案!