Redis 提供了丰富的数据结构可以很好地适配各种领域应用。RedisVL 作为一个 Python 客户端库让您能方便地使用 Redis 的搜索和查询功能并支持多种存储格式。本文介绍如何使用 RedisVL 操作Hash和JSON两种存储类型。前置条件安装了 RedisVLpip install redisvl有一台运行中的 Redis 实例Redis 8 或 Redis Cloud 均可要点理解Hash与JSON存储类型的本质差异分别为 Hash 和 JSON 定义搜索索引Schema使用两种存储类型加载数据和执行查询通过 JSONPath 表达式访问 JSON 中的嵌套字段根据应用需求做出合适的存储类型决策示例数据集预览包含用户信息的小型数据集包含姓名、年龄、职业、信用评分、办公位置、用户向量3 维和最后更新时间。先用pickle加载数据并用辅助函数打印importpicklefromredisvl.redis.utilsimportbuffer_to_arrayfromredisvl.indeximportSearchIndex# 加载示例数据datapickle.load(open(hybrid_example_data.pkl,rb))# 辅助打印函数非标准库仅用于展示fromjupyterutilsimportresult_print,table_print table_print(data)useragejobcredit_scoreoffice_locationuser_embeddinglast_updatedjohn18engineerhigh-122.4194,37.7749b’\xcd\xcc\xcc\xcd\xcc\xcc\x00\x00\x00?’1741627789derrick14doctorlow-122.4194,37.7749b’\xcd\xcc\xcc\xcd\xcc\xcc\x00\x00\x00?’1741627789nancy94doctorhigh-122.4194,37.7749b’333?\xcd\xcc\xcc\x00\x00\x00?’1710696589tyler100engineerhigh-122.0839,37.3861b’\xcd\xcc\xcc\xcd\xcc\xcc\x00\x00\x00?’1742232589tim12dermatologisthigh-122.0839,37.3861b’\xcd\xcc\xcc\xcd\xcc\xcc\x00\x00\x00?’1739644189taimur15CEOlow-122.0839,37.3861b’\x9a\x99\x19?\xcd\xcc\xcc\x00\x00\x00?’1742232589joe35dentistmedium-122.0839,37.3861b’fff?fff?\xcd\xcc\xcc’1742232589说明user_embedding列中是序列化后的向量字节串适用于 Hash 存储。后面处理 JSON 时将其转换为浮点数组。Hash vs JSONRedis 的 Hash 和 JSON 是两种截然不同的数据结构它们各有特点和适用场景。下面用一个清晰的对比来理解。Hash哈希Hash 在 Redis 中相当于一个扁平的、单层的键值对集合就像一张只有一行的表每个字段对应一个值。例如{model:Deimos,brand:Ergonom,type:Enduro bikes,price:4972}优点性能卓越内存占用小读写速度极快特别是对于大量简单字段。存储高效适合存储规范化、结构固定的数据。缺点不支持嵌套所有字段必须在同一层级无法表示复杂对象。向量必须序列化向量数据需以字节串形式存储稍后详述。适用场景您最关心速度和内存开销。数据可以方便地映射为“字段-值”的字典无需嵌套。默认推荐在没有特殊需求时优先考虑 Hash。JSONJavaScript Object NotationJSON 是 Redis 原生支持的一种文档格式可以包含多层嵌套对象和数组。例如{name:Specialized Stump jumper,metadata:{model:Stumpjumper,brand:Specialized,type:Enduro bikes,price:3000}}优点灵活建模可以表示任意复杂的嵌套结构贴近应用层的对象模型。原生支持许多应用已经使用 JSON 作为数据交换格式迁移成本低。JSONPath 支持可以精确更新或查询嵌套子元素。缺点性能开销相比 Hash 稍大内存和 CPU 消耗但现代 Redis 对此有很好优化。向量存储为数组向量需以浮点数列表形式存储稍后详述。适用场景数据本身已经是 JSON 格式或者需要表达嵌套关系。您希望用一套方案替代其他文档数据库如 MongoDB。需要灵活检索嵌套字段。决策流程图下面用一张图帮您快速判断该选哪种存储类型是否是否是否开始选择存储类型数据是否有嵌套结构JSON是否追求极致性能和内存Hash数据是否已是 JSON 格式使用 JSON 存储使用 Hash 存储使用 Hash 存储1. 定义 Hash 索引结构在 RedisVL 中索引模式Schema通过字典定义。对于 Hash我们在index部分指定storage_type: hash该值为默认值可以省略。字段定义与普通搜索索引一致包含标签、文本、数值、地理位置和向量等类型。hash_schema{index:{name:user-hash,# 索引名称prefix:user-hash-docs,# 存储键的前缀storage_type:hash,# 指定为 Hash 存储默认},fields:[{name:user,type:tag},{name:credit_score,type:tag},{name:job,type:text},{name:age,type:numeric},{name:office_location,type:geo},{name:user_embedding,type:vector,attrs:{dims:3,distance_metric:cosine,algorithm:flat,datatype:float32}}],}2. 创建索引并加载数据利用SearchIndex.from_dict构造索引对象然后调用create方法在 Redis 中创建索引。最后使用load方法批量加载数据。# 构建索引对象hindexSearchIndex.from_dict(hash_schema,redis_urlredis://localhost:6379)# 创建索引如果已存在则覆盖hindex.create(overwriteTrue)# 查看存储类型print(hindex.storage_type)# StorageType.HASH: hash重要向量字段处理Hash 专用在 Hash 存储中向量数据必须以**字节串bytes**形式存储这是为了在 Redis 内部高效索引和计算。示例数据中的user_embedding已经是字节串因此可以直接加载# 加载数据每条记录自动以 prefix 唯一 ID 存入 Hashkeyshindex.load(data)print(keys)# 返回存储的键列表3. 检查索引统计信息您可以使用 RedisVL 命令行工具查看索引状态例如文档数、向量索引大小等rvl stats-iuser-hash输出类似Statistics: ╭─────────────────────────────┬────────────╮ │ Stat Key │ Value │ ├─────────────────────────────┼────────────┤ │ num_docs │ 7 │ │ num_terms │ 6 │ │ ... │ ... │ │ vector_index_sz_mb │ 0.02820587 │ ╰─────────────────────────────┴────────────╯4. 执行查询RedisVL 的查询构造器支持组合条件标签、文本、数值和向量相似性搜索。组合过滤器例如查询信用分高、职业包含 “engineer”支持通配符、年龄大于 17 岁的用户fromredisvl.queryimportVectorQueryfromredisvl.query.filterimportTag,Text,Num# 构建过滤条件filter_expr(Tag(credit_score)high)(Text(job)%enginee*)(Num(age)17)# 构造向量查询queryVectorQuery(vector[0.1,0.1,0.5],# 查询向量vector_field_nameuser_embedding,# 向量字段名return_fields[user,credit_score,age,job,office_location],filter_expressionfilter_expr)# 执行查询resultshindex.query(query)result_print(results)结果会按照向量相似度余弦距离升序排列并返回指定的字段vector_distanceusercredit_scoreagejoboffice_location0johnhigh18engineer-122.4194,37.77490.109129190445tylerhigh100engineer-122.0839,37.3861距离为 0 表示查询向量与 john 的向量完全相同因为示例数据中 john 的向量正好是 [0.1, 0.1, 0.5]。5. 清理索引hindex.delete()使用 JSON 存储1. 定义 JSON 索引结构JSON 的索引定义与 Hash 几乎相同唯一区别在于storage_type: json。json_schema{index:{name:user-json,prefix:user-json-docs,storage_type:json,# 显式指定 JSON},fields:[# 字段定义与 Hash 一致{name:user,type:tag},{name:credit_score,type:tag},{name:job,type:text},{name:age,type:numeric},{name:office_location,type:geo},{name:user_embedding,type:vector,attrs:{dims:3,distance_metric:cosine,algorithm:flat,datatype:float32}}],}2. 创建索引jindexSearchIndex.from_dict(json_schema,redis_urlredis://localhost:6379)jindex.create(overwriteTrue)3. 数据格式转换向量字段对于 JSON 存储向量字段必须是浮点数列表Python list而不是字节串。我们需要将数据中的user_embedding由字节串转换为数组json_datadata.copy()fordinjson_data:d[user_embedding]buffer_to_array(d[user_embedding],dtypefloat32)转换后的记录示例{user:john,age:18,job:engineer,credit_score:high,office_location:-122.4194,37.7749,user_embedding:[0.1,0.1,0.5],# 变为列表last_updated:1741627789}然后加载数据keysjindex.load(json_data)4. 执行同样的查询由于字段名和类型一致我们可以复用之前定义的VectorQuery对象result_print(jindex.query(query))结果与 Hash 版本完全相同。5. 清理jindex.delete()JSON 嵌套数据与 JSONPath 支持JSON 的“王牌”功能是支持嵌套对象。当您需要索引深层字段时必须在模式中指定路径path格式为$.object.attribute。如果未指定pathRedisVL 默认使用$.{name}即根级字段。下面我们用一个自行车商品示例来演示如何索引嵌套元数据并基于向量检索。1. 生成向量嵌入我们使用 HuggingFace 文本向量化工具HFTextVectorizer将描述文字转换为向量fromredisvl.utils.vectorizeimportHFTextVectorizer emb_modelHFTextVectorizer()bike_data[{name:Specialized Stump jumper,metadata:{model:Stumpjumper,brand:Specialized,type:Enduro bikes,price:3000},description:The Specialized Stumpjumper is a versatile enduro bike that dominates both climbs and descents. Features a FACT 11m carbon fiber frame, FOX FLOAT suspension with 160mm travel, and SRAM X01 Eagle drivetrain. The asymmetric frame design and internal storage compartment make it a practical choice for all-day adventures.},{name:bike_2,metadata:{model:Slash,brand:Trek,type:Enduro bikes,price:5000},description:Treks Slash is built for aggressive enduro riding and racing. Featuring Treks Alpha Aluminum frame with RE:aktiv suspension technology, 160mm travel, and Knock Block frame protection. Equipped with Bontrager components and a Shimano XT drivetrain, this bike excels on technical trails and enduro race courses.}]# 为每条数据生成向量嵌入并添加到原字典中bike_data[{**d,bike_embedding:emb_model.embed(d[description])}fordinbike_data]2. 定义索引模式含 JSONPath在字段定义中使用path指向嵌套字段bike_schema{index:{name:bike-json,prefix:bike-json,storage_type:json,},fields:[{name:model,type:tag,path:$.metadata.model# 指向 metadata.model},{name:brand,type:tag,path:$.metadata.brand},{name:price,type:numeric,path:$.metadata.price},{name:bike_embedding,type:vector,attrs:{dims:len(bike_data[0][bike_embedding]),distance_metric:cosine,algorithm:flat,datatype:float32}}],}3. 创建索引并加载数据bike_indexSearchIndex.from_dict(bike_schema,redis_urlredis://localhost:6379)bike_index.create(overwriteTrue)bike_index.load(bike_data)4. 查询并返回嵌套字段我们使用自然语言查询“I’d like a bike for aggressive riding”我想要一辆适合激进骑行的自行车将其向量化后执行检索。在return_fields中如果想返回未索引的嵌套字段例如type必须提供完整的 JSONPath$.metadata.typequery_vectoremb_model.embed(Id like a bike for aggressive riding)queryVectorQuery(vectorquery_vector,vector_field_namebike_embedding,return_fields[brand,# 索引字段直接返回name,# 根级字段$.metadata.type# 非索引字段需用完整路径])resultsbike_index.query(query)print(results)输出[{id:bike-json:01KHKJ5WW3DJE0X6E85GG27V0Y,vector_distance:0.519988954067,brand:Trek,$.metadata.type:Enduro bikes},{id:bike-json:01KHKJ5WW3DJE0X6E85GG27V0X,vector_distance:0.65762424469,brand:Specialized,$.metadata.type:Enduro bikes}]注意返回字段中的$.metadata.type虽然未在索引中定义但只要提供路径Redis 仍能正确提取该值。若您希望过滤或排序该字段则必须将其加入索引字段列表并指定路径。总结与推荐特性HashJSON数据结构扁平键值对嵌套文档对象/数组向量存储格式字节串bytes浮点数列表嵌套字段访问不支持支持 JSONPath性能极高内存占用小良好但比 Hash 稍高灵活性低高适用场景标准化、固定结构、高性能需求复杂对象、现成 JSON 数据、需灵活检索最终建议如果不确定优先选择 Hash它简单高效足以应付大多数场景。当数据天然具有层级关系或者您希望保留完整的 JSON 文档结构时请选择 JSON。如果您的应用已经使用其他文档数据库迁移到 Redis JSON 可以保持相似的编程模型。