- 后端
- 缓存
- 数据库客户端
- 消息队列
【免费下载链接】StackExchange.Redis
The Redis client for .NET
Redis Vector Sets(向量集合)是 Redis 8.0 起提供的向量数据类型,用于高效存储向量数据并执行相似度检索。本文以 StackExchange.Redis 官方文档 docs/VectorSets.md 为骨架,结合仓库内 RedisDatabase.VectorSets.cs、VectorSetAddRequest.cs、VectorSetSimilaritySearchRequest.cs 等源码实现与 VectorSetIntegrationTests.cs 测试用例,完整讲解 SE.Redis 强类型向量 API 的用法、底层命令映射、内存管理与性能优化策略。读完本文,你将能够使用 StackExchange.Redis 完成向量写入、带 JSON 属性的过滤式相似度搜索、HNSW 索引调参、范围查询与语义搜索/推荐系统等实战方案。
前置条件:Redis 版本要求
Vector Sets 依赖 Redis 8.0 或更高版本。在调用任何VectorSet*系列 API 之前,请确认服务端版本满足要求,否则命令将不被识别。客户端侧无需额外配置,只要使用本文介绍的 API 即可;向量集在首次执行VADD时自动创建,其索引类型(默认使用 HNSW 近似最近邻索引)与参数可在写入时控制(详见下文"高级配置"一节)。
快速上手
与文档一致,下文示例中的向量仅为便于演示而缩小(4 维),实际生产场景中通常使用高维向量(如 128、384、768 维)。API 的设计目标是高效处理大向量:例如 VectorSetAddRequest.Member 接收ReadOnlyMemory<float>而非数组,这使得你可以直接基于ArrayPool<T>池化缓冲区甚至内存映射文件(memory-mapped files)中的原始内存工作,避免不必要的数组分配与拷贝。
添加向量
使用VectorSetAddAsync将成员及其向量写入向量集:
var db = conn.GetDatabase(); var key = "product-embeddings"; // 从 ML 模型产出的向量 var vector = new[] { 0.1f, 0.2f, 0.3f, 0.4f }; // 添加带向量的成员 var request = VectorSetAddRequest.Member("product-123", vector.AsMemory()); bool added = await db.VectorSetAddAsync(key, request);返回值bool表示成员是否新增(已存在成员的重复写入返回false)。同步版本VectorSetAdd同样可用,两者在 RedisDatabase.VectorSets.cs 中最终都会把request转换为VADD命令消息下发。
添加带属性的向量
可以为成员附加 JSON 元数据,用于后续过滤搜索:
var vector = new[] { 0.1f, 0.2f, 0.3f, 0.4f }; var request = VectorSetAddRequest.Member( "product-123", vector.AsMemory(), attributesJson: """{"category":"electronics","price":299.99}""" ); await db.VectorSetAddAsync(key, request);从 VectorSetAddMessage.cs 的实现可见,属性会以SETATTR {attributes}参数形式随VADD一并发送。此外,源码还暴露了UseCheckAndSet(对应CAS参数)用于按需做"检查-设置"模式的部分线程安全写入,详见 VectorSetAddRequest.cs。
相似度搜索
按已有成员搜索相似向量,使用VectorSetSimilaritySearchAsync:
// 按已有成员搜索 var query = VectorSetSimilaritySearchRequest.ByMember("product-123"); query.Count = 10; query.WithScores = true; using var results = await db.VectorSetSimilaritySearchAsync(key, query); if (results is not null) { foreach (var result in results.Value.Results) { Console.WriteLine($"Member: {result.Member}, Score: {result.Score}"); } }也可以直接用一个向量作为查询:
var queryVector = new[] { 0.15f, 0.25f, 0.35f, 0.45f }; var query = VectorSetSimilaritySearchRequest.ByVector(queryVector.AsMemory()); query.Count = 10; query.WithScores = true; using var results = await db.VectorSetSimilaritySearchAsync(key, query);两种查询方式分别对应VSIM命令的ELE {member}与FP32 {vector}目标形态(见 VectorSetSimilaritySearchMessage.cs)。结果类型 VectorSetSimilaritySearchResult 提供Member、Score(0~1,1 表示完全一致,0 表示方向相反;未请求WITHSCORES时为NaN)与可选的AttributesJson。
过滤搜索
使用 JSONPath 表达式过滤结果:
var query = VectorSetSimilaritySearchRequest.ByVector(queryVector.AsMemory()); query.Count = 10; query.FilterExpression = "$.category == 'electronics' && $.price < 500"; query.WithAttributes = true; // 结果中包含属性 query.MaxFilteringEffort = 100; // 可选:限制过滤重试次数(FILTER-EF 参数) using var results = await db.VectorSetSimilaritySearchAsync(key, query);FilterExpression对应FILTER参数,其完整语法(如==、<、&&组合、嵌套路径等)以 Redis 官方 Vector Sets 过滤搜索文档为准;MaxFilteringEffort(FILTER-EF)则控制过滤时的最大尝试次数,二者均由 VectorSetSimilaritySearchRequest.cs 公开并映射到 VectorSetSimilaritySearchMessage.cs 的写盘逻辑。
Vector Set 常用操作
以下 API 均提供同步与Async两种形态,底层命令映射可从 RedisDatabase.VectorSets.cs 直接确认。
获取向量集信息
VINFO命令返回集合元数据,由只读结构体 VectorSetInfo 承载:
var info = await db.VectorSetInfoAsync(key); if (info is not null) { Console.WriteLine($"Dimension: {info.Value.Dimension}"); Console.WriteLine($"Length: {info.Value.Length}"); Console.WriteLine($"Quantization: {info.Value.Quantization}"); }除文档展示的Dimension(向量维度)与Length(集合基数)外,VectorSetInfo还包含MaxLevel(HNSW 图结构最高层)、VectorSetUid(集合唯一标识)与HnswMaxNodeUid(HNSW 图中最大节点 ID),可用于观测索引规模。当服务端返回未知量化类型时,Quantization为Unknown,此时可通过QuantizationRaw读取原始字符串,见 VectorSetQuantization.cs。
检查成员是否存在
bool exists = await db.VectorSetContainsAsync(key, "product-123");对应VISMEMBER命令(RedisDatabase.VectorSets.cs)。
移除成员
bool removed = await db.VectorSetRemoveAsync(key, "product-123");对应VREM命令,返回是否确实移除了成员。
获取随机成员
// 获取单个随机成员 var member = await db.VectorSetRandomMemberAsync(key); // 获取多个随机成员 var members = await db.VectorSetRandomMembersAsync(key, count: 5);分别对应VRANDMEMBER与带count的VRANDMEMBER(RedisDatabase.VectorSets.cs),可服务于抽样、A/B 测试等场景。
范围查询
按字典序范围获取成员
VRANGE命令支持按成员名的字典序做范围遍历,返回Lease<RedisValue>:
// 获取全部成员 using var allMembers = await db.VectorSetRangeAsync(key); // ... 访问 allMembers.Span 等 // 获取指定范围内的成员 using var rangeMembers = await db.VectorSetRangeAsync( key, start: "product-100", end: "product-200", count: 50 ); // 排除边界 using var members = await db.VectorSetRangeAsync( key, start: "product-100", end: "product-200", exclude: Exclude.Both );参数细节可在 RedisDatabase.VectorSets.cs 中确认:start/end为空时分别映射为-与+(正负无穷);exclude通过((开区间)与[(闭区间)前缀控制边界;count默认-1表示不限制返回数量。
枚举大结果集
对于大型向量集,应使用异步枚举按批处理结果,避免客户端一次性缓冲全部数据:
await foreach (var member in db.VectorSetRangeEnumerateAsync(key, count: 100)) { Console.WriteLine($"Processing: {member}"); }从 RedisDatabase.VectorSets.cs 的实现可以看到其工作原理:每次拉取一批(默认count=100),将本批最后一个值作为下一批的排他起点继续向后翻页;当返回批长度小于count或到达指定终点时停止。这意味着提前退出循环时,客户端与服务端都会停止处理与发送剩余结果。枚举同样支持异步取消:
using var cts = new CancellationTokenSource(); // 取消逻辑未展示 await foreach (var member in db.VectorSetRangeEnumerateAsync(key, count: 100) .WithCancellation(cts.Token)) { // ... }内部通过[EnumeratorCancellation]把取消令牌贯穿到每一批的VectorSetRangeAsync调用(RedisDatabase.VectorSets.cs),做到按批粒度的协作式取消。
高级配置
量化(Quantization)
控制向量压缩方式,降低内存占用并提升检索速度:
var request = VectorSetAddRequest.Member("product-123", vector.AsMemory()); request.Quantization = VectorSetQuantization.Int8; // 默认 // 或 VectorSetQuantization.None (对应 NOQUANT / f32 全精度) // 或 VectorSetQuantization.Binary(对应 BIN 二进制量化) await db.VectorSetAddAsync(key, request);枚举定义见 VectorSetQuantization.cs:Int8(8 位整数量化,默认值,对应int8/Q8)、None(全精度 f32,对应NOQUANT)、Binary(二进制量化,对应BIN)。量化类型通过AsciiHash特性与服务端返回的字符串做无分配快速解析,并在 VectorSetAddMessage.cs 中按类型决定是否携带NOQUANT/BIN参数(Int8为隐式默认,不发送参数)。
维度缩减
使用随机投影(random projection)降低向量维度:
var request = VectorSetAddRequest.Member("product-123", vector.AsMemory()); request.ReducedDimensions = 128; // 从原始维度降维 await db.VectorSetAddAsync(key, request);对应VADD的REDUCE {dim}参数(VectorSetAddRequest.cs、VectorSetAddMessage.cs)。适合对极高维向量在牺牲一定精度的前提下换取内存与计算开销。
HNSW 索引参数
在写入时微调 HNSW 图构建参数:
var request = VectorSetAddRequest.Member("product-123", vector.AsMemory()); request.MaxConnections = 32; // M 参数(默认 16,每个节点的最大连接数) request.BuildExplorationFactor = 400; // EF 参数(默认 200,构建期的探索因子) await db.VectorSetAddAsync(key, request);MaxConnections与BuildExplorationFactor的默认值、语义(M {numlinks}、EF {build-exploration-factor})均可在 VectorSetAddRequest.cs 的 XML 注释中确认。注意:这些参数是在首次创建该向量集(即对空 key 的首次VADD)时生效的索引配置,已建索引的后续写入不会改变图结构参数。
搜索参数
控制搜索阶段的行为:
var query = VectorSetSimilaritySearchRequest.ByVector(queryVector.AsMemory()); query.SearchExplorationFactor = 500; // EF 越大越精确,但越慢 query.Epsilon = 0.1; // 仅返回相似度 >= 0.9 的结果 query.UseExactSearch = true; // 使用线性扫描(TRUTH)替代 HNSW 近似搜索 await db.VectorSetSimilaritySearchAsync(key, query);SearchExplorationFactor(EF):搜索期探索因子,越大召回率越高、耗时越长;Epsilon(EPSILON):相似度阈值,只返回相似度>= 1 - epsilon的结果(如0.1对应阈值0.9);UseExactSearch(TRUTH):强制线性精确扫描,牺牲性能换取 100% 准确召回,适用于小集合或对精度要求苛刻的场景。
上述参数以及DisableThreading(NOTHREAD,高级选项,将搜索限制在主线程执行)的完整映射见 VectorSetSimilaritySearchRequest.cs 与 VectorSetSimilaritySearchMessage.cs。
向量数据处理
获取近似向量
按成员获取其近似向量(注意:若启用了量化或维度缩减,返回的是重建后的近似向量而非原始值):
using var vectorLease = await db.VectorSetGetApproximateVectorAsync(key, "product-123"); if (vectorLease != null) { ReadOnlySpan<float> vector = vectorLease.Value.Span; // 使用向量数据 }对应VEMB命令,结果以Lease<float>返回(见 RedisDatabase.VectorSets.cs),务必用后即还(详见"内存管理"一节)。
管理 JSON 属性
// 获取属性 var json = await db.VectorSetGetAttributesJsonAsync(key, "product-123"); // 设置属性 await db.VectorSetSetAttributesJsonAsync( key, "product-123", """{"category":"electronics","updated":"2024-01-15"}""" );分别对应VGETATTR与VSETATTR命令(RedisDatabase.VectorSets.cs、RedisDatabase.VectorSets.cs)。VSETATTR返回bool表示成员是否存在,可用于校验。
查看 HNSW 图链接
检查 HNSW 图结构中的邻居连接:
// 获取链接的成员列表 using var links = await db.VectorSetGetLinksAsync(key, "product-123"); if (links != null) { foreach (var link in links.Value.Span) { Console.WriteLine($"Linked to: {link}"); } } // 获取带相似度分数的链接 using var linksWithScores = await db.VectorSetGetLinksWithScoresAsync(key, "product-123"); if (linksWithScores != null) { foreach (var link in linksWithScores.Value.Span) { Console.WriteLine($"Linked to: {link.Member}, Score: {link.Score}"); } }二者均对应VLINKS命令,后者追加WITHSCORES参数,结果元素类型为 VectorSetLink(含Member与Score)。该能力可用于诊断索引质量、可视化图结构或做基于图邻居的扩展检索。
内存管理:Lease<T>
向量相关操作(相似度搜索、范围查询、近似向量、图链接)返回Lease<T>。Lease<T>是一段由内存池(默认ArrayPool<T>.Shared,也支持自定义MemoryPool<T>)支撑的连续内存,必须在使用后释放以归还池中内存,见 Lease.cs。
// 推荐:using 语句 using var results = await db.VectorSetSimilaritySearchAsync(key, query); // 或显式释放 var results = await db.VectorSetSimilaritySearchAsync(key, query); try { // 使用 results } finally { results?.Dispose(); }using声明式的隐式释放(scope 结束时)在大多数场景下足够安全;显式finally释放则适合需要精确控制归还时机的长生命期代码。服务端返回null时(如集合不存在或按成员搜索的成员缺失)上述代码也能正确跳过释放。
性能考量
批量写入
批量插入推荐使用管道(pipelining),一次往返提交多个VADD:
var batch = db.CreateBatch(); var tasks = new List<Task<bool>>(); foreach (var (member, vector) in vectorData) { var request = VectorSetAddRequest.Member(member, vector.AsMemory()); tasks.Add(batch.VectorSetAddAsync(key, request)); } batch.Execute(); await Task.WhenAll(tasks);管道批量提交把 N 次命令合并为更少的网络往返,显著提升吞吐。若要控制单批规模,可对vectorData分片后循环执行。
搜索优化要点
- 量化:使用
VectorSetQuantization.Int8(默认)或Binary降低内存占用并提升搜索速度,代价是向量精度损失; - 调优
SearchExplorationFactor:在召回率与延迟之间取平衡,数值越大越精确、越慢; - 使用过滤表达式:缩小搜索空间,减少无效候选;
- 考虑维度缩减:对超高维向量使用
ReducedDimensions投影,换空间与算力; - 小集合用精确搜索:
UseExactSearch = true(TRUTH)在集合规模较小时可避免 HNSW 近似误差。
范围查询分页
优先使用异步枚举分批处理,避免一次性载入全部结果:
// 好:按批加载、逐项处理 await foreach (var member in db.VectorSetRangeEnumerateAsync(key)) { await ProcessMemberAsync(member); } // 避免:一次性加载全部结果 using var allMembers1 = await db.VectorSetRangeAsync(key); // 避免:虽然分批拉取,但 ToArrayAsync 仍会把全部结果堆进内存 var allMembers2 = await db.VectorSetRangeEnumerateAsync(key).ToArrayAsync();ToArrayAsync这类"消费型"收尾操作会抵消枚举的按批内存优势,应避免在大集合上使用。
常见模式
语义搜索
将文档嵌入向量存储后按查询向量检索:
// 1. 存储文档嵌入向量 var embedding = await GetEmbeddingFromMLModel(document); var request = VectorSetAddRequest.Member( documentId, embedding.AsMemory(), attributesJson: $$"""{"title":"{{document.Title}}","date":"{{document.Date}}"}""" ); await db.VectorSetAddAsync("documents", request); // 2. 按查询向量搜索相似文档 var queryEmbedding = await GetEmbeddingFromMLModel(searchQuery); var query = VectorSetSimilaritySearchRequest.ByVector(queryEmbedding.AsMemory()); query.Count = 10; query.WithScores = true; query.WithAttributes = true; using var results = await db.VectorSetSimilaritySearchAsync("documents", query);属性(标题、日期等)随结果返回,可用于直接渲染或二次过滤。
推荐系统
基于用户已喜欢的商品做相似推荐,并用属性过滤库存与价格:
// 以用户喜欢的商品为锚点查找相似商品 var query = VectorSetSimilaritySearchRequest.ByMember(userLikedItemId); query.Count = 20; query.FilterExpression = "$.inStock == true && $.price < 100"; query.WithScores = true; using var recommendations = await db.VectorSetSimilaritySearchAsync("products", query);附:API 与 Redis 命令映射速查
以上各 API 的底层命令映射可直接在 RedisDatabase.VectorSets.cs 中核对,接口声明见 IDatabaseAsync.VectorSets.cs,集成验证用例见 VectorSetIntegrationTests.cs:
| API(Async 形态) | Redis 命令 | 用途 |
|---|---|---|
VectorSetAddAsync | VADD | 添加成员与向量(含SETATTR/REDUCE/CAS/量化/HNSW 参数) |
VectorSetSimilaritySearchAsync | VSIM | 按成员(ELE)或向量(FP32)相似度搜索 |
VectorSetLengthAsync/VectorSetDimensionAsync | VCARD/VDIM | 集合基数 / 向量维度 |
VectorSetInfoAsync | VINFO | 集合元数据(量化、维度、基数、HNSW 状态) |
VectorSetContainsAsync | VISMEMBER | 成员存在性检查 |
VectorSetRemoveAsync | VREM | 移除成员 |
VectorSetRandomMember(s)Async | VRANDMEMBER | 随机成员抽样 |
VectorSetRangeAsync/VectorSetRangeEnumerateAsync | VRANGE | 字典序范围查询 / 分批枚举 |
VectorSetGetApproximateVectorAsync | VEMB | 获取成员的近似向量 |
VectorSetGetAttributesJsonAsync/VectorSetSetAttributesJsonAsync | VGETATTR/VSETATTR | 读写 JSON 属性 |
VectorSetGetLinksAsync/VectorSetGetLinksWithScoresAsync | VLINKS(可选WITHSCORES) | 查看 HNSW 图链接 |
结语
StackExchange.Redis 对 Redis Vector Sets 提供了完整且强类型的客户端支持:从VADD写入、VSIM相似度搜索、VRANGE范围枚举,到 HNSW 索引调参、量化与维度缩减,再到Lease<T>池化内存管理,均可通过统一的IDatabaseAPI 高效使用。建议在生产中遵循本文的性能要点——批量管道写入、按需量化、过滤缩小搜索空间、用异步枚举处理大结果集,并在引入前确认 Redis 服务端为 8.0 及以上版本。
- 后端
- 缓存
- 数据库客户端
- 消息队列
【免费下载链接】StackExchange.Redis
The Redis client for .NET
相关推荐
Redis搜索模块:RediSearch使用
Redis搜索模块:RediSearch使用 Redis作为高性能的键值对数据库,除了基础的数据结构支持外,还通过模块系统扩展了更多高级功能。RediSearc
数据库缓存KV存储消息队列不装任何客户端,怎么拿到 8 大网盘的网盘直链地址
不装任何客户端,怎么拿到 8 大网盘的网盘直链地址 把资料从网盘搬到新设备时,逐个安装各家的官方客户端是条弯路——很多平台连 Linux 版都没有。免费开源的油
前端Redis Vector Sets 实战指南:HNSW 向量检索与混合过滤搜索完全解析
Redis Vector Sets 实战指南:HNSW 向量检索与混合过滤搜索完全解析 导读 Redis Vector Sets 是 Redis 8 起内置的一
数据库缓存KV存储消息队列
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考