
本文详解 Qdrant 向量数据库在 Golang 项目中的实战应用,涵盖环境部署、SDK 接入、集合管理、向量增删改查、混合检索及过滤查询,附带报错处理与性能优化技巧,助你快速落地 AI 搜索与推荐系统。
为什么选择 Qdrant
在 AI 原生应用爆发的今天,向量数据库已成为大模型 RAG(检索增强生成)、推荐系统、图像检索等场景的核心基础设施。在众多向量数据库中,Qdrant (read: quadrant) 凭借以下优势脱颖而出:
- 高性能:使用 Rust 编写,内存利用率极高,在过滤和检索性能上表现优异。
- 易用性:提供了直观的 REST API 和 gRPC 接口,且支持丰富的过滤条件。
- 功能强大:支持混合检索(向量+关键词过滤)、动态 Payload 管理、分片与复制。
- 云原生 :支持 Kubernetes 部署,资源控制灵活。 本文将抛开枯燥的理论,直接通过 Golang 代码,带你从零实现一个基于 Qdrant 的向量检索系统。
环境准备
在开始编码前,我们需要准备好 Qdrant 服务和 Go 开发环境。
部署 Qdrant 服务
最快捷的方式是使用 Docker 启动一个单机实例:
创建 docker-compose.yaml 配置文件:
yaml
services:
qdrant:
image: qdrant/qdrant:latest
restart: always
container_name: qdrant
ports:
- 6333:6333
- 6334:6334
environment:
QDRANT__SERVICE__API_KEY: "password"
expose:
- 6333
- 6334
- 6335
configs:
- source: qdrant_config
target: /qdrant/config/production.yaml
volumes:
- ./data:/qdrant/storage
configs:
qdrant_config:
content: |
log_level: INFO
使用 docker compose up 启动服务
- 6333 端口:HTTP API 接口(用于 Web UI 或 HTTP 调用)。
- 6334 端口 :gRPC API 接口(Golang SDK 主要使用此端口,性能更高)。 启动后,访问
http://localhost:6333/dashboard即可看到 Web 管理界面。
安装 Golang SDK
Qdrant 官方提供了维护良好的 Go 客户端。在项目中初始化并安装 SDK:
bash
go mod init qdrant-demo
go get github.com/qdrant/go-client/qdrant
Golang 客户端连接与初始化
首先,我们需要建立与 Qdrant 的连接。Qdrant Go SDK 基于 gRPC,因此连接配置非常简单。 代码示例:初始化客户端
go
package main
import (
"fmt"
"log"
"github.com/qdrant/go-client/qdrant"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
// 1. 创建 gRPC 连接
conn, err := grpc.Dial("localhost:6334", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
if err != nil {
log.Fatalf("failed to connect to Qdrant: %v", err)
}
defer conn.Close()
// 2. 初始化 Qdrant Client
qdrant.NewCollectionsClient(conn)
// 后续代码将在这里展开...
fmt.Println("Qdrant client connected successfully!")
}
注意 :在生产环境中,建议将 grpc.WithBlock() 去掉以实现异步连接,并添加重试机制。
核心功能实战
我们将实现一个简单的"文章语义搜索"场景。假设我们存储文章的向量(由 Embedding 模型生成)以及文章的元数据(如分类、标题、URL)。
创建集合
在 Qdrant 中,数据存储在 Collection(集合)中。创建集合时必须指定向量维度。 代码示例:创建集合 articles
go
func createCollection(ctx context.Context, client qdrant.CollectionsClient) error {
req := &qdrant.CreateCollection{
CollectionName: "articles",
// 768 维向量模型 (如 BAAI/bge-base-zh),使用余弦相似度
VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
Size: 768,
Distance: qdrant.Distance_Cosine,
HnswConfig: &qdrant.HnswConfigDiff{
M: qdrant.PtrOf(uint64(16)), // HNSW 图的连接度,影响召回率与速度
EfConstruct: qdrant.PtrOf(uint64(100)), // 构建索引时的搜索范围
},
}),
OptimizersConfig: &qdrant.OptimizersConfigDiff{
IndexingThreshold: qdrant.PtrOf(uint64(10000)), // 数据量达到该值时开始索引
},
}
_, err := client.Create(ctx, req)
if err != nil {
// 集合已存在时直接跳过,保证脚本可重复执行
if strings.Contains(err.Error(), "already exists") {
fmt.Println("Collection 'articles' already exists, skip creation.")
return nil
}
return fmt.Errorf("failed to create collection: %w", err)
}
fmt.Println("Collection 'articles' created successfully.")
return nil
}
向量数据插入
Qdrant 不仅仅是存向量,还能存 Payload(结构化数据)。这是 Qdrant 相比纯向量库的一大优势,允许我们在搜索时进行精细过滤。 代码示例:插入数据
go
func upsertPoints(ctx context.Context, client qdrant.PointsClient) error {
points := []*qdrant.PointStruct{
{
Id: &qdrant.PointId{PointIdOptions: &qdrant.PointId_Num{Num: 1}},
Vectors: qdrant.NewVectorsDense(generateFakeVector(768)), // 模拟向量
Payload: map[string]*qdrant.Value{
"title": {Kind: &qdrant.Value_StringValue{StringValue: "Go语言最佳实践"}},
"category": {Kind: &qdrant.Value_StringValue{StringValue: "tech"}},
"views": {Kind: &qdrant.Value_IntegerValue{IntegerValue: 1000}},
},
},
{
Id: &qdrant.PointId{PointIdOptions: &qdrant.PointId_Num{Num: 2}},
Vectors: qdrant.NewVectorsDense(generateFakeVector(768)), // 模拟向量
Payload: map[string]*qdrant.Value{
"title": {Kind: &qdrant.Value_StringValue{StringValue: "深度学习入门指南"}},
"category": {Kind: &qdrant.Value_StringValue{StringValue: "ai"}},
"views": {Kind: &qdrant.Value_IntegerValue{IntegerValue: 5000}},
},
},
}
req := &qdrant.UpsertPoints{
CollectionName: "articles",
Points: points,
}
_, err := client.Upsert(ctx, req)
if err != nil {
return fmt.Errorf("failed to upsert points: %w", err)
}
fmt.Println("Points upserted successfully.")
return nil
}
// 辅助函数:生成模拟向量
func generateFakeVector(size int) []float32 {
vec := make([]float32, size)
for i := range vec {
vec[i] = 0.1 // 仅作演示,实际应使用 Embedding 模型
}
return vec
}
相似度检索
这是最核心的功能:给定向量,返回最相似的 K 个结果。 代码示例:基础搜索
go
func searchPoints(ctx context.Context, client qdrant.PointsClient) error {
queryVector := generateFakeVector(768) // 模拟查询向量
req := &qdrant.SearchPoints{
CollectionName: "articles",
Vector: queryVector,
Limit: uint64(3), // 返回 Top 3
WithPayload: &qdrant.WithPayloadSelector{SelectorOptions: &qdrant.WithPayloadSelector_Enable{Enable: true}}, // 返回 Payload
}
resp, err := client.Search(ctx, req)
if err != nil {
return fmt.Errorf("failed to search points: %w", err)
}
fmt.Println("Search Results:")
for _, result := range resp.Result {
fmt.Printf("ID: %d, Score: %.4f, Title: %s\n",
result.Id.GetNum(),
result.Score,
result.Payload["title"].GetStringValue())
}
return nil
}
条件过滤检索
实战中,我们经常需要"既相似,又满足特定条件"的数据。例如:"查找关于'Go语言'的文章,且阅读量大于 500"。这就是 Qdrant 的 Filter 功能。 代码示例:带过滤条件的搜索
go
func searchWithFilter(ctx context.Context, client qdrant.PointsClient) error {
queryVector := generateFakeVector(768)
// 构建过滤条件:Category 必须是 'tech' 且 Views 大于 500
req := &qdrant.SearchPoints{
CollectionName: "articles",
Vector: queryVector,
Limit: uint64(10),
WithPayload: &qdrant.WithPayloadSelector{SelectorOptions: &qdrant.WithPayloadSelector_Enable{Enable: true}}, // 返回 Payload
Filter: &qdrant.Filter{
Must: []*qdrant.Condition{
// 条件1:Category 等于 'tech'
{
ConditionOneOf: &qdrant.Condition_Field{
Field: &qdrant.FieldCondition{
Key: "category",
Match: &qdrant.Match{
MatchValue: &qdrant.Match_Keyword{Keyword: "tech"},
},
},
},
},
// 条件2:Views 大于 500
{
ConditionOneOf: &qdrant.Condition_Field{
Field: &qdrant.FieldCondition{
Key: "views",
Range: &qdrant.Range{
Gt: qdrant.PtrOf(float64(500)),
},
},
},
},
},
},
}
resp, err := client.Search(ctx, req)
if err != nil {
return fmt.Errorf("failed to search with filter: %w", err)
}
fmt.Println("Filtered Search Results (Category=tech, Views>500):")
for _, result := range resp.Result {
fmt.Printf("ID: %d, Score: %.4f, Title: %s, Views: %d\n",
result.Id.GetNum(),
result.Score,
result.Payload["title"].GetStringValue(),
result.Payload["views"].GetIntegerValue())
}
return nil
}
数据更新与删除
业务数据是动态变化的,Qdrant 支持对 Payload 和向量进行更新,以及删除特定 ID 的数据。 代码示例:更新与删除
go
func updateAndDelete(ctx context.Context, client qdrant.PointsClient) error {
// 1. 更新 Payload (将 ID 为 1 的文章阅读量改为 9999)
setPayloadReq := &qdrant.SetPayloadPoints{
CollectionName: "articles",
Payload: map[string]*qdrant.Value{
"views": {Kind: &qdrant.Value_IntegerValue{IntegerValue: 9999}},
},
PointsSelector: &qdrant.PointsSelector{
PointsSelectorOneOf: &qdrant.PointsSelector_Points{Points: &qdrant.PointsIdsList{
Ids: []*qdrant.PointId{{PointIdOptions: &qdrant.PointId_Num{Num: 1}}},
}},
},
}
_, err := client.SetPayload(ctx, setPayloadReq)
if err != nil {
return fmt.Errorf("failed to update payload: %w", err)
}
fmt.Println("Payload updated for ID 1.")
// 2. 删除数据 (删除 ID 为 2 的数据)
deleteReq := &qdrant.DeletePoints{
CollectionName: "articles",
Points: &qdrant.PointsSelector{
PointsSelectorOneOf: &qdrant.PointsSelector_Points{Points: &qdrant.PointsIdsList{
Ids: []*qdrant.PointId{{PointIdOptions: &qdrant.PointId_Num{Num: 2}}},
}},
},
}
_, err = client.Delete(ctx, deleteReq)
if err != nil {
return fmt.Errorf("failed to delete point: %w", err)
}
fmt.Println("Point ID 2 deleted.")
return nil
}
性能优化技巧
为了让 Qdrant 在生产环境跑得更快,请注意以下几点:
批量插入
不要逐条调用 Upsert。Qdrant 对批量写入做了深度优化。建议将数据积攒到 100-1000 条后批量插入,可以显著提高吞吐量。
调整 HNSW 参数
HNSW 是 Qdrant 的核心索引算法:
m:最大连接数。默认 16。增大可提高召回率,但消耗更多内存。ef_construct:构建索引时的搜索范围。默认 100。增大可提高索引质量,但构建变慢。- 搜索时的
ef:可以在搜索请求中动态指定。增大ef可以提高召回率,但会降低搜索速度。对于实时性要求高的接口,设置小一点(如 50);对于离线分析,设置大一点(如 200)。
Payload 索引
如果你经常按"城市"、"类别"等字段过滤,务必为这些字段建立索引。
go
createCollection 的 payload_index_config 参数示例:
PayloadIndexConfig: []*qdrant.PayloadIndexParams{
{
FieldName: "category",
FieldType: qdrant.PayloadIndexType_Keyword,
},
}
量化
如果你的内存吃紧,可以在创建集合时开启量化(Scalar Quantization 或 Product Quantization)。这会将 float32 的向量压缩为 int8 或 uint8,虽然会损失极少量的精度,但能节省 4 倍的内存。
总结
本文通过详尽的代码示例,展示了如何在 Golang 中集成 Qdrant 向量数据库,涵盖了从环境搭建到核心 CRUD、过滤检索的全过程。Qdrant 结合 Golang 的高并发特性,非常适合构建高性能的 AI 应用后端。 掌握了这些基础知识后,你可以进一步探索 Qdrant 的高级特性,如 多向量存储 (同一份数据存不同模型的向量)、负向量搜索 (推荐系统常用)以及 分布式集群部署,为你的业务赋能。