共计 2705 个字符,预计需要花费 7 分钟才能阅读完成。
为什么需要向量数据库?
在构建 AI 应用时,我们经常需要处理非结构化数据(如图片、音频、文本),这些数据经过嵌入模型处理后变成高维向量。传统的关系型数据库在设计上并不擅长处理这类数据:

- 查询效率低下:用 SQL 实现相似度计算需要全表扫描,时间复杂度 O(n)
- 存储结构不适配 :B+ 树索引对向量的最近邻搜索(NNS) 支持有限
- 扩展性差:垂直扩容成本高,难以应对 AI 场景的数据增长
向量数据库通过以下特性解决这些问题:
- 专用索引结构(如 HNSW、IVF-PQ)实现亚线性的近似搜索
- 原生支持欧式距离、余弦相似度等度量方式
- 分布式架构实现水平扩展
技术选型对比
| 特性 | ChromaDB | Faiss | Milvus |
|---|---|---|---|
| 写入 QPS | 3k-5k | 无服务层 | 10k+ |
| 查询延迟 | 15-50ms | <5ms | 20-100ms |
| 内存占用 | 低(Go 实现) | 极低 | 较高 |
| 学习曲线 | 简单 | 陡峭 | 中等 |
| 云托管支持 | 无 | 无 | 有 |
ChromaDB 的优势在于:
- 内置 HTTP/gRPC 接口,无需额外开发服务层
- 支持多租户和集合 (Collection) 隔离
- 活跃的 Python 生态,Java SDK 正在完善
环境准备
项目初始化
-
创建 SpringBoot 2.7 项目(Java 11+):
spring init --dependencies=web,lombok -g=com.example -a=chroma-demo -
添加 Chroma Java 客户端依赖(需手动安装到本地仓库):
<dependency> <groupId>org.pinecone</groupId> <artifactId>chroma-java</artifactId> <version>0.3.1</version> </dependency>
Docker 部署 ChromaDB
创建docker-compose.yml:
version: '3.8'
services:
chroma:
image: chromadb/chroma
ports:
- "8000:8000"
environment:
- CHROMA_SERVER_HOST=0.0.0.0
volumes:
- chroma_data:/chroma
volumes:
chroma_data:
启动服务:
docker-compose up -d
核心实现
连接池配置
@Configuration
public class ChromaConfig {
@Bean
public ChromaClient chromaClient() {return new ChromaClient.Builder()
.baseUrl("http://localhost:8000")
.connectTimeout(Duration.ofSeconds(5))
.readTimeout(Duration.ofSeconds(30))
.maxConnections(50) // 根据 QPS 调整
.build();}
}
向量操作示例
插入数据
@Data
@Builder
public class ArticleVector {
@NonNull private String id;
@NonNull private float[] embedding;
private String title;
private Map<String, String> metadata;
}
@Service
@RequiredArgsConstructor
public class VectorService {
private final ChromaClient client;
public void batchInsert(List<ArticleVector> vectors) {Collection collection = client.getOrCreateCollection("news");
List<String> ids = vectors.stream()
.map(ArticleVector::getId)
.collect(Collectors.toList());
List<float[]> embeddings = vectors.stream()
.map(ArticleVector::getEmbedding)
.collect(Collectors.toList());
collection.add(
ids,
embeddings,
vectors.stream().map(v -> v.getMetadata()).collect(Collectors.toList())
);
}
}
相似度搜索
public List<QueryResult> search(float[] queryEmbedding, int topK) {Collection collection = client.getCollection("news");
return collection.query()
.queryEmbeddings(List.of(queryEmbedding))
.nResults(topK)
.include(List.of("metadatas", "documents"))
.execute()
.getResults();}
性能优化
批量写入分片
// 每批 500 个向量,并行写入
List<List<ArticleVector>> batches = Lists.partition(vectors, 500);
batches.parallelStream().forEach(batch -> {
try {batchInsert(batch);
} catch (Exception e) {log.error("Batch insert failed", e);
}
});
ANN 参数调优
collection.updateHnswParameters(HnswParameters.builder()
.efConstruction(200) // 影响构建质量
.m(16) // 影响内存占用
.build());
生产环境注意事项
-
维度校验:插入前检查向量长度
if (embedding.length != 768) {throw new InvalidDimensionException("Embedding must be 768-dimensional"); } -
索引重建:采用蓝绿部署
旧集群(v1)← 流量 → 新集群(v2) -
TLS 配置:
chroma: ssl: key-store: classpath:keystore.p12 key-store-password: changeit
思考题
- 如何设计基于一致性哈希的跨集群分片方案?
- 当查询 QPS>1000 时,怎样利用 Spring Cache 减少数据库压力?
- 对比测试 gRPC 和 REST 接口在不同负载下的吞吐量差异
正文完
发表至: 未分类
近两天内
