在构建语义搜索、推荐系统或RAG(检索增强生成)应用时,我们经常需要对高维向量进行快速的相似性检索。传统的关系型数据库在这方面几乎无能为力,而专用向量数据库虽然功能丰富,但引入新组件意味着更高的运维成本。如果你的技术栈中已经有Redis,那么好消息是:通过RediSearch模块提供的Vector Search能力,Redis完全可以承担低延迟向量相似搜索的任务,甚至在百万级向量规模下依然能保持毫秒级响应。

一、Redis向量搜索的底层原理
Redis的向量搜索能力由RediSearch模块提供,它从2.4版本开始引入向量类型(VECTOR)字段支持。核心思路是:向量作为一个字段存储在Hash结构中,RediSearch为这些向量字段建立专门的索引结构,查询时通过索引快速找到与目标向量最相似的K个结果,而不是暴力遍历全量数据。
RediSearch支持两种索引算法。第一种是FLAT(暴力搜索),它会把所有向量完整存入索引,查询时逐一计算距离,优点是100%精确无召回损失,缺点是数据量上去之后延迟会线性增长,适合十万级以下的数据集。第二种是HNSW(分层可导航小世界图),这是一种近似最近邻算法,通过构建多层图结构,让查询从顶层快速跳转到目标邻域,再在底层精细搜索,时间复杂度接近对数级别,适合百万到亿级数据,代价是存在一定的召回率损失,需要通过参数调节来平衡速度与精度。
距离度量方面,Redis支持三种:COSINE(余弦距离,适合文本 embedding)、L2(欧氏距离,适合图像特征)、IP(内积,适合已归一化向量的场景)。选择哪种度量取决于你的向量生成模型,比如OpenAI的embedding已经归一化,用COSINE和IP效果接近。
二、创建向量索引并写入数据
创建索引使用FT.CREATE命令,关键是定义VECTOR字段时的参数配置。下面用redis-cli演示创建一个HNSW索引:
FT.CREATE idx:docs ON HASH PREFIX 1 doc: SCHEMA title TEXT content TEXT embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE M 16 EF_CONSTRUCTION 200
这段命令的含义是:针对前缀为doc:的Hash键建立索引,embedding字段是向量类型,使用HNSW算法,维度1536(对应OpenAI text-embedding模型),余弦距离。M参数控制每个节点在图中的连接数,越大召回越高但内存消耗也越大,通常取12到48之间;EF_CONSTRUCTION控制建索引时的搜索深度,影响索引质量。
写入数据时,向量必须以字节数组形式存储。用Python的redis-py库操作会更方便:
import numpy as np
import redis
from redis.commands.search.field import VectorField, TextField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
r = redis.Redis(host="localhost", port=6379)
# 定义向量字段
vector_field = VectorField("embedding",
"HNSW", {
"TYPE": "FLOAT32",
"DIM": 1536,
"DIM": 1536,
"DISTANCE_METRIC": "COSINE",
"M": 16,
"EF_CONSTRUCTION": 200
})
embedding = np.random.rand(1536).astype(np.float32)
# 向量转为字节写入Hash
r.hset("doc:1", mapping={
"title": "Redis教程",
"content": "向量搜索实战",
"embedding": embedding.tobytes()
})注意一个常见坑:向量必须显式转换为float32类型再tobytes,如果直接用float64的numpy数组,写入会成功但查询时会报维度或类型不匹配的错误。另外索引创建后写入的数据会被自动索引,无需手动刷新。
三、执行KNN查询与混合过滤
向量查询最常用的场景是KNN搜索,即找出与查询向量最相似的K条记录。命令格式如下:
FT.SEARCH idx:docs "*=>[KNN 5 @embedding $vec AS score]" PARAMS 2 vec "查询向量的字节数组" SORTBY score DIALECT 2 RETURN 3 title content score
这里的查询语法中,KNN 5表示返回最相似的5条结果,@embedding指定搜索的向量字段,$vec是参数占位符,实际向量通过PARAMS传入,这样可以避免拼接字节串带来的转义问题。AS score把距离值存为临时字段方便排序展示。Python端的完整写法:
from redis.commands.search.query import Query
query_vec = np.random.rand(1536).astype(np.float32).tobytes()
q = Query("*=>[KNN 5 @embedding $vec AS score]") \
.sort_by("score") \
.return_fields("title", "score") \
.dialect(2)
results = r.ft("idx:docs").search(q, query_params={"vec": query_vec})
for doc in results.docs:
print(doc.title, doc.score)更强大的能力是混合查询,即在向量搜索的同时叠加属性过滤。比如只搜索某个分类下、发布时间晚于某日期的文档,查询语句写成:
FT.SEARCH idx:docs
"(@category:{科技} @created:[20240101 inf])=>[KNN 5 @embedding $vec AS score]"
PARAMS 2 vec "..."
DIALECT 2需要注意的是,混合查询默认是先过滤再搜索(pre-filter),这保证了过滤条件总是生效,但如果过滤后的候选集太小,HNSW图的连通性可能受影响,导致召回下降,此时可以考虑调整EF_RUNTIME参数增大运行时搜索范围。
四、性能优化与生产实践建议
在生产环境中部署Redis向量搜索,有几个关键点值得注意。首先是内存规划:一个1536维的float32向量本身约占6KB,加上HNSW图结构开销(大约是向量本身的1.5到2倍),百万级数据需要15GB以上内存,建议开启Redis的企业版或模块的二级存储(vector sets配合SSD)来降低成本,社区版则需要做好容量评估。
其次是参数调优。HNSW的EF_RUNTIME默认为10,如果发现召回率不达标,可以逐步提高到100甚至更高,延迟会相应增加但通常仍在可接受范围。建议用真实数据集离线评测召回率与延迟的曲线,找到业务能接受的平衡点。对于FLAT索引,如果数据量在50万以内且延迟要求不苛刻,直接用FLAT可以获得100%精确结果,省去调参烦恼。
最后是架构层面的建议:向量写入建议批量进行,用pipeline一次性提交可以大幅降低网络往返开销;查询服务前可以加一层embedding缓存,避免重复调用向量化模型;如果Redis中同时存放业务数据和向量索引,务必做好内存隔离(比如独立部署实例),防止向量索引的内存增长挤压业务数据的生存空间。对于超大规模场景,Redis 8引入的Vector Sets提供了更现代的API,可以作为RediSearch方案的补充选型。
总结来说,Redis Vector Search凭借内存级读写速度和熟悉的运维体系,为中小规模向量检索场景提供了极高的性价比。掌握索引算法选择、数据写入规范和混合查询语法这三个核心点,就能快速搭建起一套低延迟的语义搜索服务。
Redis向量搜索Vector Search API相似性搜索修改时间:2026-08-31 22:01:00