本文将手把手教你如何搭建 ElasticSearch 环境,并通过 Node.js 实现高效数据检索。包含 10+ 个可直接复用的代码片段,助你快速掌握搜索、聚合等核心功能!
环境搭建篇
1. ElasticSearch 安装要点
下载
下载下来后,进入 bin
目录,终端运行第一个文件,即可启动es。
修改密码
进入 bin
目录下,终端输入:
bash
.\elasticsearch-reset-password -u elastic -i
输入两次密码即可修改超级用户 elastic
的密码。
然后访问 http://localhost:9200
。输入密码和账号后,若返回以下信息则代表修改密码成功:
json
{
"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
的版本要与 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
目录,在终端输入以下命令:bash.\elasticsearch-reset-password -u elastic -i
-
按照提示输入两次新密码。
-
访问
http://localhost:9200
,使用账号elastic
和新密码登录。如果返回类似以下信息,则表示密码修改成功:json{ "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 客户端库:
bash
npm install @elastic/elasticsearch
基本使用
以下是一些常见的 ElasticSearch 操作示例:
1. 初始化客户端 (支持多种认证方式)
js
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. 创建索引并添加数据
js
const user = await client.index({
index: 'user-data',
document: {
user: 1,
age: 18,
name: 'jack',
}
});
3. 查询数据
js
const response = await client.get({
index: 'user-data',
id: user._id // 可以指定 ID 或使用自动生成的 ID
});
4. 搜索数据
js
const result = await client.search({
index: 'user-data',
query: {
match: {
name: 'jack' // 模糊查询
}
},
size: 1 // 返回结果数量
});
console.log(result.hits.hits); // 打印搜索结果
5. 删除数据
js
await client.delete({
index: 'user-data',
id: user._id
});
6. 搜索所有数据
js
const response = await client.search({
index: 'users',
query: {
match_all: {}, // 空对象表示匹配所有
},
size: 100 // 返回 100 条数据
});
7. 索引管理
创建索引(带映射)
javascript
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);
}
}
检查索引是否存在
javascript
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;
}
}
删除索引
javascript
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. 文档操作
批量插入文档
javascript
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);
}
}
更新文档
javascript
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 });
部分更新与脚本更新
javascript
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. 高级搜索查询
多条件复合查询
javascript
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;
}
}
聚合查询
javascript
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;
}
}
全文搜索与高亮
javascript
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. 实战案例:电商商品搜索
javascript
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. 错误处理与性能优化
重试机制
javascript
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;
}
}
批量操作优化
javascript
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。根据实际业务需求,您可以进一步扩展和优化这些代码。