欢迎光临
我们一直在努力

高级java每日一道面试题-2025年7月01日-基础篇[LangChain4j]-EmbeddingStore 的作用是什么,有哪些主流实现?

EmbeddingStore 详解:作用、原理与主流实现

一、EmbeddingStore 的核心概念

1.1 什么是 EmbeddingStore?

EmbeddingStore 是专门用于存储、索引和检索 向量嵌入(Embeddings) 的数据库或存储系统。它是现代 AI 应用架构中的核心组件,特别是在 RAG(检索增强生成)系统中扮演关键角色。

1.2 核心作用与价值

// EmbeddingStore 的核心功能接口抽象
public interface EmbeddingStore<T> {
// 1. 向量存储:将文本/数据与对应的向量嵌入一起存储
void store(String id, float[] embedding, T metadata);

// 2. 相似性搜索:基于向量相似度检索最相关的条目
List<SearchResult<T>> search(float[] queryEmbedding, int k);

// 3. 混合搜索:结合向量相似度和元数据过滤
List<SearchResult<T>> search(float[] queryEmbedding,
FilterCondition filter,
int k);

// 4. 更新与删除
boolean update(String id, float[] newEmbedding, T metadata);
boolean delete(String id);

// 5. 批量操作
void batchStore(List<EmbeddingRecord<T>> records);
List<SearchResult<T>> batchSearch(List<float[]> queries, int k);
}

1.3 为什么需要专门的 EmbeddingStore?

传统数据库EmbeddingStore
基于精确匹配 基于相似性匹配
支持结构化查询 支持高维向量运算
优化事务处理 优化相似性搜索
行/列存储模型 向量索引存储模型

二、EmbeddingStore 的技术架构

2.1 核心组件

public class EmbeddingStoreArchitecture {
/**
* EmbeddingStore 的标准架构组件
*/

public interface Architecture {
// 1. 向量化组件
interface Vectorizer {
float[] embed(String text);
List<float[]> embed(List<String> texts);
}

// 2. 索引组件
interface VectorIndex {
void buildIndex(List<float[]> vectors);
List<Integer> search(float[] query, int k);
void updateIndex(int id, float[] newVector);
}

// 3. 存储组件
interface VectorStorage {
void persist(String id, float[] vector, Map<String, Object> metadata);
EmbeddingRecord retrieve(String id);
void delete(String id);
}

// 4. 检索组件
interface Retriever {
List<RetrievalResult> retrieve(String query, int k);
List<RetrievalResult> retrieve(float[] queryEmbedding, int k);
}
}

/**
* 完整的数据流
*/

public class EmbeddingStorePipeline {
// 数据准备 → 向量化 → 索引构建 → 存储 → 检索 → 返回结果
public List<RetrievalResult> process(String query) {
// 1. 文本预处理
String processed = preprocess(query);

// 2. 向量化
float[] embedding = vectorizer.embed(processed);

// 3. 相似性搜索
List<SearchResult> vectorResults = index.search(embedding, 10);

// 4. 元数据过滤(可选)
List<SearchResult> filtered = applyFilters(vectorResults);

// 5. 重排序(可选)
List<SearchResult> reranked = rerank(filtered, query);

// 6. 返回最终结果
return formatResults(reranked);
}
}
}

2.2 向量索引算法对比

public class VectorIndexAlgorithms {
/**
* 主流向量索引算法对比
*/

public enum IndexAlgorithm {
// 精确搜索算法
FLAT("Flat", "暴力搜索,100%准确率,速度慢,适合小规模数据"),
IVF("Inverted File", "倒排索引,平衡准确率和速度"),

// 近似最近邻搜索(ANN)算法
HNSW("Hierarchical Navigable Small World",
"分层可导航小世界图,高召回率,适合大规模数据"),
PQ("Product Quantization",
"乘积量化,高压缩比,内存效率高"),
LSH("Locality-Sensitive Hashing",
"局部敏感哈希,快速但准确率较低"),

// 混合算法
IVF_PQ("IVF + Product Quantization", "IVF 索引 + PQ 压缩"),
IVF_HNSW("IVF + HNSW", "IVF 粗筛 + HNSW 精筛");

private final String name;
private final String description;

IndexAlgorithm(String name, String description) {
this.name = name;
this.description = description;
}
}

/**
* 算法选择指南
*/

public class AlgorithmSelector {
public IndexAlgorithm select(Requirements req) {
if (req.datasetSize < 10_000) {
return IndexAlgorithm.FLAT; // 小数据集用精确搜索
} else if (req.datasetSize < 1_000_000) {
if (req.memoryBudget < 1_000_000_000) { // < 1GB
return IndexAlgorithm.IVF_PQ;
} else {
return IndexAlgorithm.HNSW;
}
} else { // 超大规模
if (req.accuracy > 0.95) {
return IndexAlgorithm.IVF_HNSW;
} else {
return IndexAlgorithm.PQ;
}
}
}
}

/**
* 索引配置参数
*/

public class IndexConfiguration {
// HNSW 参数
public static class HNSWConfig {
int M = 16; // 每个节点的最大连接数
int efConstruction = 200; // 构建时的动态列表大小
int efSearch = 100; // 搜索时的动态列表大小
}

// IVF 参数
public static class IVFConfig {
int nlist = 1024; // 聚类中心数量
int nprobe = 8; // 搜索时探查的聚类数
}

// PQ 参数
public static class PQConfig {
int m = 8; // 子空间数量
int bits = 8; // 每个子空间的编码位数
}
}
}

三、主流 EmbeddingStore 实现详解

3.1 开源实现

3.1.1 Milvus – 云原生向量数据库

/**
* Milvus Java 客户端示例
*/

public class MilvusExample {
private MilvusClient client;
private final String COLLECTION_NAME = "documents";

public void setupMilvus() {
// 1. 连接 Milvus
ConnectParam connectParam = ConnectParam.newBuilder()
.withHost("localhost")
.withPort(19530)
.build();
client = new MilvusClient(connectParam);

// 2. 创建集合(Collection)
FieldType fieldId = FieldType.newBuilder()
.withName("id")
.withDataType(DataType.Int64)
.withPrimaryKey(true)
.withAutoID(true)
.build();

FieldType fieldEmbedding = FieldType.newBuilder()
.withName("embedding")
.withDataType(DataType.FloatVector)
.withDimension(768) // 向量维度
.build();

FieldType fieldText = FieldType.newBuilder()
.withName("text")
.withDataType(DataType.VarChar)
.withMaxLength(65535)
.build();

CreateCollectionParam createParam = CreateCollectionParam.newBuilder()
.withCollectionName(COLLECTION_NAME)
.withDescription("Document embeddings")
.addFieldType(fieldId)
.addFieldType(fieldEmbedding)
.addFieldType(fieldText)
.build();

client.createCollection(createParam);

// 3. 创建向量索引
IndexType indexType = IndexType.IVF_FLAT;
String indexParam = "{\\"nlist\\":1024}";

CreateIndexParam indexParam = CreateIndexParam.newBuilder()
.withCollectionName(COLLECTION_NAME)
.withFieldName("embedding")
.withIndexType(indexType)
.withExtraParam(indexParam)
.build();

client.createIndex(indexParam);
}

public void storeDocument(String text, float[] embedding) {
// 准备插入数据
List<Long> ids = Arrays.asList(System.currentTimeMillis());
List<List<Float>> vectors = Arrays.asList(
Arrays.stream(embedding).boxed().collect(Collectors.toList())
);
List<String> texts = Arrays.asList(text);

// 构建插入参数
InsertParam insertParam = InsertParam.newBuilder()
.withCollectionName(COLLECTION_NAME)
.addField("id", ids)
.addField("embedding", vectors)
.addField("text", texts)
.build();

// 插入数据
InsertResponse response = client.insert(insertParam);

// 刷新使数据可搜索
FlushParam flushParam = FlushParam.newBuilder()
.addCollectionName(COLLECTION_NAME)
.build();
client.flush(flushParam);
}

public List<SearchResult> searchSimilar(float[] queryEmbedding, int k) {
// 构建搜索参数
List<String> outputFields = Arrays.asList("id", "text");
List<List<Float>> queryVectors = Arrays.asList(
Arrays.stream(queryEmbedding).boxed().collect(Collectors.toList())
);

SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName(COLLECTION_NAME)
.withMetricType(MetricType.L2) // 使用 L2 距离
.withTopK(k)
.withVectors(queryVectors)
.withVectorFieldName("embedding")
.withParams("{\\"nprobe\\":10}")
.withOutFields(outputFields)
.build();

// 执行搜索
SearchResponse response = client.search(searchParam);

return response.getResults().stream()
.map(this::convertToResult)
.collect(Collectors.toList());
}

// 混合搜索:向量相似度 + 标量过滤
public List<SearchResult> hybridSearch(float[] queryEmbedding,
String category,
int k) {
String expr = String.format("category == '%s'", category);

SearchParam searchParam = SearchParam.newBuilder()
.withCollectionName(COLLECTION_NAME)
.withMetricType(MetricType.IP) // 内积相似度
.withTopK(k)
.withVectors(Arrays.asList(
Arrays.stream(queryEmbedding).boxed()
.collect(Collectors.toList())
))
.withVectorFieldName("embedding")
.withExpr(expr) // 过滤表达式
.withParams("{\\"nprobe\\":16}")
.build();

SearchResponse response = client.search(searchParam);
return convertResults(response);
}
}

3.1.2 Weaviate – 基于 GraphQL 的向量搜索引擎

/**
* Weaviate Java 客户端示例
*/

public class WeaviateExample {
private WeaviateClient client;
private final String CLASS_NAME = "Document";

public void setupWeaviate() {
// 1. 配置客户端
Config config = new Config("http", "localhost:8080");
client = new WeaviateClient(config);

// 2. 创建模式(Schema)
WeaviateClass documentClass = WeaviateClass.builder()
.className(CLASS_NAME)
.description("A document with text and embedding")
.vectorizer("none") // 使用外部向量化
.properties(Arrays.asList(
Property.builder()
.name("text")
.dataType(Arrays.asList(DataType.TEXT))
.build(),
Property.builder()
.name("category")
.dataType(Arrays.asList(DataType.TEXT))
.build(),
Property.builder()
.name("source")
.dataType(Arrays.asList(DataType.TEXT))
.build()
))
.build();

client.schema().classCreator().withClass(documentClass).run();
}

public void storeWithEmbedding(String text, float[] embedding,
String category) {
// 创建数据对象
Map<String, Object> properties = new HashMap<>();
properties.put("text", text);
properties.put("category", category);
properties.put("source", "web_crawl");

// 构建请求
client.data().creator()
.withClassName(CLASS_NAME)
.withProperties(properties)
.withVector(embedding) // 外部生成的向量
.withID(UUID.randomUUID().toString())
.run();
}

public List<SearchResult> semanticSearch(String query,
float[] queryEmbedding,
int limit) {
// 构建 GraphQL 查询
String graphqlQuery = String.format(
"{" +
" Get {" +
" %s (" +
" nearVector: {" +
" vector: %s" +
" certainty: 0.8" + // 相似度阈值
" }" +
" limit: %d" +
" where: {" +
" path: [\\"category\\"]" +
" operator: Equal" +
" valueString: \\"technology\\"" +
" }" +
" ) {" +
" text" +
" category" +
" _additional {" +
" id" +
" certainty" +
" vector" +
" }" +
" }" +
" }" +
"}",
CLASS_NAME,
Arrays.toString(queryEmbedding),
limit
);

// 执行查询
Result<GraphQLResponse> result = client.graphQL().raw().run(graphqlQuery);

if (result.hasErrors()) {
throw new RuntimeException("GraphQL query failed: " + result.getError());
}

return parseGraphQLResponse(result.getResult());
}

// BM25 文本搜索 + 向量搜索的混合查询
public List<SearchResult> hybridSearch(String queryText,
float[] queryEmbedding,
double alpha) {
// alpha 控制文本搜索和向量搜索的权重
// alpha=1: 纯向量搜索, alpha=0: 纯文本搜索

String graphqlQuery = String.format(
"{" +
" Get {" +
" %s (" +
" hybrid: {" +
" query: \\"%s\\"" +
" vector: %s" +
" alpha: %f" +
" }" +
" limit: 10" +
" ) {" +
" text" +
" _additional {" +
" id" +
" score" +
" explainScore" +
" }" +
" }" +
" }" +
"}",
CLASS_NAME,
queryText,
Arrays.toString(queryEmbedding),
alpha
);

Result<GraphQLResponse> result = client.graphQL().raw().run(graphqlQuery);
return parseHybridResults(result.getResult());
}
}

3.1.3 Qdrant – Rust 开发的高性能向量数据库

/**
* Qdrant Java 客户端示例
*/

public class QdrantExample {
private QdrantClient client;
private final String COLLECTION_NAME = "documents";

public void setupQdrant() {
// 1. 创建客户端
client = new QdrantClient(
new QdrantGrpcClient(
"localhost",
6334,
false // 不使用 TLS
)
);

// 2. 创建集合(Collection)
Distance distance = Distance.Cosine; // 余弦相似度

VectorParams vectorParams = VectorParams.newBuilder()
.size(768) // 向量维度
.distance(distance)
.build();

CreateCollection createCollection = CreateCollection.newBuilder()
.collectionName(COLLECTION_NAME)
.vectorsConfig(vectorParams)
.build();

client.createCollection(createCollection).join();

// 3. 创建索引
CreateIndex createIndex = CreateIndex.newBuilder()
.collectionName(COLLECTION_NAME)
.fieldName("category")
.build();

client.createPayloadIndex(createIndex).join();
}

public void upsertDocument(String id, float[] embedding,
Map<String, Value> payload) {
// 准备点(Point)
PointStruct point = PointStruct.newBuilder()
.id(PointId.newBuilder().num(Long.parseLong(id)).build())
.vector(embedding)
.payload(payload)
.build();

// 构建 Upsert 操作
UpsertPoints upsertPoints = UpsertPoints.newBuilder()
.collectionName(COLLECTION_NAME)
.points(Collections.singletonList(point))
.wait(true) // 等待操作完成
.build();

client.upsertPoints(upsertPoints).join();
}

public List<ScoredPoint> searchVectors(float[] queryEmbedding,
int limit,
Map<String, Value> filter) {
// 构建搜索参数
SearchPoints searchPoints = SearchPoints.newBuilder()
.collectionName(COLLECTION_NAME)
.vector(queryEmbedding)
.limit(limit)
.withPayload(true) // 返回 payload
.withVector(false) // 不返回向量数据
.filter(Filter.newBuilder()
.must(Collections.singletonList(
Condition.newBuilder()
.field(Field.newBuilder()
.key("category")
.match(Match.newBuilder()
.keyword("technology")
.build())
.build())
.build()))
.build())
.params(SearchParams.newBuilder()
.hnswEf(128) // HNSW 参数
.exact(false) // 使用近似搜索
.build())
.build();

// 执行搜索
SearchResponse response = client.searchPoints(searchPoints).join();
return response.getResultList();
}

// 推荐搜索:基于多个正例和负例
public List<ScoredPoint> recommendSearch(List<Long> positiveIds,
List<Long> negativeIds,
int limit) {
RecommendPoints recommendPoints = RecommendPoints.newBuilder()
.collectionName(COLLECTION_NAME)
.positive(positiveIds.stream()
.map(id -> PointId.newBuilder().num(id).build())
.collect(Collectors.toList()))
.negative(negativeIds.stream()
.map(id -> PointId.newBuilder().num(id).build())
.collect(Collectors.toList()))
.limit(limit)
.withPayload(true)
.params(RecommendParams.newBuilder()
.using("text_vector") // 使用特定的向量字段
.build())
.build();

RecommendResponse response = client.recommendPoints(recommendPoints).join();
return response.getResultList();
}
}

3.2 商业/云服务实现

3.2.1 Pinecone – 完全托管的向量数据库

/**
* Pinecone Java 客户端示例
*/

public class PineconeExample {
private PineconeClient client;
private final String INDEX_NAME = "quickstart";

public void setupPinecone() {
// 1. 初始化客户端
PineconeConfig config = new PineconeConfig.Builder()
.withApiKey(System.getenv("PINECONE_API_KEY"))
.withEnvironment("us-west1-gcp") // 选择环境
.build();

client = new PineconeClient(config);

// 2. 创建索引
CreateIndexRequest createRequest = CreateIndexRequest.builder()
.name(INDEX_NAME)
.dimension(768) // 向量维度
.metric(IndexMetric.COSINE) // 相似度度量
.spec(IndexSpec.builder()
.serverless(ServerlessSpec.builder()
.cloud("aws") // 或 "gcp"
.region("us-west-2")
.build())
.build())
.build();

try {
client.createIndex(createRequest);
} catch (PineconeException e) {
// 索引可能已存在
}

// 等待索引就绪
waitForIndexReady();
}

public void upsertVectors(List<Vector> vectors, String namespace) {
// 构建 Upsert 请求
UpsertRequest upsertRequest = UpsertRequest.builder()
.vectors(vectors)
.namespace(namespace) // 支持多命名空间隔离
.build();

// 执行 Upsert
UpsertResponse response = client.getIndexClient(INDEX_NAME)
.upsert(upsertRequest);

System.out.println("Upserted " + response.upsertedCount() + " vectors");
}

public QueryResponse queryVectors(float[] queryEmbedding,
int topK,
String namespace,
Map<String, Object> filter) {
// 构建查询请求
QueryRequest queryRequest = QueryRequest.builder()
.vector(queryEmbedding)
.topK(topK)
.namespace(namespace)
.includeMetadata(true)
.includeValues(false) // 不返回向量值
.filter(filter) // 元数据过滤
.build();

// 执行查询
return client.getIndexClient(INDEX_NAME)
.query(queryRequest);
}

// 使用稀疏向量进行混合搜索
public QueryResponse sparseDenseHybridSearch(float[] denseVector,
Map<Integer, Float> sparseVector,
double alpha,
int topK) {
// alpha: 稀疏向量权重 (1-alpha): 稠密向量权重

SparseValues sparseValues = SparseValues.builder()
.indices(new ArrayList<>(sparseVector.keySet()))
.values(new ArrayList<>(sparseVector.values()))
.build();

QueryRequest queryRequest = QueryRequest.builder()
.vector(denseVector)
.sparseVector(sparseValues)
.topK(topK)
.includeMetadata(true)
.build();

return client.getIndexClient(INDEX_NAME)
.query(queryRequest);
}

private void waitForIndexReady() {
int maxAttempts = 30;
for (int i = 0; i < maxAttempts; i++) {
try {
DescribeIndexStatsRequest request = DescribeIndexStatsRequest.builder().build();
DescribeIndexStatsResponse response = client.getIndexClient(INDEX_NAME)
.describeIndexStats(request);

if (response.totalVectorCount() > 0 || i == maxAttempts 1) {
break;
}

Thread.sleep(1000);
} catch (Exception e) {
// 继续等待
}
}
}
}

3.2.2 Elasticsearch with向量插件

/**
* Elasticsearch 向量搜索示例
*/

public class ElasticsearchVectorExample {
private RestHighLevelClient client;
private final String INDEX_NAME = "vector-docs";

public void setupElasticsearch() {
// 1. 创建客户端
client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")
)
);

// 2. 创建支持向量的索引映射
Map<String, Object> embeddingMapping = new HashMap<>();
embeddingMapping.put("type", "dense_vector");
embeddingMapping.put("dims", 768);
embeddingMapping.put("index", true);
embeddingMapping.put("similarity", "cosine"); // 或 "l2_norm", "dot_product"

Map<String, Object> properties = new HashMap<>();
properties.put("text", Map.of("type", "text"));
properties.put("embedding", embeddingMapping);
properties.put("category", Map.of("type", "keyword"));

Map<String, Object> mappings = Map.of("properties", properties);

CreateIndexRequest request = new CreateIndexRequest(INDEX_NAME)
.mapping(mappings);

try {
client.indices().create(request, RequestOptions.DEFAULT);
} catch (IOException e) {
// 索引可能已存在
}
}

public void indexDocument(String id, String text, float[] embedding,
String category) {
// 构建文档
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("text", text);
jsonMap.put("embedding", embedding);
jsonMap.put("category", category);

IndexRequest request = new IndexRequest(INDEX_NAME)
.id(id)
.source(jsonMap);

try {
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
} catch (IOException e) {
throw new RuntimeException("索引文档失败", e);
}
}

public List<SearchHit> knnSearch(float[] queryEmbedding, int k) {
// 构建 KNN 搜索请求
KnnSearchBuilder knnSearch = new KnnSearchBuilder("embedding", queryEmbedding, k)
.boost(1.0f)
.numCandidates(100); // 候选集大小

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
.knnSearch(List.of(knnSearch))
.size(k);

SearchRequest searchRequest = new SearchRequest(INDEX_NAME)
.source(sourceBuilder);

try {
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
return Arrays.asList(response.getHits().getHits());
} catch (IOException e) {
throw new RuntimeException("KNN 搜索失败", e);
}
}

// 混合搜索:KNN + BM25
public List<SearchHit> hybridSearch(String queryText,
float[] queryEmbedding,
int k) {
// 1. KNN 搜索
KnnSearchBuilder knnSearch = new KnnSearchBuilder("embedding", queryEmbedding, k)
.numCandidates(100);

// 2. 文本搜索
QueryBuilder textQuery = QueryBuilders.matchQuery("text", queryText)
.boost(0.5f); // 文本搜索权重

// 3. 组合查询
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
.query(QueryBuilders.boolQuery()
.should(knnSearch.toQueryBuilder())
.should(textQuery))
.size(k);

SearchRequest searchRequest = new SearchRequest(INDEX_NAME)
.source(sourceBuilder);

try {
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
return Arrays.asList(response.getHits().getHits());
} catch (IOException e) {
throw new RuntimeException("混合搜索失败", e);
}
}
}

3.3 轻量级/库实现

3.3.1 FAISS – Facebook 的向量搜索库

/**
* FAISS Java 绑定示例
*/

public class FaissExample {
private Index index;
private Map<Long, String> idToText;
private final int DIMENSION = 768;

public void setupFaiss() {
// 1. 选择索引类型
// 精确搜索
index = IndexFactory.indexIDMap(
IndexFactory.indexFlatL2(DIMENSION)
);

// 或使用近似搜索
// index = IndexFactory.indexIVFFlat(
// new IndexFlatL2(DIMENSION),
// DIMENSION,
// 1024, // nlist: 聚类中心数
// MetricType.METRIC_L2
// );

idToText = new ConcurrentHashMap<>();
}

public void trainIndex(List<float[]> trainingVectors) {
if (index instanceof IndexIVF) {
// IVF 索引需要训练
IndexIVF ivfIndex = (IndexIVF) index;

// 转换为二维数组
float[][] vectors = trainingVectors.toArray(new float[0][]);

// 训练索引
ivfIndex.train(trainingVectors.size(), vectors);
}
}

public void addVectors(List<Long> ids, List<float[]> vectors, List<String> texts) {
// 添加到索引
float[][] vectorArray = vectors.toArray(new float[0][]);
index.addWithIds(vectors.size(), vectorArray, ids.toArray(new Long[0]));

// 存储映射关系
for (int i = 0; i < ids.size(); i++) {
idToText.put(ids.get(i), texts.get(i));
}
}

public List<SearchResult> search(float[] queryVector, int k) {
// 搜索参数
SearchParameters params = new SearchParameters();

if (index instanceof IndexIVF) {
IndexIVF ivfIndex = (IndexIVF) index;
params = new IVFSearchParameters(32); // nprobe 参数
}

// 执行搜索
SearchResult[] results = new SearchResult[k];
float[] distances = new float[k];
long[] labels = new long[k];

index.search(1, queryVector, k, distances, labels, params);

// 转换为结果对象
List<SearchResult> searchResults = new ArrayList<>();
for (int i = 0; i < k; i++) {
if (labels[i] >= 0) { // 有效的 ID
String text = idToText.get(labels[i]);
searchResults.add(new SearchResult(labels[i], distances[i], text));
}
}

return searchResults;
}

// 范围搜索:查找距离在指定范围内的向量
public List<SearchResult> rangeSearch(float[] queryVector, float radius) {
RangeSearchResult rangeResult = index.rangeSearch(
1, queryVector, radius
);

List<SearchResult> results = new ArrayList<>();
for (int i = 0; i < rangeResult.limits[1]; i++) {
long label = rangeResult.labels[i];
float distance = rangeResult.distances[i];
String text = idToText.get(label);
results.add(new SearchResult(label, distance, text));
}

return results;
}
}

3.3.2 Chroma – 嵌入式向量数据库

/**
* Chroma Java 客户端示例
*/

public class ChromaExample {
private ChromaClient client;
private ChromaCollection collection;

public void setupChroma() {
// 1. 连接到 Chroma
client = new ChromaClient("http://localhost:8000");

// 2. 创建或获取集合
try {
collection = client.createCollection("documents");
} catch (Exception e) {
// 集合已存在
collection = client.getCollection("documents");
}
}

public void addDocuments(List<String> texts,
List<float[]> embeddings,
List<Map<String, Object>> metadatas,
List<String> ids) {
// 添加文档到集合
AddDocumentsRequest request = AddDocumentsRequest.builder()
.documents(texts)
.embeddings(embeddings)
.metadatas(metadatas)
.ids(ids)
.build();

collection.add(request);
}

public QueryResponse queryDocuments(float[] queryEmbedding,
int nResults,
Map<String, Object> whereFilter) {
// 构建查询请求
QueryRequest request = QueryRequest.builder()
.queryEmbeddings(Arrays.asList(queryEmbedding))
.nResults(nResults)
.where(whereFilter) // 元数据过滤
.include(Arrays.asList(
IncludeEnum.METADATAS,
IncludeEnum.DOCUMENTS,
IncludeEnum.DISTANCES
))
.build();

return collection.query(request);
}

// 更新文档的元数据
public void updateDocument(String id,
Map<String, Object> metadata,
String newText) {
UpdateDocumentRequest request = UpdateDocumentRequest.builder()
.ids(Arrays.asList(id))
.metadatas(Arrays.asList(metadata))
.documents(Arrays.asList(newText))
.build();

collection.update(request);
}
}

四、选择指南与对比分析

4.1 特性对比表

系统类型核心特性适用场景部署复杂度
Milvus 专用向量数据库 云原生、多索引支持、标量过滤 大规模生产环境 中等
Weaviate 向量搜索引擎 GraphQL接口、混合搜索、内置模块 知识图谱、语义搜索 中等
Qdrant 向量数据库 Rust开发、高性能、丰富过滤 高性能需求、复杂过滤 简单
Pinecone 云服务 完全托管、自动扩缩容、Serverless 快速原型、生产部署 非常简单
Elasticsearch 通用搜索引擎 向量插件、成熟生态、全文搜索 已有ES环境、混合搜索 复杂
FAISS 高效算法、灵活集成、CPU/GPU支持 研究、嵌入式应用 简单
Chroma 嵌入式数据库 轻量级、Python原生、易于使用 本地开发、小规模应用 非常简单

4.2 选择决策树

public class EmbeddingStoreSelector {

public String selectStore(Requirements req) {
// 决策逻辑
if (req.managedService) {
return "Pinecone"; // 需要完全托管服务
}

if (req.existingElasticsearch) {
return "Elasticsearch with vector plugin"; // 已有ES环境
}

if (req.scale > 10_000_000) { // 超大规模
if (req.needAdvancedFeatures) {
return "Milvus"; // 需要高级功能
} else {
return "Qdrant"; // 追求性能
}
}

if (req.needGraphQL) {
return "Weaviate"; // 需要GraphQL接口
}

if (req.lightweight) {
if (req.inMemory) {
return "FAISS"; // 内存索引
} else {
return "Chroma"; // 轻量级嵌入式
}
}

// 默认选择
return "Qdrant";
}

public static class Requirements {
boolean managedService; // 是否需要托管服务
boolean existingElasticsearch; // 是否已有ES环境
long scale; // 数据规模
boolean needAdvancedFeatures; // 是否需要高级功能
boolean needGraphQL; // 是否需要GraphQL
boolean lightweight; // 是否要求轻量级
boolean inMemory; // 是否仅内存使用
boolean realTimeUpdate; // 是否需要实时更新
boolean multiTenancy; // 是否需要多租户
double budget; // 预算限制
}
}

4.3 性能基准测试

/**
* EmbeddingStore 性能测试框架
*/

public class EmbeddingStoreBenchmark {

public BenchmarkResult benchmark(EmbeddingStore store,
Dataset dataset,
TestConfig config) {
BenchmarkResult result = new BenchmarkResult();

// 1. 写入性能测试
long startWrite = System.currentTimeMillis();
store.batchStore(dataset.getRecords());
long endWrite = System.currentTimeMillis();
result.writeThroughput = dataset.size() / ((endWrite startWrite) / 1000.0);

// 2. 搜索性能测试(QPS)
List<float[]> queryVectors = dataset.getQueryVectors();
long startSearch = System.currentTimeMillis();

int queriesProcessed = 0;
for (float[] query : queryVectors) {
store.search(query, config.topK);
queriesProcessed++;

if (System.currentTimeMillis() startSearch >= config.durationMs) {
break;
}
}

long endSearch = System.currentTimeMillis();
result.qps = queriesProcessed / ((endSearch startSearch) / 1000.0);

// 3. 准确率测试(召回率)
result.recall = calculateRecall(store, dataset, config.topK);

// 4. 内存使用测试
result.memoryUsage = getMemoryUsage(store);

return result;
}

private double calculateRecall(EmbeddingStore store,
Dataset dataset,
int topK) {
int totalRelevant = 0;
int totalRetrievedRelevant = 0;

for (float[] query : dataset.getQueryVectors()) {
List<GroundTruth> groundTruth = dataset.getGroundTruth(query);
List<SearchResult> results = store.search(query, topK);

totalRelevant += groundTruth.size();

for (SearchResult result : results) {
if (groundTruth.contains(result.id())) {
totalRetrievedRelevant++;
}
}
}

return (double) totalRetrievedRelevant / totalRelevant;
}

public static class BenchmarkResult {
double writeThroughput; // 写入吞吐量 (vectors/sec)
double qps; // 查询每秒 (queries/sec)
double recall; // 召回率
long memoryUsage; // 内存使用 (bytes)
double latencyP50; // 延迟中位数
double latencyP95; // 95分位延迟
double latencyP99; // 99分位延迟
}
}

五、生产环境最佳实践

5.1 数据建模

/**
* 生产环境数据模型设计
*/

public class ProductionDataModel {

/**
* 向量记录设计
*/

@Data
public class VectorRecord {
// 唯一标识
private String id;

// 向量数据
private float[] embedding;

// 原始内容
private String content;

// 元数据(用于过滤)
private Metadata metadata;

// 时间戳(用于版本控制)
private long timestamp;

// 分区/分片键
private String partitionKey;
}

/**
* 元数据设计
*/

@Data
public class Metadata {
// 内容类型
private ContentType contentType; // TEXT, IMAGE, AUDIO, VIDEO

// 来源信息
private String source;
private String sourceId;

// 分类标签
private List<String> categories;
private List<String> tags;

// 权限控制
private String owner;
private List<String> allowedUsers;
private Visibility visibility; // PUBLIC, PRIVATE, SHARED

// 质量指标
private double qualityScore;
private long viewCount;

// 时间信息
private Date createdAt;
private Date updatedAt;
private Date expiresAt; // 过期时间
}

/**
* 索引策略
*/

public class IndexingStrategy {
// 1. 分片策略
public String getShardKey(VectorRecord record) {
// 基于内容类型分片
return record.getMetadata().getContentType().toString();
}

// 2. 索引类型选择
public IndexType selectIndexType(long datasetSize,
int dimension,
AccuracyRequirement accuracy) {
if (datasetSize < 100_000) {
return IndexType.FLAT; // 小数据用精确搜索
} else if (accuracy == AccuracyRequirement.HIGH) {
return IndexType.HNSW; // 高准确率需求
} else {
return IndexType.IVF_PQ; // 内存效率优先
}
}

// 3. 向量归一化
public float[] normalizeVector(float[] vector) {
// L2 归一化,适用于余弦相似度
float norm = 0.0f;
for (float v : vector) {
norm += v * v;
}
norm = (float) Math.sqrt(norm);

float[] normalized = new float[vector.length];
for (int i = 0; i < vector.length; i++) {
normalized[i] = vector[i] / norm;
}
return normalized;
}
}
}

5.2 监控与运维

/**
* EmbeddingStore 监控系统
*/

public class EmbeddingStoreMonitoring {

private final MetricRegistry metrics = new MetricRegistry();
private final HealthCheckRegistry healthChecks = new HealthCheckRegistry();

public void setupMonitoring(EmbeddingStore store) {
// 1. 注册性能指标
metrics.register("search.latency", new Timer());
metrics.register("write.latency", new Timer());
metrics.register("index.size", new Gauge<Long>() {
@Override
public Long getValue() {
return store.getIndexSize();
}
});

// 2. 注册健康检查
healthChecks.register("connection", new HealthCheck() {
@Override
protected Result check() {
try {
store.healthCheck();
return Result.healthy();
} catch (Exception e) {
return Result.unhealthy(e);
}
}
});

// 3. 设置告警规则
setupAlerts();
}

private void setupAlerts() {
AlertManager alertManager = new AlertManager();

// 搜索延迟告警
alertManager.addRule(AlertRule.builder()
.metric("search.latency.p99")
.threshold(100) // 100ms
.duration("1m") // 持续1分钟
.severity(Severity.WARNING)
.build());

// 索引大小告警
alertManager.addRule(AlertRule.builder()
.metric("index.size")
.threshold(10_000_000_000L) // 10GB
.severity(Severity.CRITICAL)
.build());

// 错误率告警
alertManager.addRule(AlertRule.builder()
.metric("search.error_rate")
.threshold(0.01) // 1%
.duration("5m")
.severity(Severity.ERROR)
.build());
}

/**
* 性能优化建议
*/

public OptimizationSuggestions analyzePerformance(MetricsSnapshot snapshot) {
OptimizationSuggestions suggestions = new OptimizationSuggestions();

// 分析搜索性能
if (snapshot.searchLatencyP99 > 100) {
if (snapshot.indexSize > 1_000_000) {
suggestions.add("考虑使用 HNSW 索引替代 IVF_PQ");
}
suggestions.add("增加 nprobe 参数以提高准确率");
}

// 分析内存使用
if (snapshot.memoryUsage > snapshot.availableMemory * 0.8) {
suggestions.add("考虑使用 PQ 压缩减少内存使用");
suggestions.add("增加分片数量分散内存压力");
}

// 分析写入性能
if (snapshot.writeThroughput < 1000) {
suggestions.add("启用批量写入优化");
suggestions.add("调整索引刷新间隔");
}

return suggestions;
}
}

5.3 安全与权限

/**
* EmbeddingStore 安全层
*/

public class EmbeddingStoreSecurity {

/**
* 基于角色的访问控制
*/

public class RBACAuthorization {
private final Map<String, Set<Permission>> rolePermissions;

public boolean checkPermission(User user,
String resource,
Permission required) {
// 获取用户角色
Set<Role> roles = user.getRoles();

// 检查每个角色的权限
for (Role role : roles) {
Set<Permission> permissions = rolePermissions.get(role.getName());
if (permissions != null && permissions.contains(required)) {
return true;
}
}

return false;
}
}

/**
* 行级安全(RLS)
*/

public class RowLevelSecurity {
public FilterCondition createFilter(User user) {
// 基于用户属性创建过滤条件
return FilterCondition.builder()
.must(FieldCondition.builder()
.field("visibility")
.match(Match.builder()
.keyword("PUBLIC")
.build())
.build())
.should(FieldCondition.builder()
.field("owner")
.match(Match.builder()
.keyword(user.getId())
.build())
.build())
.should(FieldCondition.builder()
.field("allowedUsers")
.match(Match.builder()
.keyword(user.getId())
.build())
.build())
.build();
}
}

/**
* 数据加密
*/

public class DataEncryption {
private final EncryptionService encryptionService;

public EncryptedVector encryptVector(float[] vector, String keyId) {
// 序列化向量
ByteBuffer buffer = ByteBuffer.allocate(vector.length * 4);
for (float f : vector) {
buffer.putFloat(f);
}

// 加密数据
byte[] encrypted = encryptionService.encrypt(
buffer.array(),
keyId
);

return new EncryptedVector(encrypted, keyId);
}

public float[] decryptVector(EncryptedVector encrypted) {
// 解密数据
byte[] decrypted = encryptionService.decrypt(
encrypted.getData(),
encrypted.getKeyId()
);

// 反序列化
ByteBuffer buffer = ByteBuffer.wrap(decrypted);
float[] vector = new float[decrypted.length / 4];
for (int i = 0; i < vector.length; i++) {
vector[i] = buffer.getFloat();
}

return vector;
}
}
}

六、面试问题与深度解析

6.1 常见面试问题

Q1:EmbeddingStore 与传统数据库在向量搜索方面有什么本质区别?

深度解析:

核心区别在于索引结构和查询范式:

1. 索引结构差异:
传统数据库:B-tree、Hash、倒排索引(用于文本)
EmbeddingStore:HNSW、IVF、PQ 等向量索引

2. 查询范式不同:
传统数据库:精确匹配(WHERE column = value)
EmbeddingStore:相似性匹配(WHERE vector ≈ query_vector)

3. 性能优化目标:
传统数据库:优化磁盘I/O、事务处理
EmbeddingStore:优化高维空间距离计算、内存访问模式

4. 典型用例:
传统数据库:SELECT * FROM users WHERE age > 30
EmbeddingStore:查找与query_vector最相似的10个向量

技术实现对比:
– 传统数据库使用倒排索引加速文本搜索,但对向量无能为力
– EmbeddingStore使用近似最近邻(ANN)算法,牺牲部分精度换取搜索速度
– 混合系统(如ES)通过插件同时支持两种索引

Q2:在Milvus和Pinecone之间如何选择?

决策框架:

选择依据的多维度分析:

1. 运维能力:
– 有专业运维团队 → Milvus(自托管,控制权高)
– 无运维团队或想专注业务 → Pinecone(完全托管)

2. 成本考虑:
– 长期成本敏感 → Milvus(一次性投入,无持续费用)
– 短期或弹性需求 → Pinecone(按使用付费)

3. 数据规模和性能:
– 超大规模(亿级)→ Milvus(分布式架构)
– 中小规模 → Pinecone(Serverless自动扩缩)

4. 功能需求:
– 需要复杂过滤、多租户 → Milvus
– 需要快速上手、简单API → Pinecone

5. 合规要求:
– 数据必须本地部署 → Milvus
– 允许云服务 → Pinecone

实际案例对比:
╔════════════════════════════╦══════════════════════════════╗
║ Milvus ║ Pinecone ║
╠════════════════════════════╬══════════════════════════════╣
║ 部署:需要K8s和运维 ║ 部署:API调用即可 ║
║ 成本:基础设施+人力 ║ 成本:按向量数和使用量计费 ║
║ 扩展:手动分片和扩容 ║ 扩展:自动Serverless扩展 ║
║ 控制:完全控制所有组件 ║ 控制:仅API层面控制 ║
╚════════════════════════════╩══════════════════════════════╝

Q3:如何处理 EmbeddingStore 中的数据更新和版本控制?

解决方案:

三种主要策略:

1. 增量更新策略:

// 1. 为新数据生成新ID
String newId = generateVersionId(oldId, version);

// 2. 插入新版本
store.add(newId, newEmbedding, newMetadata);

// 3. 标记旧版本为过期
store.updateMetadata(oldId, Map.of(“active”, false));

// 4. 搜索时过滤过期版本
FilterCondition filter = FilterCondition.activeOnly();

2. 软删除 + 版本链:

class VersionedDocument {
String documentId; // 文档唯一ID
String versionId; // 版本ID
boolean isCurrent; // 是否当前版本
String previousVersion; // 前一个版本ID
Embedding embedding;
Metadata metadata;
}

3. 时间旅行查询:

// 查询特定时间点的数据
List vectors = store.searchAtTime(
queryEmbedding,
timestamp, // 查询的时间点
topK
);

性能优化技巧:
– 使用 delta 索引:仅对变更部分重建索引
– 分层存储:热数据内存,冷数据磁盘
– 异步更新:后台线程处理更新,不影响搜索性能

6.2 系统设计题

题目:设计一个支持十亿级向量的 EmbeddingStore 系统

架构设计:

public class BillionScaleEmbeddingStore {

/**
* 分层存储架构
*/

public class HierarchicalStorage {
// L0: 内存缓存(热点数据)
private ConcurrentHashMap<String, Vector> hotCache;

// L1: SSD 索引(近期数据)
private VectorIndex ssdIndex;

// L2: 分布式存储(全量数据)
private DistributedVectorStore distributedStore;

public List<SearchResult> search(float[] query, int k) {
// 1. 先在内存缓存搜索
List<SearchResult> results = searchCache(query, k);
if (results.size() >= k) {
return results;
}

// 2. 在SSD索引搜索
results.addAll(searchSSD(query, k results.size()));
if (results.size() >= k) {
return results;
}

// 3. 最后在分布式存储搜索
results.addAll(searchDistributed(query, k results.size()));

return results;
}
}

/**
* 分布式索引策略
*/

public class DistributedIndexing {
// 1. 基于向量ID的哈希分片
public int getShard(String vectorId, int totalShards) {
return Math.abs(vectorId.hashCode()) % totalShards;
}

// 2. 基于向量内容的聚类分片
public int getContentBasedShard(float[] vector, int totalShards) {
// 使用向量的前几个维度进行聚类
float[] reduced = reduceDimensions(vector, 2);
return kMeansAssign(reduced, clusterCenters);
}

// 3. 混合分片策略
public ShardInfo getHybridShard(String vectorId, float[] vector) {
// 先按内容分片,同一内容内的按哈希分片
int contentShard = getContentBasedShard(vector, 100);
int hashShard = getShard(vectorId, 10);
return new ShardInfo(contentShard, hashShard);
}
}

/**
* 查询路由优化
*/

public class QueryRouting {
// 1. 基于查询向量的路由
public List<Integer> routeQuery(float[] query, int totalShards) {
// 找出最相关的几个分片进行查询
List<ScoredShard> scoredShards = new ArrayList<>();

for (int i = 0; i < totalShards; i++) {
float[] shardCentroid = getShardCentroid(i);
float similarity = cosineSimilarity(query, shardCentroid);
scoredShards.add(new ScoredShard(i, similarity));
}

// 按相似度排序,选择top N个分片
return scoredShards.stream()
.sorted(Comparator.reverseOrder())
.limit(calculateProbeShards(totalShards))
.map(ScoredShard::getShardId)
.collect(Collectors.toList());
}

// 2. 自适应路由
public List<Integer> adaptiveRoute(float[] query,
QueryStatistics stats) {
// 基于历史查询模式动态调整
if (stats.isCacheHot(query)) {
return routeToCacheNodes(query);
} else if (stats.isDistributedQuery(query)) {
return routeToDistributedNodes(query);
} else {
return defaultRouting(query);
}
}
}

/**
* 索引压缩策略
*/

public class IndexCompression {
// 1. 乘积量化(PQ)
public class ProductQuantization {
private int m; // 子空间数量
private int k; // 每个子空间的聚类数

public CompressedVector compress(float[] vector) {
// 将向量分成m个子向量
float[][] subVectors = splitVector(vector, m);

// 对每个子向量进行量化
byte[] codes = new byte[m];
for (int i = 0; i < m; i++) {
codes[i] = quantize(subVectors[i], codebooks[i]);
}

return new CompressedVector(codes);
}
}

// 2. 标量量化(SQ)
public class ScalarQuantization {
public QuantizedVector quantize(float[] vector) {
// 计算向量各维度的统计信息
float min = min(vector);
float max = max(vector);

// 线性量化到8位整数
byte[] quantized = new byte[vector.length];
float scale = 255.0f / (max min);

for (int i = 0; i < vector.length; i++) {
quantized[i] = (byte) ((vector[i] min) * scale);
}

return new QuantizedVector(quantized, min, max);
}
}
}
}

七、未来趋势与演进

7.1 技术发展趋势

  • 统一查询语言:SQL-like 语法支持向量搜索
  • 硬件加速:GPU、TPU、专用AI芯片支持
  • 多模态融合:文本、图像、音频向量联合搜索
  • 实时机器学习:在线学习更新向量表示
  • 联邦学习支持:隐私保护的分布式向量学习
  • 7.2 新兴架构

    /**
    * 下一代 EmbeddingStore 架构
    */

    public class NextGenEmbeddingStore {

    /**
    * 智能路由层
    */

    public class IntelligentRouter {
    // 基于查询内容动态选择算法
    public SearchAlgorithm selectAlgorithm(QueryContext context) {
    if (context.isSemanticQuery()) {
    return new SemanticSearchAlgorithm();
    } else if (context.isKeywordQuery()) {
    return new KeywordSearchAlgorithm();
    } else if (context.isHybridQuery()) {
    return new HybridSearchAlgorithm();
    }
    return new DefaultAlgorithm();
    }
    }

    /**
    * 自适应索引
    */

    public class AdaptiveIndex {
    // 根据查询模式自动调整索引参数
    public void optimizeIndex(QueryPattern pattern) {
    if (pattern.isLatencySensitive()) {
    // 优化为低延迟模式
    adjustHNSWParameters(efConstruction: 100, efSearch: 50);
    } else if (pattern.isRecallSensitive()) {
    // 优化为高召回率模式
    adjustHNSWParameters(efConstruction: 200, efSearch: 100);
    }
    }
    }

    /**
    * 边缘计算集成
    */

    public class EdgeComputingIntegration {
    // 边缘节点缓存热点数据
    private Map<String, EdgeCache> edgeCaches;

    public SearchResult edgeAwareSearch(float[] query) {
    // 1. 在边缘节点搜索
    SearchResult edgeResult = searchEdgeCache(query);

    // 2. 如果边缘结果不足,回源到中心节点
    if (edgeResult.confidence < THRESHOLD) {
    edgeResult.merge(searchCentral(query));
    }

    return edgeResult;
    }
    }
    }

    通过以上详细分析,我们可以看到 EmbeddingStore 在现代 AI 应用中的核心地位。选择合适的 EmbeddingStore 需要综合考虑数据规模、性能要求、运维能力和成本预算等多个因素。随着 AI 技术的快速发展,EmbeddingStore 将继续演进,提供更强大、更智能的向量数据管理能力。

    赞(0)
    未经允许不得转载:171主机测评 » 高级java每日一道面试题-2025年7月01日-基础篇[LangChain4j]-EmbeddingStore 的作用是什么,有哪些主流实现?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址