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 的技术架构
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 技术发展趋势
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 将继续演进,提供更强大、更智能的向量数据管理能力。



