一 简介
众所周知,检索是大模型 RAG 应用中的重要步骤。用户输入的问题,需要 rag 服务先使用检索模块检索到与问题最相关的知识,再进行筛选、排序、甄别、整理、总结,直到生成最终的回答。而检索结果直接关系到回答的质量。
尽管有了很多向量检索方案,但在实际应用中我们发现,基于向量的检索并不能完全取代关键词检索(尤其是在一些专业领域)。因此像 Elasticsearch 这类传统检索工具依然有很大的用武之地。本篇回顾一些 Es 安装和集成的基础知识,供大家尤其是初学者参考和从零开始搭建环境。
二 Elasticsearch 7.x 按照步骤及注意事项
2.1 安装
2.1.1 homebrew 安装
1. 安装 Homebrew(如果尚未安装)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
2. 添加 Elastic 的 Homebrew Tap
brew tap elastic/tap
3. 安装 Elasticsearch 7.x(最新 7.17.x)
brew install elastic/tap/elasticsearch-full@7
4. 启动 Elasticsearch
brew services start elastic/tap/elasticsearch-full@7
复制代码
2.1.2 下载压缩包安装
# 1. 下载 Elasticsearch 7.x(选择最新 7.17.x 版本)
curl -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.13-darwin-x86_64.tar.gz
# 2. 解压文件
tar -xzf elasticsearch-7.17.13-darwin-x86_64.tar.gz
cd elasticsearch-7.17.13/
# 3. 启动 Elasticsearch(前台运行)
./bin/elasticsearch
# 或者作为守护进程运行
./bin/elasticsearch -d -p pid
复制代码
验证安装结果:
curl -X GET "http://localhost:9200"
# 得到的输出如下
{
"name" : "MacBook-Air.local",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "GZx-tgLsRe61lUHb60J4qg",
"version" : {
"number" : "7.17.13",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "2b211dbb8bfdecaf7f5b44d356bdfe54b1050c13",
"build_date" : "2023-08-31T17:33:19.958690787Z",
"build_snapshot" : false,
"lucene_version" : "8.11.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
复制代码
注意:按照上述方法安装,执行./bin/elasticsearch 启动服务时,可能会抱如下错误:
error updating geoip database [GeoLite2-ASN.mmdb]
java.net.SocketTimeoutException: Connect timed out
复制代码
错误信息是 Elasticsearch 在启动时尝试下载 GeoIP 数据库(GeoLite2-ASN.mmdb)时连接超时。Elasticsearch 从 7.0 版本开始,内置了 GeoIP 下载功能,但可能由于网络问题(特别是国内访问 MaxMind 的数据库下载较慢)导致超时。
解决方法:
1. 手动下载并安装 GeoIP 数据库,操作步骤如下:
# 1. 停止 Elasticsearch(如果正在运行)
ps aux | grep elasticsearch | grep -v grep | awk '{print $2}' | xargs kill -9
# 2. 手动下载数据库
mkdir -p config/ingest-geoip
cd config/ingest-geoip
curl -O https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-ASN.mmdb
curl -O https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-City.mmdb
curl -O https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb
cd ../..
复制代码
注意:https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-ASN.mmdb这个源 git 地址也很可能无法下载,所以需要改为使用其他公开源的地址。例如 Cloudflare 镜像(本文采用,已验证):
# ASN 数据库
curl -O https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/GeoLite2-ASN.mmdb
# City 数据库
curl -O https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/GeoLite2-City.mmdb
# Country 数据库
curl -O https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/GeoLite2-Country.mmdb
复制代码
或尝试其他源,例如阿里或华为云,也已验证可下载并且速度很快:
# 阿里云镜像
curl -O https://mirrors.aliyun.com/elasticsearch/geoip/GeoLite2-ASN.mmdb
# 华为云镜像
curl -O https://repo.huaweicloud.com/geoip/GeoLite2-ASN.mmdb
复制代码
2. 禁用 GeoIP 更新(临时解决方案)
编辑 config/elasticsearch.yml
:
# 禁用自动更新
ingest.geoip.downloader.enabled: false
# 使用空数据库(避免错误)
ingest.geoip.database_md5.ASN: "dummy"
复制代码
3. 增加超时时间
编辑 config/elasticsearch.yml
# 增加超时时间(单位:秒)
ingest.geoip.downloader.timeout: 300
复制代码
2.1.3 其他常见问题
1. 内存不足错误
编辑 config/jvm.options
:
# 修改为适合您系统的值(推荐 1-2GB)
-Xms1g
-Xmx1g
复制代码
2. 文件描述符限制问题
# 临时增加限制
sudo sysctl -w kern.maxfiles=65536
sudo sysctl -w kern.maxfilesperproc=65536
# 永久设置(创建 /etc/sysctl.conf)
kern.maxfiles=65536
kern.maxfilesperproc=65536
复制代码
3. 无法绑定端口
检查端口是否被占用:
查到占用端口的进程后,kill {PID} 杀掉占用端口的进程,或修改 elasticsearch 的默认端口即可。
4. 权限问题
# 修改数据目录权限
sudo chown -R $USER /usr/local/var/lib/elasticsearch/
复制代码
5、安装 Kibana(可选)及基础使用简介
# 使用 Homebrew 安装
brew install elastic/tap/kibana-full
# 启动 Kibana
brew services start elastic/tap/kibana-full
复制代码
访问:http://localhost:5601 即可看到 es 的基础信息,并进行操作。例如根据提供的示例,添加示例数据:
添加完成后,可以在『view data』中查看 DashBoard。
es 中查看通过 kibana 添加的测试数据:
http://localhost:9200/cat/indices/kibana_sample_data*?v&s=index 可以查到如下三个 index 的数据信息:
也可以使用 Kibana → 左侧菜单 → Management → Dev Tools,在 Console 中执行查询:
// 查询电子商务数据(前10条)
GET kibana_sample_data_ecommerce/_search
{
"size": 10,
"query": {
"match_all": {}
}
}
// 查询航班数据(按时间排序)
GET kibana_sample_data_flights/_search
{
"size": 5,
"sort": [
{ "timestamp": "desc" }
]
}
// 查询日志数据(过滤错误日志)
GET kibana_sample_data_logs/_search
{
"query": {
"match": {
"event.severity": "error"
}
}
}
复制代码
执行结果如下图所示:
更多 Kibana 的使用操作,如果感兴趣后续再做详细讲解,本篇不做展开。
6、卸载 Elasticsearch
# Homebrew 用户
brew services stop elastic/tap/elasticsearch-full
brew uninstall elastic/tap/elasticsearch-full
# 手动安装用户
pkill -F pid # 停止进程
rm -rf elasticsearch-7.17.13/
复制代码
7、版本兼容性说明
Elasticsearch 7.x 需要 Java 11 或更高版本
使用 java -version
检查 Java 版本
如果需要安装 Java: brew install openjdk@11
三 Springboot 集成 Elasticsearch 7.x
3.1 集成关键代码
1、新建 maven 工程
2、pom 文件引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.flamingskys.learn.rag</groupId>
<artifactId>db-apps</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version> <!-- 与 ES 7.x 兼容 -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- 确保使用 REST 客户端而非过时的 Transport 客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
复制代码
3、实体类代码,这里定义商品的 id、名称、价格
package com.flamingskys.learn.rag.es.entity;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Data
@Document(indexName = "products")
public class Product {
@Id
private String id;
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String name;
@Field(type = FieldType.Double)
private Double price;
// 必须有默认构造函数
public Product() {}
public Product(String name, Double price) {
this.name = name;
this.price = price;
}
}
复制代码
4、Repository 定义
继承自 ElasticsearchRepository
package com.flamingskys.learn.rag.es.repository;
import com.flamingskys.learn.rag.es.entity.Product;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
List<Product> findByName(String name);
Page<Product> findByName(String name, Pageable pageable);
List<Product> findByPriceBetween(Double minPrice, Double maxPrice);
}
复制代码
5、Service 定义:
import com.flamingskys.learn.rag.es.entity.Product;
import com.flamingskys.learn.rag.es.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
private final ProductRepository productRepository;
@Autowired
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Product saveProduct(Product product) {
return productRepository.save(product);
}
public Page<Product> searchProducts(String query, Pageable pageable) {
return productRepository.findByName(query, pageable);
}
public void deleteProduct(String id) {
productRepository.deleteById(id);
}
}
复制代码
6、Controller:3 个接口:创建索引、检索、删除
import com.flamingskys.learn.rag.es.service.ProductService;
import com.flamingskys.learn.rag.es.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@PostMapping("/create")
public Product createProduct(@RequestBody Product product) {
return productService.saveProduct(product);
}
@GetMapping("/search")
public Page<Product> searchProducts(
@RequestParam String query,
Pageable pageable) {
return productService.searchProducts(query, pageable);
}
@DeleteMapping("/{id}")
public void deleteProduct(@PathVariable String id) {
productService.deleteProduct(id);
}
}
复制代码
7、配置类,从 application.yml 中读取 es 相关配置信息:
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
@Configuration
public class ElasticsearchConfig {
private final ElasticsearchProperties elasticsearchProperties;
@Autowired
public ElasticsearchConfig(ElasticsearchProperties elasticsearchProperties) {
this.elasticsearchProperties = elasticsearchProperties;
}
@Bean
public RestHighLevelClient elasticsearchClient() {
// 将 URI 转换为 host:port 格式
List<String> hostsAndPorts = elasticsearchProperties.getUris().stream()
.map(uri -> {
URI parsed = URI.create(uri);
return parsed.getHost() + ":" + parsed.getPort();
})
.collect(Collectors.toList());
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo(hostsAndPorts.toArray(new String[0]))
// .withBasicAuth(elasticsearchProperties.getUsername(), elasticsearchProperties.getPassword())
.withConnectTimeout(elasticsearchProperties.getConnectionTimeout())
.withSocketTimeout(elasticsearchProperties.getSocketTimeout())
.build();
return RestClients.create(clientConfiguration).rest();
}
@Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchRestTemplate(elasticsearchClient());
}
}
复制代码
8、启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ElasticsearchApplication {
public static void main(String[] args) {
SpringApplication.run(ElasticsearchApplication.class, args);
}
}
复制代码
9、application.yml
spring:
elasticsearch:
rest:
uris: http://localhost:9200
# 如果启用了安全认证(ES 8.x默认启用)
# username: elastic
# password: yourpassword
# 连接池配置
connection-timeout: 5s
read-timeout: 30s
# 可选:禁用不需要的ES功能
xpack:
ml:
enabled: false
security:
enabled: false
复制代码
完整工程代码,可查看 https://gitee.com/flamingskyline/rag.git并获取。
3.2 常见问题处理
3.2.1 源发行版本 11
运行工程时,一直提示 java: 警告: 源发行版 11 需要目标发行版 11 导致无法运行,
已完成设置项目 SDK、设置模块语言级别、并在 pom.xml 的<properties>增加如下配置后运行还是没有生效:
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
复制代码
在命令行执行 mvn clean compile 清理并重新编译时,出现了错误信息:[ERROR] 源发行版 11 与 --enable-preview 一起使用时无效
排查 pom.xml 配置,确实在<build>标签中设置了<compilerArgs>--enable-preview</compilerArgs>
移除<jvmArguments>--enable-previews</> 后 错误消除。
3.2.2 ik 分词器
es 服务已启动,但启动 springboot 工程时报错,且信息如下:
Caused by: org.springframework.data.elasticsearch.RestStatusException:
Elasticsearch exception [type=illegal_argument_exception,
reason=Custom Analyzer [ik_max_word] failed to find tokenizer under name [ik_max_word]];
nested exception is ElasticsearchStatusException[Elasticsearch exception [type=illegal_argument_exception, reason=Custom Analyzer [ik_max_word] failed to find tokenizer under name [ik_max_word]]]
复制代码
Elasticsearch 无法找到名为 ik_max_word
的分词器,是因为 IK 分词器插件未正确安装或配置导致。
安装方法:
从https://release.infinilabs.com/analysis-ik/stable/ 中,下载 elasticsearch-analysis-ik-7.17.13.zip,解压到本地 es 下的 plugins ik 目录下,例如: /Users/{这里是你的真实目录}/develop/middlewire/elasticsearch-7.17.13/plugins/ik。 解压后包含以下文件:
安装完成后,重启 es 服务。
3.2.3 启动时无法加载机器学习(ML)所需的本地库
按照 ik 后重启 es 报错,且错误信息如下:
uncaught exception in thread [main] ElasticsearchException[Failure running machine learning native code.
This could be due to running on an unsupported OS or distribution, missing OS libraries, or a problem with the temp directory. To bypass this problem by running Elasticsearch without machine learning functionality set [xpack.ml.enabled: false].]
复制代码
是无法加载机器学习本地库导致。由于暂时没有使用到,所以使用提供的参考处理方法,禁用机器学习功能(推荐用于非生产环境)。
vi config/elasticsearch.yml
在配置中增加以下两个配置:
# 禁用 ML 功能
xpack.ml.enabled: false
# 同时禁用安全模块(如不需要)
xpack.security.enabled: false
复制代码
保存并再次重启 es,启动正常。
评论