引言

在 AI 应用蓬勃发展的今天,向量数据库已成为处理非结构化数据(如图片、文本、音视频)的核心基础设施。面对市场上众多的向量数据库选项,如何根据自身技术栈和业务需求进行选型,是每个技术团队必须面对的课题。本文将聚焦于两款备受关注的向量数据库——MilvusQdrant,从 Java 开发者的视角,深入对比它们在 Java 生态中的集成方式、性能表现和适用场景,助你做出更明智的技术决策。

1. 核心特性概览

在深入集成细节前,我们先快速了解两款数据库的核心定位与特性。

1.1 Milvus:专为向量搜索而生的数据库

Milvus 是一个开源的向量数据库,设计初衷就是为海量向量数据提供高效的相似性搜索。其核心特点包括:

  • 云原生架构:支持分布式部署,具备良好的可扩展性。
  • 丰富的索引类型:支持 IVF_FLAT、IVF_SQ8、HNSW 等多种索引,适配不同精度与速度的需求。
  • 多向量与标量数据混合查询:支持在向量搜索的同时,结合标量字段进行过滤。
  • 活跃的社区与生态:由 Zilliz 公司主导,拥有庞大的用户群和丰富的客户端支持。

1.2 Qdrant:用 Rust 打造的高性能向量搜索引擎

Qdrant 是一个用 Rust 编写的向量搜索引擎和数据库,强调性能、可靠性和易用性。

  • 极致性能:得益于 Rust 语言的优势,在内存安全和执行效率方面表现突出。
  • 丰富的过滤条件:支持复杂的 payload(有效载荷)过滤,查询表达能力强大。
  • 内置的分布式支持:设计之初就考虑了横向扩展。
  • 简洁的 API:提供直观的 RESTful 和 gRPC API,易于集成。

2. Java 客户端与依赖集成

Java 生态的集成便利性是选型的关键因素之一。

2.1 Milvus Java SDK

Milvus 提供了官方的 Java SDK,可以通过 Maven 或 Gradle 方便地引入。

Maven 依赖:

<dependency>
    <groupId>io.milvus</groupId>
    <artifactId>milvus-sdk-java</artifactId>
    <version>2.3.4</version> <!-- 请使用最新稳定版本 -->
</dependency>

核心客户端初始化示例:

import io.milvus.client.MilvusServiceClient;
import io.milvus.param.ConnectParam;

public class MilvusClientDemo {
    public static void main(String[] args) {
        ConnectParam connectParam = ConnectParam.newBuilder()
            .withHost("localhost")
            .withPort(19530)
            .build();
        MilvusServiceClient milvusClient = new MilvusServiceClient(connectParam);
        // ... 后续操作
    }
}

2.2 Qdrant Java 客户端

Qdrant 社区提供了 qdrant-java 客户端,同样支持主流的构建工具。

Maven 依赖:

<dependency>
    <groupId>io.qdrant</groupId>
    <artifactId>client</artifactId>
    <version>1.7.0</version> <!-- 请使用最新稳定版本 -->
</dependency>

核心客户端初始化示例:

import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public class QdrantClientDemo {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 6334)
            .usePlaintext()
            .build();
        QdrantGrpcClient grpcClient = new QdrantGrpcClient(channel);
        QdrantClient client = new QdrantClient(grpcClient);
        // ... 后续操作
    }
}

3. 核心操作对比

我们通过创建集合(Collection)、插入数据、执行搜索这三个核心操作来直观感受两者的 API 设计差异。

3.1 创建集合/点集合

Milvus 创建集合:

// 1. 定义字段模式
FieldType fieldType1 = FieldType.newBuilder()
        .withName("id")
        .withDataType(DataType.Int64)
        .withPrimaryKey(true)
        .withAutoID(false)
        .build();
FieldType fieldType2 = FieldType.newBuilder()
        .withName("vector")
        .withDataType(DataType.FloatVector)
        .withDimension(128) // 向量维度
        .build();
// 2. 创建集合
CreateCollectionParam createCollectionReq = CreateCollectionParam.newBuilder()
        .withCollectionName("my_collection")
        .withDescription("测试集合")
        .withShardsNum(2)
        .addFieldType(fieldType1)
        .addFieldType(fieldType2)
        .build();
milvusClient.createCollection(createCollectionReq);

Qdrant 创建点集合:

import io.qdrant.client.grpc.Collections;

// 定义向量参数
Collections.VectorParams vectorParams = Collections.VectorParams.newBuilder()
        .setSize(128) // 向量维度
        .setDistance(Collections.Distance.Cosine) // 距离度量方式
        .build();
// 创建集合
Collections.CreateCollection createCollection = Collections.CreateCollection.newBuilder()
        .setCollectionName("my_collection")
        .setVectorsConfig(Collections.VectorsConfig.newBuilder()
                .setParams(vectorParams)
                .build())
        .build();
client.createCollection(createCollection).get();

3.2 插入向量数据

Milvus 插入数据:

List<Long> ids = Arrays.asList(1L, 2L);
List<List<Float>> vectors = Arrays.asList(
    Arrays.asList(0.1f, 0.2f, ...), // 128维向量
    Arrays.asList(0.3f, 0.4f, ...)
);
List<InsertParam.Field> fields = new ArrayList<>();
fields.add(new InsertParam.Field("id", ids));
fields.add(new InsertParam.Field("vector", vectors));

InsertParam insertParam = InsertParam.newBuilder()
        .withCollectionName("my_collection")
        .withFields(fields)
        .build();
milvusClient.insert(insertParam);

Qdrant 插入数据:

import io.qdrant.client.grpc.Points;

List<Float> vector1 = Arrays.asList(0.1f, 0.2f, ...);
List<Float> vector2 = Arrays.asList(0.3f, 0.4f, ...);

Points.PointStruct point1 = Points.PointStruct.newBuilder()
        .setId(Points.PointId.newBuilder().setNum(1).build())
        .setVectors(Points.Vectors.newBuilder()
                .addAllVector(vector1).build())
        .build();
// ... 构建 point2

client.upsert(Points.UpsertPoints.newBuilder()
        .setCollectionName("my_collection")
        .addPoints(point1)
        .addPoints(point2)
        .build()).get();

3.3 向量相似性搜索

Milvus 搜索:

List<List<Float>> searchVectors = Arrays.asList(
    Arrays.asList(0.15f, 0.25f, ...) // 查询向量
);
List<String> outputFields = Arrays.asList("id");
SearchParam searchParam = SearchParam.newBuilder()
        .withCollectionName("my_collection")
        .withVectorFieldName("vector")
        .withVectors(searchVectors)
        .withTopK(10)
        .withMetricType(MetricType.IP) // 或 L2, COSINE
        .withParams("{\"nprobe\":10}")
        .withOutFields(outputFields)
        .build();
SearchResults resp = milvusClient.search(searchParam);

Qdrant 搜索:

import io.qdrant.client.grpc.Points;

List<Float> queryVector = Arrays.asList(0.15f, 0.25f, ...);
Points.SearchPoints searchRequest = Points.SearchPoints.newBuilder()
        .setCollectionName("my_collection")
        .setVector(Points.Vector.newBuilder()
                .addAllData(queryVector).build())
        .setLimit(10)
        .setWithVectors(true) // 是否返回向量
        .build();

Points.SearchResponse response = client.search(searchRequest).get();

4. 性能与生态考量

4.1 性能特点

  • Milvus:在超大规模数据集(亿级以上)和复杂混合查询场景下经过大量实践验证。其分布式架构更适合需要弹性伸缩的企业级应用。
  • Qdrant:单节点性能优异,延迟低,资源利用率高。对于中小规模数据集或对延迟极其敏感的场景(如实时推荐),往往有出色表现。

4.2 Java 生态融合度

  • Milvus:SDK 功能全面,与 Spring Boot 等框架集成示例丰富。其“数据库”的定位使得它在需要持久化、事务性(尽管有限)概念的系统中更易被理解。
  • Qdrant:客户端 API 设计现代、简洁,更符合微服务架构下服务调用的直觉。gRPC 接口在云原生环境中具有天然优势。

4.3 运维与部署

  • Milvus:部署相对复杂,涉及多个组件(MinIO, Etcd, Pulsar等),但 Kubernetes 生态的 Helm Chart 和 Operator 降低了运维门槛。
  • Qdrant:部署非常简单,一个二进制文件或 Docker 容器即可运行。云托管服务(Qdrant Cloud)也让起步变得极其容易。

5. 选型建议

如何选择?这里提供一个简单的决策参考:

选择 Milvus,如果你需要:

  • 处理 海量数据(十亿级以上向量)。
  • 进行复杂的 多模态混合查询(向量+标量)。
  • 项目技术栈偏传统大数据或数据库体系。
  • 需要非常丰富的索引类型来调优搜索精度与速度。

选择 Qdrant,如果你需要:

  • 极致的单机性能低延迟
  • 快速原型验证或启动项目,追求 简单的部署和运维
  • 强大的 过滤查询 能力。
  • 对 Rust 技术栈有好感,或计划深入定制。

结语

Milvus 和 Qdrant 都是优秀的向量数据库,没有绝对的优劣,只有是否适合。对于 Java 团队而言,两者都提供了成熟的客户端,集成成本相当。建议在技术选型初期,根据业务的数据规模、性能指标和团队技术偏好,用实际业务数据进行 POC(概念验证)测试,用数据说话,从而找到最适合你当前场景的向量数据解决方案。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐