Milvus向量数据库

官网:https://milvus.io/zh

核心概念

  • Collection:Collection 是 Milvus 中数据组织的基本单位,对应 MySQL 中的
  • Schema:定义了 Collection 中每条数据包含哪些字段,对应 MySQL 中的表结构
  • Index:包含为向量创建的ANN索引、为标量创建的索引
  • Partition:Collection 内部的逻辑子集,可以按某个业务维度把数据分到不同的 Partition 里。比如按文档类别分区:退货政策放一个 Partition,物流规则放一个 Partition,促销活动放一个 Partition
  • Entity:一条数据

安装

Docker

Milvus依赖两个外部组件:一个对象存储(用来存索引文件和日志)和一个 etcd(用来存元数据)

  1. docker-compose.yml文件

    rustfs:替代 Milvus 默认的 MinIO,作为底层对象存储,持久化保存所有向量数据、索引文件、数据段文件

    etcd:作为Milvus 的元数据中心,保存集合 Schema、索引配置、节点状态、任务调度信息等

    attuMilvus 官方的 Web 管理控制台

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    name: milvus-stack

    services:
    rustfs:
    container_name: rustfs
    image: rustfs/rustfs:1.0.0-alpha.72
    command:
    - "--address"
    - ":9000"
    - "--console-enable"
    - "--access-key"
    - "rustfsadmin"
    - "--secret-key"
    - "rustfsadmin"
    - "/data"
    environment:
    - RUSTFS_ACCESS_KEY=rustfsadmin
    - RUSTFS_SECRET_KEY=rustfsadmin
    - RUSTFS_CONSOLE_ENABLE=true
    ports:
    - "9000:9000"
    - "9001:9001"
    volumes:
    - rustfs-data:/data
    healthcheck:
    test: ["CMD", "sh", "-c", "wget -qO- http://localhost:9000/ || exit 1"]
    interval: 30s
    timeout: 10s
    retries: 5

    etcd:
    container_name: etcd
    image: quay.io/coreos/etcd:v3.5.18
    environment:
    - ETCD_AUTO_COMPACTION_MODE=revision
    - ETCD_AUTO_COMPACTION_RETENTION=1000
    - ETCD_QUOTA_BACKEND_BYTES=4294967296
    - ETCD_SNAPSHOT_COUNT=50000
    command: >
    etcd
    -advertise-client-urls=http://etcd:2379
    -listen-client-urls http://0.0.0.0:2379
    --data-dir /etcd
    volumes:
    - etcd-data:/etcd
    healthcheck:
    test: ["CMD", "etcdctl", "endpoint", "health"]
    interval: 30s
    timeout: 20s
    retries: 3

    standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v2.6.6
    command: ["milvus", "run", "standalone"]
    security_opt:
    - seccomp:unconfined
    environment:
    ETCD_ENDPOINTS: etcd:2379
    MINIO_ADDRESS: rustfs:9000
    MINIO_ACCESS_KEY_ID: rustfsadmin
    MINIO_SECRET_ACCESS_KEY: rustfsadmin
    volumes:
    - milvus-data:/var/lib/milvus
    ports:
    - "19530:19530"
    - "9091:9091"
    depends_on:
    - etcd
    - rustfs
    healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
    interval: 30s
    start_period: 90s
    timeout: 20s
    retries: 3

    attu:
    container_name: milvus-attu
    image: zilliz/attu:v2.6.3
    environment:
    MILVUS_URL: milvus-standalone:19530
    ports:
    - "8000:3000"
    depends_on:
    - standalone

    volumes:
    rustfs-data:
    etcd-data:
    milvus-data:

    networks:
    default:
    name: milvus-net
  2. 各组件作用

    组件 作用 端口
    rustfs 对象存储,存储 Milvus 的索引文件和日志 9000(API)、9001(控制台)
    etcd 元数据存储,管理 Milvus 的集群元信息 2379
    standalone Milvus 单机版服务 19530(gRPC)、9091(健康检查)
    attu Milvus 可视化管理界面 8000
  3. 执行docker compose up -d命令

  4. 访问http://localhost:8000/控制台,默认不要账号密码

使用

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<!-- Milvus Java SDK -->
<dependency>
<groupId>io.milvus</groupId>
<artifactId>milvus-sdk-java</artifactId>
<version>2.6.6</version>
</dependency>

<!-- OkHttp,用于调用 SiliconFlow Embedding API -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>

<!-- JSON 处理 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.1</version>
</dependency>
</dependencies>

创建Collection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 向量维度,和 Embedding 模型保持一致(Qwen3-Embedding-8B 输出 4096 维)
private static final int VECTOR_DIM = 4096;
private static final String COLLECTION_NAME = "customer_service_chunks";

public static void main(String[] args) {
// 1. 连接 Milvus
ConnectConfig connectConfig = ConnectConfig.builder()
.uri("http://localhost:19530")
.build();
MilvusClientV2 client = new MilvusClientV2(connectConfig);
System.out.println("已连接到 Milvus");

// 2. 定义 Schema
CreateCollectionReq.CollectionSchema schema = client.createSchema();

// 主键字段:自增 ID
schema.addField(AddFieldReq.builder()
.fieldName("id")
.dataType(DataType.Int64)
.isPrimaryKey(true)
.autoID(true)
.build());

// 向量字段:存储 Embedding 向量
schema.addField(AddFieldReq.builder()
.fieldName("vector")
.dataType(DataType.FloatVector)
.dimension(VECTOR_DIM)
.build());

// 标量字段:chunk 原文
schema.addField(AddFieldReq.builder()
.fieldName("chunk_text")
.dataType(DataType.VarChar)
.maxLength(8192)
.build());

// 标量字段:文档 ID(标识这个 chunk 来自哪个文档)
schema.addField(AddFieldReq.builder()
.fieldName("doc_id")
.dataType(DataType.VarChar)
.maxLength(64)
.build());

// 标量字段:分类(退货政策、物流规则、促销活动等)
schema.addField(AddFieldReq.builder()
.fieldName("category")
.dataType(DataType.VarChar)
.maxLength(32)
.build());

// 3. 创建 Collection
CreateCollectionReq createCollectionReq = CreateCollectionReq.builder()
.collectionName(COLLECTION_NAME)
.collectionSchema(schema)
.build();
client.createCollection(createCollectionReq);
System.out.println("Collection 创建成功:" + COLLECTION_NAME);
}

插入数据

注意,在没有创建索引前,虽然该单元测试显示插入成功,但是 Milvus 控制台查看依然是 0 条数据。只有在创建索引且加载 Collection 到内存才会正常展示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
private static final String SILICONFLOW_API_KEY = "硅基流动的 api key";
private static final String EMBEDDING_URL = "https://api.siliconflow.cn/v1/embeddings";
private static final String EMBEDDING_MODEL = "Qwen/Qwen3-Embedding-8B";
private static final Gson GSON = new Gson();
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient();

public static void main(String[] args) throws IOException {
// 连接 Milvus
ConnectConfig connectConfig = ConnectConfig.builder()
.uri("http://localhost:19530")
.build();
MilvusClientV2 client = new MilvusClientV2(connectConfig);

// 模拟电商客服知识库的 chunk 数据
List<String> chunkTexts = List.of(
"退货政策:自签收之日起 7 天内,商品未拆封、不影响二次销售的情况下,支持无理由退货。退货运费由买家承担,质量问题除外。",
"退货政策:生鲜食品、定制商品、贴身衣物等特殊商品不支持无理由退货。如有质量问题,请在签收后 48 小时内联系客服并提供照片凭证。",
"物流规则:普通商品下单后 48 小时内发货,预售商品以商品详情页标注的发货时间为准。偏远地区(新疆、西藏、青海等)可能需要额外 2~3 天。",
"物流规则:支持顺丰、中通、圆通、韵达等主流快递。默认使用中通快递,如需指定快递公司,请在下单时备注,可能产生额外运费。",
"促销活动:2026 年春节大促,全场满 300 减 50,满 500 减 100。活动时间:2026 年 1 月 20 日至 2 月 5 日。优惠券不可叠加使用。"
);
List<String> docIds = List.of("doc_return_001", "doc_return_001", "doc_logistics_001", "doc_logistics_001", "doc_promo_001");
List<String> categories = List.of("return_policy", "return_policy", "logistics", "logistics", "promotion");

// 调用 Embedding API 生成向量
List<List<Float>> vectors = getEmbeddings(chunkTexts);

// 组装插入数据
List<JsonObject> rows = new ArrayList<>();
for (int i = 0; i < chunkTexts.size(); i++) {
JsonObject row = new JsonObject();
row.addProperty("chunk_text", chunkTexts.get(i));
row.addProperty("doc_id", docIds.get(i));
row.addProperty("category", categories.get(i));
row.add("vector", GSON.toJsonTree(vectors.get(i)));
rows.add(row);
}

// 插入 Milvus
InsertReq insertReq = InsertReq.builder()
.collectionName("customer_service_chunks")
.data(rows)
.build();
InsertResp insertResp = client.insert(insertReq);
System.out.println("插入成功,数量:" + insertResp.getInsertCnt());
}

/**
* 调用 SiliconFlow Embedding API,批量生成向量
*/
private static List<List<Float>> getEmbeddings(List<String> texts) throws IOException {
JsonObject requestBody = new JsonObject();
requestBody.addProperty("model", EMBEDDING_MODEL);
requestBody.add("input", GSON.toJsonTree(texts));

Request request = new Request.Builder()
.url(EMBEDDING_URL)
.addHeader("Authorization", "Bearer " + SILICONFLOW_API_KEY)
.addHeader("Content-Type", "application/json")
.post(RequestBody.create(GSON.toJson(requestBody),
MediaType.parse("application/json")))
.build();

try (Response response = HTTP_CLIENT.newCall(request).execute()) {
String body = response.body().string();
JsonObject json = GSON.fromJson(body, JsonObject.class);
JsonArray dataArray = json.getAsJsonArray("data");

List<List<Float>> vectors = new ArrayList<>();
for (int i = 0; i < dataArray.size(); i++) {
JsonArray embeddingArray = dataArray.get(i).getAsJsonObject()
.getAsJsonArray("embedding");
List<Float> vector = new ArrayList<>();
for (int j = 0; j < embeddingArray.size(); j++) {
vector.add(embeddingArray.get(j).getAsFloat());
}
vectors.add(vector);
}
return vectors;
}
}

创建索引

没有索引的话,Milvus 只能做暴力搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private static void createIndex(MilvusClientV2 client) {
// 为向量字段创建 HNSW 索引
IndexParam vectorIndex = IndexParam.builder()
.fieldName("vector")
.indexType(IndexParam.IndexType.HNSW)
.metricType(IndexParam.MetricType.COSINE) // 余弦相似度
.extraParams(Map.of(
"M", 16, // 每个向量的最大连接数
"efConstruction", 256 // 建索引时的搜索宽度
))
.build();

// 为 category 标量字段创建索引(加速过滤查询)
IndexParam categoryIndex = IndexParam.builder()
.fieldName("category")
.indexType(IndexParam.IndexType.TRIE) // 字符串类型用 Trie 索引
.build();

CreateIndexReq createIndexReq = CreateIndexReq.builder()
.collectionName("customer_service_chunks")
.indexParams(List.of(vectorIndex, categoryIndex))
.build();
client.createIndex(createIndexReq);
System.out.println("索引创建成功");
}
1
2
3
4
5
// 还需要将数据和索引加载到内存才能进行检索
client.loadCollection(LoadCollectionReq.builder()
.collectionName("customer_service_chunks")
.build());
System.out.println("Collection 已加载到内存");

检索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private static void search(MilvusClientV2 client) throws IOException {
// 用户的问题
String query = "买了东西不想要了怎么退货?";

// 把问题向量化(复用前面的 getEmbeddings 方法)
List<List<Float>> queryVectors = getEmbeddings(List.of(query));

List<BaseVector> milvusQueryVectors = queryVectors.stream()
.map(FloatVec::new) // FloatVec(List<Float>)
.collect(java.util.stream.Collectors.toList());

// 执行向量检索
SearchReq searchReq = SearchReq.builder()
.collectionName("customer_service_chunks")
.data(milvusQueryVectors) // 查询向量
.topK(3) // 返回最相似的 3 个结果
.outputFields(List.of("chunk_text", "doc_id", "category")) // 需要返回的字段
.annsField("vector") // 指定在哪个向量字段上检索
.searchParams(Map.of("ef", 128)) // HNSW 检索时的搜索宽度
.build();

SearchResp searchResp = client.search(searchReq);

// 输出检索结果
List<List<SearchResp.SearchResult>> results = searchResp.getSearchResults();
for (List<SearchResp.SearchResult> resultList : results) {
System.out.println("=== 检索结果 ===");
for (int i = 0; i < resultList.size(); i++) {
SearchResp.SearchResult result = resultList.get(i);
System.out.println("Top-" + (i + 1) + ":");
System.out.println(" 相似度分数:" + result.getScore());
System.out.println(" 分类:" + result.getEntity().get("category"));
System.out.println(" 文档ID:" + result.getEntity().get("doc_id"));
System.out.println(" 内容:" + result.getEntity().get("chunk_text"));
System.out.println();
}
}
}

条件检索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private static void search(MilvusClientV2 client) throws IOException {
// 用户的问题
String query = "买了东西不想要了怎么退货?";

// 把问题向量化(复用前面的 getEmbeddings 方法)
List<List<Float>> queryVectors = getEmbeddings(List.of(query));

List<BaseVector> milvusQueryVectors = queryVectors.stream()
.map(FloatVec::new) // FloatVec(List<Float>)
.collect(java.util.stream.Collectors.toList());

// 执行向量检索
// 混合检索:向量相似度 + 标量过滤
// 只在退货政策类的 chunk 里检索
SearchReq filteredSearchReq = SearchReq.builder()
.collectionName("customer_service_chunks")
.data(milvusQueryVectors)
.topK(3)
.outputFields(List.of("chunk_text", "doc_id", "category"))
.annsField("vector")
.filter("category == \"return_policy\"") // 只搜索退货政策类
.searchParams(Map.of("ef", 128))
.build();

SearchResp filteredResp = client.search(filteredSearchReq);

// 输出过滤后的结果
List<List<SearchResp.SearchResult>> filteredResults = filteredResp.getSearchResults();
for (List<SearchResp.SearchResult> resultList : filteredResults) {
System.out.println("=== 过滤检索结果(仅退货政策) ===");
for (int i = 0; i < resultList.size(); i++) {
SearchResp.SearchResult result = resultList.get(i);
System.out.println("Top-" + (i + 1) + ":");
System.out.println(" 相似度分数:" + result.getScore());
System.out.println(" 内容:" + result.getEntity().get("chunk_text"));
System.out.println();
}
}
}

Milvus向量数据库
http://xwww12.github.io/2026/06/27/数据库/Milvus向量数据库/
作者
xw
发布于
2026年6月27日
许可协议