跳到主要内容

Redis Vector Sets

Vector Sets 是 Redis 8.0 引入的原生数据类型(Beta 版本),专门用于存储和检索高维向量数据,支持高效的向量相似性搜索。这一特性由 Redis 的创始人 Salvatore Sanfilippo(antirez)设计开发,使 Redis 能够原生支持 AI 应用中的向量检索需求,无需依赖外部向量数据库或额外的模块。

版本说明

Vector Sets 在 Redis 8.0 中以 Beta 版本发布,API 和功能可能在未来版本中发生变化。对于需要企业级搜索能力(如全文搜索、混合查询)的场景,建议使用 Redis Query Engine。

概述​

什么是 Vector Sets?​

Vector Sets 类似于 Sorted Sets,但每个元素关联的是一个向量(而非分数)。Vector Sets 专门设计用于:

  • 相似性搜索:快速找到与查询向量最相似的元素
  • 推荐系统:基于向量相似度进行内容推荐
  • 语义搜索:结合文本嵌入实现语义检索
  • AI 应用:支持 RAG(检索增强生成)等场景

Vector Sets 与 Redis Query Engine 的区别​

Redis 现在提供两种互补的向量搜索能力:

特性Vector SetsRedis Query Engine
定位轻量级原生数据类型企业级搜索引擎
搜索类型向量相似性向量 + 全文 + 数值 + 地理
API 复杂度简单直观功能丰富
扩展性单机优化支持水平扩展
适用场景纯向量相似性搜索混合查询、大规模数据

何时选择 Vector Sets:

  • 应用主要关注向量相似性搜索
  • 不需要全文搜索或复杂混合查询
  • 追求简单、轻量的解决方案
  • AI 驱动的应用需要高效存储和查询嵌入向量

何时选择 Redis Query Engine:

  • 需要综合搜索和查询能力
  • 需要全文搜索、数值过滤、地理操作的组合
  • 大规模数据需要水平扩展
  • 企业级应用需要高可用性

核心特性​

特性说明
原生支持无需安装额外模块,开箱即用
HNSW 算法使用分层导航小世界算法实现高效检索
内存高效支持向量量化,减少内存占用
混合搜索支持向量相似性与属性过滤的结合
属性存储每个元素可关联 JSON 格式的元数据

HNSW 算法简介​

HNSW(Hierarchical Navigable Small World)是目前向量搜索领域最主流的近似最近邻(ANN)算法:

层级结构示意:

Level 2: [Head]─────────────────────>[Tail]
│ │
Level 1: [Head]──────>[B]────────────>[Tail]
│ │ │
Level 0: [Head]──>[A]──>[B]──>[C]──>[D]──>[Tail]

核心思想:

  • 构建多层图结构,高层稀疏、底层稠密
  • 搜索时从高层开始快速定位区域,逐层下沉
  • 时间复杂度接近 O(log N),远优于暴力搜索的 O(N)

基本命令​

VADD 添加向量​

向 Vector Set 添加元素和对应的向量:

# 语法
VADD key [NOCREATE] [REDUCE dim] [QUANTIZATION {q8|bf|no}] [EF build_ef] [SETATTR json_attr] vector element

# 参数说明
# key - Vector Set 的键名
# vector - 向量数据的字符串表示(如 "1.0,2.0,3.0")
# element - 元素名称
# NOCREATE - 如果集合不存在则报错,不自动创建
# REDUCE - 使用随机投影降维到指定维度
# QUANTIZATION - 量化类型:q8(8位,默认)、bf(二进制)、no(不量化)
# EF - 构建时的 ef 参数(影响索引质量,默认200)
# SETATTR - 设置元素的 JSON 属性

# 添加二维向量
127.0.0.1:6379> VADD points "1.0,1.0" point_A
(integer) 1

127.0.0.1:6379> VADD points "-1.0,-1.0" point_B
(integer) 1

127.0.0.1:6379> VADD points "-1.0,1.0" point_C
(integer) 1

127.0.0.1:6379> VADD points "1.0,-1.0" point_D
(integer) 1

# 添加带属性的向量
127.0.0.1:6379> VADD products "0.5,0.3,0.8" prod_001 SETATTR '{"name":"Redis实战","category":"tech","price":89}'
(integer) 1

# 使用高精度(不量化)
127.0.0.1:6379> VADD precise_data "0.1,0.2,0.3" item_001 QUANTIZATION no
(integer) 1

# 使用二进制量化(内存最省)
127.0.0.1:6379> VADD binary_data "0.1,0.2,0.3" item_002 QUANTIZATION bf
(integer) 1

# 使用降维(从高维降到低维)
127.0.0.1:6379> VADD reduced "0.1,0.2,...,0.768" doc_001 REDUCE 128
(integer) 1

向量格式说明:

  • 向量以逗号分隔的浮点数字符串表示
  • 同一 Vector Set 中所有向量维度必须一致
  • 默认使用 8 位量化以节省内存和提高性能

VSIM 相似性搜索​

查找与给定向量或元素最相似的结果:

# 语法
VSIM key [vector | ELE element] [WITHSCORES] [COUNT num] [EF search_ef] [FILTER filter_expr] [FILTER-ATTRS name ...] [TRUTH]

# 参数说明
# vector - 查询向量
# ELE element - 使用已有元素的向量作为查询
# WITHSCORES - 返回相似度分数
# COUNT - 返回结果数量(默认10)
# EF - 搜索时的 ef 参数(影响召回率)
# FILTER - 过滤表达式
# FILTER-ATTRS - 指定过滤使用的属性名
# TRUTH - 返回精确值而非量化后的近似值

# 使用查询向量搜索
127.0.0.1:6379> VSIM points "1.0,0.5" WITHSCORES COUNT 3
1) "point_A"
2) "0.9999"
3) "point_D"
4) "0.8944"
5) "point_C"
6) "0.4472"

# 使用已有元素搜索(找相似元素)
127.0.0.1:6379> VSIM points ELE point_A WITHSCORES COUNT 3
1) "point_A"
2) "1.0"
3) "point_D"
4) "0.0"
5) "point_C"
6) "0.0"

# 带过滤条件的搜索
127.0.0.1:6379> VSIM products "0.6,0.4,0.7" WITHSCORES COUNT 10 FILTER ".category == 'tech'"
1) "prod_001"
2) "0.9876"

# 使用高精度搜索
127.0.0.1:6379> VSIM points "1.0,0.5" WITHSCORES COUNT 3 TRUTH

过滤表达式语法:

  • 使用类似 JSONPath 的语法访问属性
  • 支持 ==、!=、>、<、>=、<= 等比较操作符
  • 支持逻辑组合:&&(与)、||(或)
  • 属性通过 . 前缀访问
# 复杂过滤示例
VSIM products "0.5,0.5,0.5" FILTER ".price < 100 && .category == 'tech'"
VSIM documents "0.3,0.4,0.5" FILTER ".year > 2020 && .lang == 'en'"
VSIM items "0.1,0.2,0.3" FILTER ".status == 'active' || .featured == true"

相似度分数说明:

  • 分数范围是 0 到 1,值越大表示越相似
  • 与自己比较时分数为 1.0
  • 分数基于余弦相似度计算

VEMB 获取向量​

获取元素的向量表示:

# 语法
VEMB key element

# 获取向量
127.0.0.1:6379> VEMB points point_A
1) "1.0"
2) "1.0"

# 注意:返回的可能是量化后的近似值

VCARD 获取元素数量​

获取 Vector Set 中的元素数量:

127.0.0.1:6379> VCARD points
(integer) 4

VDIM 获取向量维度​

获取 Vector Set 中向量的维度:

127.0.0.1:6379> VDIM points
(integer) 2

VINFO 获取详细信息​

获取 Vector Set 的元数据:

127.0.0.1:6379> VINFO points
1) quant_type
2) "q8"
3) dim
4) (integer) 2
5) size
6) (integer) 4
7) max_level
8) (integer) 3
9) entry_point
10) "point_A"
...

属性管理​

VSETATTR 设置属性​

为元素设置 JSON 格式的属性:

# 语法
VSETATTR key element json

# 设置属性
127.0.0.1:6379> VSETATTR points point_A '{"label":"第一象限","value":100}'
OK

# 清空属性
127.0.0.1:6379> VSETATTR points point_A ""
OK

VGETATTR 获取属性​

获取元素的属性:

# 语法
VGETATTR key element

127.0.0.1:6379> VGETATTR points point_A
"{\"label\":\"第一象限\",\"value\":100}"

其他命令​

VREM 删除元素​

从 Vector Set 中删除元素:

# 语法
VREM key element

127.0.0.1:6379> VREM points point_A
(integer) 1

VRANDMEMBER 随机获取元素​

随机获取 Vector Set 中的元素:

# 获取一个随机元素
127.0.0.1:6379> VRANDMEMBER points
"point_C"

# 获取多个随机元素
127.0.0.1:6379> VRANDMEMBER points 2
1) "point_B"
2) "point_D"

获取元素在 HNSW 图中的邻居连接信息:

# 语法
VLINKS key element

127.0.0.1:6379> VLINKS points point_A
1) (integer) 0
2) 1) "point_D"
2) "point_C"

实际应用示例​

场景一:文档语义搜索​

假设我们有一个文档库,每个文档已经被转换为向量(通过嵌入模型如 OpenAI 的 text-embedding-ada-002):

import redis
import numpy as np

# 连接 Redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def index_document(doc_id, text, embedding, metadata=None):
"""索引文档"""
# 将向量转换为字符串
vector_str = ",".join(map(str, embedding))

# 添加到 Vector Set
if metadata:
r.execute_command('VADD', 'documents', vector_str, doc_id,
'SETATTR', metadata)
else:
r.execute_command('VADD', 'documents', vector_str, doc_id)

def search_similar(query_embedding, top_k=10, filters=None):
"""搜索相似文档"""
vector_str = ",".join(map(str, query_embedding))

args = ['VSIM', 'documents', vector_str, 'WITHSCORES', 'COUNT', str(top_k)]
if filters:
args.extend(['FILTER', filters])

results = r.execute_command(*args)

# 解析结果
documents = []
for i in range(0, len(results), 2):
documents.append({
'doc_id': results[i],
'score': float(results[i + 1])
})
return documents

# 使用示例
doc_embedding = np.random.rand(1536).tolist() # 假设是 1536 维向量
index_document('doc_001', 'Redis 入门教程', doc_embedding,
'{"title":"Redis入门","category":"tech","views":1000}')

query_embedding = np.random.rand(1536).tolist()
results = search_similar(query_embedding, top_k=5)

场景二:商品推荐系统​

import redis
import json

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def add_product(product_id, name, category, price, features_vector):
"""添加商品"""
vector_str = ",".join(map(str, features_vector))
attr = json.dumps({
'name': name,
'category': category,
'price': price
})
r.execute_command('VADD', 'products', vector_str, product_id, 'SETATTR', attr)

def recommend_products(user_preference_vector, category=None, max_price=None, top_k=10):
"""推荐商品"""
vector_str = ",".join(map(str, user_preference_vector))

# 构建过滤条件
filters = []
if category:
filters.append(f".category == '{category}'")
if max_price:
filters.append(f".price <= {max_price}")

filter_expr = " && ".join(filters) if filters else None

args = ['VSIM', 'products', vector_str, 'WITHSCORES', 'COUNT', str(top_k)]
if filter_expr:
args.extend(['FILTER', filter_expr])

results = r.execute_command(*args)

recommendations = []
for i in range(0, len(results), 2):
product_id = results[i]
score = float(results[i + 1])
attr = r.execute_command('VGETATTR', 'products', product_id)
recommendations.append({
'product_id': product_id,
'score': score,
'attributes': json.loads(attr) if attr else {}
})
return recommendations

# 使用示例
add_product('prod_001', 'Redis实战', 'tech', 89, [0.5, 0.3, 0.8])
add_product('prod_002', 'Python编程', 'tech', 79, [0.6, 0.4, 0.7])
add_product('prod_003', '算法导论', 'tech', 129, [0.7, 0.5, 0.6])

user_vector = [0.55, 0.35, 0.75]
recommendations = recommend_products(user_vector, category='tech', max_price=100)

场景三:人脸相似度搜索​

import redis
import json

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def register_face(user_id, face_embedding, user_info):
"""注册人脸"""
vector_str = ",".join(map(str, face_embedding))
attr = json.dumps(user_info)
r.execute_command('VADD', 'faces', vector_str, user_id, 'SETATTR', attr)

def identify_face(face_embedding, threshold=0.95, top_k=1):
"""识别人脸"""
vector_str = ",".join(map(str, face_embedding))

results = r.execute_command('VSIM', 'faces', vector_str,
'WITHSCORES', 'COUNT', str(top_k))

if not results:
return None

user_id = results[0]
similarity = float(results[1])

if similarity >= threshold:
attr = r.execute_command('VGETATTR', 'faces', user_id)
return {
'user_id': user_id,
'similarity': similarity,
'user_info': json.loads(attr) if attr else {}
}
return None

# 使用示例
face_embedding = [0.1, 0.2, 0.3, ...] # 512 维人脸特征向量
register_face('user_001', face_embedding, {'name': '张三', 'dept': '研发部'})

# 识别
query_embedding = [0.1, 0.21, 0.31, ...]
result = identify_face(query_embedding)
if result:
print(f"识别成功: {result['user_info']['name']}")

场景四:RAG 检索增强生成​

import redis
import json

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def index_knowledge(doc_id, content, embedding, source, page=None):
"""索引知识库"""
vector_str = ",".join(map(str, embedding))
attr = json.dumps({
'content': content,
'source': source,
'page': page
})
r.execute_command('VADD', 'knowledge_base', vector_str, doc_id, 'SETATTR', attr)

def retrieve_context(query_embedding, top_k=5):
"""检索相关上下文"""
vector_str = ",".join(map(str, query_embedding))

results = r.execute_command('VSIM', 'knowledge_base', vector_str,
'WITHSCORES', 'COUNT', str(top_k))

contexts = []
for i in range(0, len(results), 2):
doc_id = results[i]
score = float(results[i + 1])
attr = r.execute_command('VGETATTR', 'knowledge_base', doc_id)
if attr:
data = json.loads(attr)
contexts.append({
'doc_id': doc_id,
'score': score,
'content': data.get('content'),
'source': data.get('source')
})
return contexts

def rag_query(query_embedding, llm_client):
"""RAG 查询"""
# 检索相关上下文
contexts = retrieve_context(query_embedding)

# 构建提示词
context_text = "\n".join([c['content'] for c in contexts])
prompt = f"根据以下上下文回答问题:\n\n{context_text}\n\n问题:..."

# 调用 LLM
response = llm_client.generate(prompt)
return response

性能参数调优​

HNSW 参数​

Vector Sets 使用 HNSW(分层导航小世界)算法进行高效的近似最近邻搜索。理解以下参数有助于优化性能:

参数说明默认值调优建议
EF(构建时)构建索引时的邻居数量200增大可提高召回率,但增加构建时间
EF(搜索时)搜索时探索的邻居数量动态增大可提高召回率,但增加延迟
# 构建时指定更高的 EF
VADD my_vectors "0.1,0.2,..." element1 EF 400

# 搜索时指定更高的 EF 以获得更好的召回率
VSIM my_vectors "0.1,0.2,..." WITHSCORES COUNT 20 EF 500

构建与搜索权衡​

  • 高召回率配置:构建 EF = 400,搜索 EF = 500
  • 平衡配置:构建 EF = 200(默认),搜索 EF = 100-200
  • 高性能配置:构建 EF = 100,搜索 EF = 50
# 高召回率场景(如推荐系统)
VADD products "0.5,0.3,..." prod_001 EF 400
VSIM products "0.5,0.3,..." COUNT 10 EF 500

# 高性能场景(如实时搜索)
VADD realtime "0.5,0.3,..." item_001 EF 100
VSIM realtime "0.5,0.3,..." COUNT 10 EF 50

COUNT 与 EF 的关系​

  • COUNT 指定返回结果数量
  • EF 影响搜索过程中的探索范围
  • 通常 EF 应该大于或等于 COUNT
  • 更大的 EF/COUNT 比值意味着更高的召回率

内存优化​

量化选项​

Vector Sets 支持多种量化类型以减少内存占用。量化在添加第一个元素时设置,之后无法更改:

量化类型参数值说明内存占用适用场景
8 位量化q8(默认)将浮点数量化为 8 位整数约 1/4大多数场景
二进制量化bf将向量二值化约 1/32内存敏感场景
不量化no保持原始精度最高需要高精度场景
# 使用默认的 8 位量化
VADD my_vectors "0.1,0.2,..." element1

# 不使用量化(最高精度)
VADD my_vectors "0.1,0.2,..." element1 QUANTIZATION no

# 使用二进制量化(最省内存)
VADD my_vectors "0.1,0.2,..." element1 QUANTIZATION bf

降维功能​

Vector Sets 支持通过随机投影进行降维,可以在添加向量时减少维度:

# 将高维向量降维到 128 维
VADD documents "0.1,0.2,...,0.1536" doc_001 REDUCE 128

降维的优势:

  • 减少内存占用
  • 提高搜索速度
  • 适合维度非常高(如 1536+)的场景

注意事项:

  • 降维会带来一定的精度损失
  • 同一集合中的所有向量必须使用相同的降维设置

内存估算​

内存占用 ≈ 向量维度 × 元素数量 × 每维度字节数 + HNSW 图开销

示例:100万向量,1536维,8位量化
≈ 1536 × 1,000,000 × 1 字节 ≈ 1.5 GB
+ HNSW 图开销约 20-30%
≈ 总计约 2 GB

二进制量化时:
≈ 1536 × 1,000,000 / 8 字节 ≈ 192 MB
+ HNSW 图开销
≈ 总计约 250 MB

与其他方案的对比​

Vector Sets vs Redis Query Engine​

特性Vector SetsRedis Query Engine
定位原生数据类型企业级搜索引擎
安装要求内置于 Redis 8.0内置于 Redis 8.0
API 复杂度简单直观功能丰富
向量搜索支持支持
全文搜索不支持支持
混合查询属性过滤完整支持
水平扩展单机优化支持
适用规模中小规模大规模

选择建议​

选择 Vector Sets 当:

  • 应用主要关注向量相似性搜索
  • 不需要全文搜索或复杂混合查询
  • 追求简单、轻量的解决方案
  • 数据量适中(百万级向量)

选择 Redis Query Engine 当:

  • 需要向量搜索与全文搜索结合
  • 需要复杂的混合查询
  • 数据量大,需要水平扩展
  • 需要索引 Hash、JSON 等 Redis 数据结构

最佳实践​

1. 向量归一化​

import numpy as np

def normalize_vector(vector):
"""归一化向量"""
norm = np.linalg.norm(vector)
if norm == 0:
return vector
return (vector / norm).tolist()

# 使用归一化后的向量
embedding = normalize_vector(raw_embedding)
vector_str = ",".join(map(str, embedding))

2. 合理设置维度​

  • 文本嵌入:通常 768-1536 维
  • 图像特征:通常 512-2048 维
  • 维度越高,精度越好,但内存和计算成本也越高

3. 定期重建索引​

# 对于频繁更新的场景,可以定期重建索引以优化性能
def rebuild_index(r, key):
"""重建 Vector Set 索引"""
# 获取所有元素
elements = r.execute_command('VRANDMEMBER', key, -1)
# ... 重新添加逻辑

4. 使用连接池​

import redis
from redis.connection import ConnectionPool

pool = ConnectionPool(host='localhost', port=6379, max_connections=100)
r = redis.Redis(connection_pool=pool)

限制与注意事项​

  1. 维度一致性:同一 Vector Set 中所有向量必须维度相同
  2. 内存限制:大量向量会占用大量内存,需合理规划
  3. 更新成本:频繁更新会影响 HNSW 图的性能
  4. 版本要求:需要 Redis 8.0+ 或 Redis Stack 8.0+

小结​

本章我们学习了:

  1. Vector Sets 概述:Redis 8.0 引入的原生向量数据类型,支持高效相似性搜索
  2. 核心特性:量化存储、降维、属性过滤、HNSW 索引
  3. 基本命令:VADD、VSIM、VEMB、VCARD、VDIM、VINFO 等
  4. 属性管理:VSETATTR 设置属性,VSIM 中使用 FILTER 过滤
  5. 应用场景:语义搜索、推荐系统、人脸识别、RAG 检索增强生成
  6. 性能调优:EF 参数调整召回率与速度的平衡
  7. 内存优化:量化选项和降维功能

练习​

  1. 创建一个 Vector Set 并添加多个二维向量,测试相似性搜索
  2. 使用属性过滤功能筛选特定类别的相似结果
  3. 对比不同量化类型对搜索结果和内存占用的影响
  4. 实现一个简单的商品推荐系统原型

参考资料​