Node.js 操作 ElasticSearch 完整指南:从安装到实战

本文将手把手教你如何搭建 ElasticSearch 环境,并通过 Node.js 实现高效数据检索。包含 10+ 个可直接复用的代码片段,助你快速掌握搜索、聚合等核心功能!

环境搭建篇

1. ElasticSearch 安装要点

下载

es下载连接

下载下来后,进入 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 下载链接

注意: 下载的 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

  1. 访问 ElasticSearch 官方下载页面
  2. 选择适合您操作系统的版本进行下载。

启动 ElasticSearch

  1. 下载完成后,解压文件并进入 bin 目录。
  2. 在终端中运行第一个文件(Windows 用户运行 .bat 文件,Linux/macOS 用户运行 .sh 文件)。
  3. 启动成功后,ElasticSearch 默认运行在 http://localhost:9200

修改 ElasticSearch 密码

  1. 进入 bin 目录,在终端输入以下命令:

    bash 复制代码
    .\elasticsearch-reset-password -u elastic -i
  2. 按照提示输入两次新密码。

  3. 访问 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

  1. 访问 Kibana 官方下载页面
  2. 注意: 下载的 Kibana 版本必须与 ElasticSearch 版本一致,否则可能会出现兼容性问题。

修改 Kibana 配置文件

  1. 进入 Kibana 目录,找到 /config/kibana.yml 文件。
  2. 修改以下配置项:
    • server.port: Kibana 的访问端口(默认为 5601)。
    • server.host: Kibana 的访问 IP(默认为 localhost)。
    • elasticsearch.usernameelasticsearch.password: 使用 kibana_system 用户的账号密码(需先在 ElasticSearch 中修改该用户的密码)。
  3. 保存配置文件。

启动 Kibana

  1. 进入 Kibana 的 bin 目录。
  2. 在终端中运行第一个文件(Windows 用户运行 .bat 文件,Linux/macOS 用户运行 .sh 文件)。
  3. 启动成功后,访问 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。根据实际业务需求,您可以进一步扩展和优化这些代码。

相关推荐
吃手机用谁付的款3 分钟前
基于hadoop的竞赛网站日志数据分析与可视化(下)
大数据·hadoop·python·信息可视化·数据分析
孟猛202332 分钟前
使用 C++ 调用 Elasticsearch API
elasticsearch
线条133 分钟前
Spark 单机模式安装与测试全攻略
大数据·分布式·spark
老周聊架构1 小时前
大数据领域开山鼻祖组件Hadoop核心架构设计
大数据
TDengine (老段)6 小时前
TDengine 使用最佳实践(2)
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
Deng9452013147 小时前
基于大数据的电力系统故障诊断技术研究
大数据·matplotlib·深度特征提取·随机森林分类算法·标签编码
小菜鸡062610 小时前
FlinkSQL通解
大数据·flink
寅鸷11 小时前
es里为什么node和shard不是一对一的关系
大数据·elasticsearch
码字的字节12 小时前
深入解析Hadoop架构设计:原理、组件与应用
大数据·hadoop·分布式·hadoop架构设计
popoxf13 小时前
在新版本的微信开发者工具中使用npm包
前端·npm·node.js