跳到主要内容

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. 实现一个简单的商品推荐系统原型

参考资料