使用原生ES当向量数据库

发布时间:2026/9/21 12:05:37
使用原生ES当向量数据库 1. Maven 依赖 (pom.xml)xmldependencies !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Elasticsearch 8.x 官方 Java Client -- dependency groupIdco.elastic.clients/groupId artifactIdelasticsearch-java/artifactId version8.12.0/version /dependency !-- ES Client 底层通信依赖 -- dependency groupIdorg.elasticsearch.client/groupId artifactIdelasticsearch-rest-client/artifactId version8.12.0/version /dependency !-- Jackson JSON 处理 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.2/version /dependency /dependencies2. 模拟 Embedding 模型服务 (EmbeddingService.java)在实际业务中这里会替换为调用 OpenAI、通义千问等真实 API 的代码。这里为了演示我们生成一个固定维度的随机向量。javaimport org.springframework.stereotype.Service; import java.util.Random; Service public class EmbeddingService { private static final int VECTOR_DIM 768; // 假设模型输出 768 维 private final Random random new Random(); /** * 将文本转换为向量 * 【实际业务中在此处通过 HTTP 调用大模型 Embedding API】 */ public float[] embed(String text) { // 模拟生成 768 维的随机向量 float[] vector new float[VECTOR_DIM]; for (int i 0; i VECTOR_DIM; i) { vector[i] random.nextFloat(); } return vector; } }3. ES 客户端配置 (ElasticsearchConfig.java)javaimport 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; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class ElasticsearchConfig { Bean public RestClient restClient() { return RestClient.builder(new HttpHost(localhost, 9200, http)).build(); } Bean public ElasticsearchClient elasticsearchClient(RestClient restClient) { ElasticsearchTransport transport new RestClientTransport(restClient, new JacksonJsonpMapper()); return new ElasticsearchClient(transport); } }4. 核心业务 Controller (VectorController.java)这个 Controller 包含了创建索引、写入文档自动调用 Embedding、以及 kNN 检索的完整流程。javaimport co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import org.springframework.web.bind.annotation.*; import java.util.*; RestController RequestMapping(/api/vector) public class VectorController { private final ElasticsearchClient esClient; private final EmbeddingService embeddingService; private static final String INDEX_NAME my_native_vector_index; private static final int VECTOR_DIM 768; public VectorController(ElasticsearchClient esClient, EmbeddingService embeddingService) { this.esClient esClient; this.embeddingService embeddingService; } /** * 1. 初始化索引 (只需调用一次) */ PostMapping(/init-index) public String initIndex() throws Exception { boolean exists esClient.indices().exists(e - e.index(INDEX_NAME)).value(); if (exists) return 索引已存在无需重复创建; esClient.indices().create(c - c .index(INDEX_NAME) .mappings(m - m .properties(content, p - p.text(t - t)) .properties(content_vector, p - p.denseVector(dv - dv .dims(VECTOR_DIM) .index(true) .similarity(cosine) )) ) ); return 索引创建成功; } /** * 2. 写入文档 (核心在这里调用 Embedding 模型并将向量放入文档) */ PostMapping(/add) public String addDocument(RequestBody MapString, String payload) throws Exception { String content payload.get(content); // 【关键步骤】调用模型生成向量 float[] vector embeddingService.embed(content); // 组装文档必须包含 content_vector 字段 MapString, Object doc new HashMap(); doc.put(content, content); doc.put(content_vector, vector); // 将生成的向量塞入文档 // 写入 ES esClient.index(i - i.index(INDEX_NAME).document(doc)); return 文档向量化并存储成功; } /** * 3. kNN 向量检索 */ GetMapping(/search) public ListMapString, Object search(RequestParam String query, RequestParam(defaultValue 5) int topK) throws Exception { // 【关键步骤】查询文本也需要调用模型生成向量 float[] queryVector embeddingService.embed(query); SearchResponseMap response esClient.search(s - s .index(INDEX_NAME) .knn(k - k .field(content_vector) .queryVector(queryVector) .k(topK) .numCandidates(topK * 10) ), Map.class ); // 格式化返回结果 ListMapString, Object results new ArrayList(); for (HitMap hit : response.hits().hits()) { MapString, Object map new HashMap(); map.put(score, hit.score()); map.put(content, hit.source().get(content)); results.add(map); } return results; } }ES索引示例PUT /vector_docs { settings: { number_of_shards: 3, number_of_replicas: 1, index.knn: true, // 必须开启KNN插件总开关 index.knn.algo_param.ef_search: 100 }, mappings: { properties: { content: { type: text }, // 原始文本全文检索 doc_id: { type: keyword }, embedding: { type: dense_vector, dims: 1536, index: true, index_options: { type: hnsw }, similarity: cosine } } } }注意这里dims要和embedding模型维度一致维度由embedding决定(特例:动态embedding可以指定维度) 核心实现逻辑总结解耦设计ES 客户端只负责“存”和“搜”不负责“文本转向量”。写入流程在addDocument接口中先调用embeddingService.embed(content)拿到float[]然后doc.put(content_vector, vector)最后交给 ES 存储。检索流程在search接口中用户的查询语句query也必须先经过embeddingService.embed(query)变成向量才能传给 ES 的.queryVector(queryVector)进行比对。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询