本文將手把手教你如何搭建 ElasticSearch 環境,并通過 Node.js 實現高效數據檢索。包含 10+ 個可直接復用的代碼片段,助你快速掌握搜索、聚合等核心功能!
環境搭建篇
1. ElasticSearch 安裝要點
下載
es下載連接
下載下來后,進入 bin
目錄,終端運行第一個文件,即可啟動es。
修改密碼
進入 bin
目錄下,終端輸入:
.\elasticsearch-reset-password -u elastic -i
輸入兩次密碼即可修改超級用戶 elastic
的密碼。
然后訪問 http://localhost:9200
。輸入密碼和賬號后,若返回以下信息則代表修改密碼成功:
{"name" : "Win10-2024UVSXG","cluster_name" : "elasticsearch","cluster_uuid" : "oan-H91LSSiReCuNSDWKIA","version" : {"number" : "8.15.0","build_flavor" : "default","build_type" : "zip","build_hash" : "1a77947f34deddb41af25e6f0ddb8e830159c179","build_date" : "2024-08-05T10:05:34.233336849Z","build_snapshot" : false,"lucene_version" : "9.11.1","minimum_wire_compatibility_version" : "7.17.0","minimum_index_compatibility_version" : "7.0.0"},"tagline" : "You Know, for Search"
}
2. Kibana 聯動配置
下載
Kibana 下載鏈接
注意: 下載的 kibana
的版本要與 es
一致,否則可能會報錯,無法訪問 Kibana
。
修改Kibana配置文件
需要進入到Kibana目錄中,修改 /config/kibana.yml
文件。設置訪問端口、ip、es賬號密碼。
注意: es的賬號密碼不能使用 elastic
超級用戶,但是默認有一個 kibana_system
用戶,只需在es中修改 kibana_system
用戶密碼即可。
啟動
進入項目的 bin
目錄中,打開終端運行第一個文件即可。
最后訪問 http://localhost:5601
即可。
Node.js 核心操作篇
ElasticSearch 和 Kibana 的安裝與使用指南
引言
ElasticSearch 是一個強大的開源搜索和分析引擎,而 Kibana 則是 ElasticSearch 的可視化工具。本文將詳細介紹如何下載、安裝和配置 ElasticSearch 和 Kibana,以及如何在 Node.js 中使用 ElasticSearch 進行數據操作。
ElasticSearch 部分
下載 ElasticSearch
- 訪問 ElasticSearch 官方下載頁面。
- 選擇適合您操作系統的版本進行下載。
啟動 ElasticSearch
- 下載完成后,解壓文件并進入
bin
目錄。 - 在終端中運行第一個文件(Windows 用戶運行
.bat
文件,Linux/macOS 用戶運行.sh
文件)。 - 啟動成功后,ElasticSearch 默認運行在
http://localhost:9200
。
修改 ElasticSearch 密碼
- 進入
bin
目錄,在終端輸入以下命令:.\elasticsearch-reset-password -u elastic -i
- 按照提示輸入兩次新密碼。
- 訪問
http://localhost:9200
,使用賬號elastic
和新密碼登錄。如果返回類似以下信息,則表示密碼修改成功:{"name": "Win10-2024UVSXG","cluster_name": "elasticsearch","cluster_uuid": "oan-H91LSSiReCuNSDWKIA","version": {"number": "8.15.0","build_flavor": "default","build_type": "zip","build_hash": "1a77947f34deddb41af25e6f0ddb8e830159c179","build_date": "2024-08-05T10:05:34.233336849Z","build_snapshot": false,"lucene_version": "9.11.1","minimum_wire_compatibility_version": "7.17.0","minimum_index_compatibility_version": "7.0.0"},"tagline": "You Know, for Search" }
Kibana 部分
下載 Kibana
- 訪問 Kibana 官方下載頁面。
- 注意: 下載的 Kibana 版本必須與 ElasticSearch 版本一致,否則可能會出現兼容性問題。
修改 Kibana 配置文件
- 進入 Kibana 目錄,找到
/config/kibana.yml
文件。 - 修改以下配置項:
server.port
: Kibana 的訪問端口(默認為 5601)。server.host
: Kibana 的訪問 IP(默認為localhost
)。elasticsearch.username
和elasticsearch.password
: 使用kibana_system
用戶的賬號密碼(需先在 ElasticSearch 中修改該用戶的密碼)。
- 保存配置文件。
啟動 Kibana
- 進入 Kibana 的
bin
目錄。 - 在終端中運行第一個文件(Windows 用戶運行
.bat
文件,Linux/macOS 用戶運行.sh
文件)。 - 啟動成功后,訪問
http://localhost:5601
即可進入 Kibana 界面。
Node.js 中使用 ElasticSearch
安裝依賴
在 Node.js 項目中安裝 ElasticSearch 客戶端庫:
npm install @elastic/elasticsearch
基本使用
以下是一些常見的 ElasticSearch 操作示例:
1. 初始化客戶端 (支持多種認證方式)
const { Client } = require('@elastic/elasticsearch');
// 基礎認證
const client = new Client({node: 'http://localhost:9200',auth: { username: 'elastic', password: 'yourpassword' }
});
// API Key 認證
const apiKeyClient = new Client({node: 'http://localhost:9200',auth: { apiKey: 'base64EncodedKey' }
});
// 云服務連接
const cloudClient = new Client({cloud: { id: 'my-cloud-id' },auth: { username: 'elastic', password: 'cloudpassword' }
});
2. 創建索引并添加數據
const user = await client.index({index: 'user-data',document: {user: 1,age: 18,name: 'jack',}
});
3. 查詢數據
const response = await client.get({index: 'user-data',id: user._id // 可以指定 ID 或使用自動生成的 ID
});
4. 搜索數據
const result = await client.search({index: 'user-data',query: {match: {name: 'jack' // 模糊查詢}},size: 1 // 返回結果數量
});
console.log(result.hits.hits); // 打印搜索結果
5. 刪除數據
await client.delete({index: 'user-data',id: user._id
});
6. 搜索所有數據
const response = await client.search({index: 'users',query: {match_all: {}, // 空對象表示匹配所有},size: 100 // 返回 100 條數據
});
7. 索引管理
創建索引(帶映射)
async function createIndexWithMapping() {try {const response = await client.indices.create({index: 'products',body: {mappings: {properties: {name: { type: 'text' },price: { type: 'float' },description: { type: 'text' },tags: { type: 'keyword' },created_at: { type: 'date' }}}}});console.log('索引創建成功:', response);} catch (error) {console.error('索引創建失敗:', error.meta.body.error);}
}
檢查索引是否存在
async function checkIndexExists(indexName) {try {const exists = await client.indices.exists({ index: indexName });console.log(`索引 ${indexName} 存在:`, exists);return exists;} catch (error) {console.error('檢查索引失敗:', error);return false;}
}
刪除索引
async function deleteIndex(indexName) {try {const response = await client.indices.delete({ index: indexName });console.log('索引刪除成功:', response);return response;} catch (error) {console.error('索引刪除失敗:', error.meta.body.error);throw error;}
}
8. 文檔操作
批量插入文檔
async function bulkInsert() {const dataset = [{ id: 1, name: 'iPhone 13', price: 799, category: 'phone' },{ id: 2, name: 'MacBook Pro', price: 1299, category: 'laptop' },{ id: 3, name: 'AirPods Pro', price: 249, category: 'accessory' }];const body = dataset.flatMap(doc => [{ index: { _index: 'products', _id: doc.id } },doc]);try {const { body: bulkResponse } = await client.bulk({ body });if (bulkResponse.errors) {console.log('批量插入部分失敗:', bulkResponse.items);} else {console.log('批量插入成功');}} catch (error) {console.error('批量插入失敗:', error);}
}
更新文檔
async function updateDocument(index, id, updates) {try {const response = await client.update({index,id,body: {doc: updates}});console.log('文檔更新成功:', response);return response;} catch (error) {console.error('文檔更新失敗:', error.meta.body.error);throw error;}
}// 使用示例
// updateDocument('products', 1, { price: 849 });
部分更新與腳本更新
async function updateWithScript() {try {const response = await client.update({index: 'products',id: 1,body: {script: {source: 'ctx._source.price += params.price_diff',lang: 'painless',params: {price_diff: 50}}}});console.log('腳本更新成功:', response);} catch (error) {console.error('腳本更新失敗:', error);}
}
9. 高級搜索查詢
多條件復合查詢
async function complexSearch() {try {const response = await client.search({index: 'products',body: {query: {bool: {must: [{ match: { category: 'phone' } }],filter: [{ range: { price: { gte: 500, lte: 1000 } } }],should: [{ match: { name: 'pro' } }],minimum_should_match: 1}},sort: [{ price: { order: 'desc' } }],highlight: {fields: {name: {},description: {}}}}});console.log('搜索結果:', response.hits.hits);return response.hits.hits;} catch (error) {console.error('搜索失敗:', error);throw error;}
}
聚合查詢
async function aggregateSearch() {try {const response = await client.search({index: 'products',body: {size: 0,aggs: {categories: {terms: { field: 'category.keyword', size: 10 },aggs: {avg_price: { avg: { field: 'price' } },max_price: { max: { field: 'price' } }}},price_stats: {stats: { field: 'price' }}}}});console.log('分類聚合結果:', response.aggregations.categories.buckets);console.log('價格統計:', response.aggregations.price_stats);return response.aggregations;} catch (error) {console.error('聚合查詢失敗:', error);throw error;}
}
全文搜索與高亮
async function fullTextSearch() {try {const response = await client.search({index: 'products',body: {query: {multi_match: {query: 'pro',fields: ['name^3', 'description'], // name字段權重更高type: 'best_fields'}},highlight: {pre_tags: ['<em>'],post_tags: ['</em>'],fields: {name: {},description: {}}}}});console.log('高亮搜索結果:');response.hits.hits.forEach(hit => {console.log(`ID: ${hit._id}, 分數: ${hit._score}`);console.log('高亮:', hit.highlight);});} catch (error) {console.error('全文搜索失敗:', error);}
}
10. 實戰案例:電商商品搜索
class ProductSearch {constructor() {this.client = new Client({ node: 'http://localhost:9200' });this.indexName = 'ecommerce_products';}async initIndex() {try {const exists = await this.client.indices.exists({ index: this.indexName });if (!exists) {await this.client.indices.create({index: this.indexName,body: {mappings: {properties: {name: { type: 'text', analyzer: 'ik_max_word' },description: { type: 'text', analyzer: 'ik_max_word' },price: { type: 'float' },stock: { type: 'integer' },categories: { type: 'keyword' },attributes: {type: 'nested',properties: {name: { type: 'keyword' },value: { type: 'keyword' }}},created_at: { type: 'date' }}}}});console.log('索引初始化完成');}} catch (error) {console.error('索引初始化失敗:', error);}}async indexProduct(product) {try {const response = await this.client.index({index: this.indexName,body: product});await this.client.indices.refresh({ index: this.indexName });return response;} catch (error) {console.error('商品索引失敗:', error);throw error;}}async searchProducts(query, filters = {}, page = 1, pageSize = 10) {try {const from = (page - 1) * pageSize;const body = {query: {bool: {must: [],filter: []}},from,size: pageSize,sort: [{ _score: 'desc' }, { created_at: 'desc' }]};// 添加全文搜索條件if (query) {body.query.bool.must.push({multi_match: {query,fields: ['name^3', 'description^2', 'categories'],type: 'best_fields'}});}// 添加過濾條件if (filters.categories) {body.query.bool.filter.push({terms: { categories: Array.isArray(filters.categories) ? filters.categories : [filters.categories] }});}if (filters.priceRange) {body.query.bool.filter.push({range: { price: filters.priceRange }});}// 添加嵌套屬性過濾if (filters.attributes) {filters.attributes.forEach(attr => {body.query.bool.filter.push({nested: {path: 'attributes',query: {bool: {must: [{ term: { 'attributes.name': attr.name } },{ term: { 'attributes.value': attr.value } }]}}}});});}const response = await this.client.search({index: this.indexName,body});return {total: response.hits.total.value,products: response.hits.hits.map(hit => ({...hit._source,id: hit._id,score: hit._score}))};} catch (error) {console.error('商品搜索失敗:', error);throw error;}}async getSuggestions(query) {try {const response = await this.client.search({index: this.indexName,body: {suggest: {name_suggest: {prefix: query,completion: {field: 'name_suggest',fuzzy: {fuzziness: 2}}},category_suggest: {text: query,term: {field: 'categories'}}}}});return {nameSuggestions: response.suggest.name_suggest[0].options.map(opt => opt.text),categorySuggestions: response.suggest.category_suggest[0].options.map(opt => opt.text)};} catch (error) {console.error('獲取建議失敗:', error);return { nameSuggestions: [], categorySuggestions: [] };}}
}// 使用示例
/*
const productSearch = new ProductSearch();
await productSearch.initIndex();// 添加商品
await productSearch.indexProduct({name: 'Apple iPhone 13 Pro',description: '最新款iPhone專業版',price: 999,stock: 100,categories: ['phone', 'apple'],attributes: [{ name: 'color', value: 'graphite' },{ name: 'storage', value: '256GB' }],created_at: new Date()
});// 搜索商品
const results = await productSearch.searchProducts('iphone',{ categories: 'phone',priceRange: { gte: 500, lte: 1200 },attributes: [{ name: 'color', value: 'graphite' }]},1,10
);// 獲取搜索建議
const suggestions = await productSearch.getSuggestions('ipho');
*/
11. 錯誤處理與性能優化
重試機制
const { Client } = require('@elastic/elasticsearch');const client = new Client({node: 'http://localhost:9200',maxRetries: 5, // 最大重試次數requestTimeout: 60000, // 請求超時時間sniffOnStart: true, // 啟動時嗅探節點sniffInterval: 60000, // 定期嗅探節點sniffOnConnectionFault: true // 連接故障時嗅探
});// 自定義重試策略
client.on('request', (err, result) => {if (err) {console.error('請求失敗:', err.meta ? err.meta.body.error : err.message);}
});// 使用Promise.catch處理錯誤
async function safeSearch() {try {const response = await client.search({index: 'products',body: { query: { match_all: {} } }}).catch(err => {console.error('搜索失敗:', err.meta.body.error);throw err;});return response;} catch (error) {console.error('捕獲到錯誤:', error);throw error;}
}
批量操作優化
async function optimizedBulkInsert(documents, batchSize = 1000) {try {for (let i = 0; i < documents.length; i += batchSize) {const batch = documents.slice(i, i + batchSize);const body = batch.flatMap(doc => [{ index: { _index: 'products' } },doc]);const { body: bulkResponse } = await client.bulk({ body });if (bulkResponse.errors) {console.log(`批量插入批次 ${i / batchSize + 1} 部分失敗`);} else {console.log(`批量插入批次 ${i / batchSize + 1} 成功`);}// 每批處理完成后稍作休息if (i + batchSize < documents.length) {await new Promise(resolve => setTimeout(resolve, 200));}}} catch (error) {console.error('批量插入失敗:', error);throw error;}
}
結語
本文提供了從基礎到高級的 Node.js 操作 ElasticSearch 的完整指南,涵蓋了索引管理、文檔操作、復雜搜索、聚合分析等核心功能,并通過電商商品搜索的實戰案例展示了如何在實際項目中應用 ElasticSearch。
希望這些示例代碼能幫助您更好地在 Node.js 項目中集成 ElasticSearch。根據實際業務需求,您可以進一步擴展和優化這些代碼。