Valkey: 搜索与 JSON

使用 Valkey 搜索模块存储和查询 JSON 文档

👋 欢迎查阅 Stackhero 文档!

Stackhero 提供即开即用的 Valkey 云端 解决方案,带来多项优势,包括:

  • 集成 Valkey Admin Web 管理界面
  • 无限制的消息大小与传输。
  • 一键完成升级,操作便捷。
  • 基于专属私有基础设施,实现卓越性能与强大安全性

节省时间简化您的工作流程:只需 5 分钟即可体验 Stackhero 的 Valkey 云托管 方案!

您在 Stackhero 上的 Valkey 实例内置了由 Valkey 项目维护的核心模块。这些模块让 Valkey 不仅仅是一个缓存:您可以原生存储 JSON 文档,对数据建立索引,并执行标签、数值、文本和向量查询,无需额外管理独立数据库。

目前提供三大模块,全部包含在您的套餐中,并采用与 Valkey 相同的宽松 BSD 3-Clause 许可协议:

  • JSONJSON.* 命令):支持 JSONPath,存储和更新结构化 JSON 文档。您可以直接读取或修改单个字段,无需为小变更重写整个文档。
  • SearchFT.* 命令):支持二级索引、标签、数值、全文检索及向量相似度查询。
  • BloomBF.* 命令):利用 Bloom filter 高效判断“大数据集中某项是否已出现”,极大节省内存。

模块默认处于关闭状态,因此您的现有服务会保持当前行为,直到您手动启用。

  1. Stackhero 控制台 打开您的服务。
  2. 进入服务配置页面。
  3. Modules(模块)部分,选择您要启用的模块。
  4. 保存更改。

Valkey 会重启以加载所选模块,通常只需几秒钟。

如果您已通过某个模块(如 JSON 文档或 Bloom filter)存储了数据,请务必保持该模块处于启用状态。Valkey 无法在缺少创建数据的模块时读取这些数据。禁用该模块将导致服务无法成功重启。

您可以随时检查当前已加载的模块:

valkey-cli -u "rediss://default:<yourPassword>@<XXXXXX>.stackhero-network.com:<PORT_TLS>" MODULE LIST

您可以直接以 JSON 格式原生存储结构化文档,无需序列化为字符串:

JSON.SET product:1 $ '{"name":"Espresso machine","brand":"Bianca","price":459,"stock":12}'

读取或更新单个字段操作快速、原子且由服务端处理:

JSON.GET product:1 $.price
# "[459]"

JSON.NUMINCRBY product:1 $.stock -1
# "[11]"

这种方式无需每次更新都在应用端获取并解析整个文档。例如,减少库存计数器现在只需一次原子操作,无需担心并发冲突或额外代码。

JSON.* 命令集与 RedisJSON 的 API 保持一致,因此大多数 Valkey 和 Redis 客户端无需修改即可兼容。

只需创建一次索引,Valkey 会自动为所有匹配前缀的 key 保持索引最新,甚至包括索引创建前已存在的 key:

FT.CREATE productsIndex
  ON HASH
  PREFIX 1 product:
  SCHEMA
    name TEXT
    brand TAG
    price NUMERIC

您可以通过强大的搜索命令查询已索引数据:

# 标签精确匹配
FT.SEARCH productsIndex "@brand:{Bianca}"

# 数值范围
FT.SEARCH productsIndex "@price:[0 500]"

# 组合查询
FT.SEARCH productsIndex "@brand:{Bianca} @price:[0 500]"

# 按名称全文检索
FT.SEARCH productsIndex "espresso"

您还可以使用 FT.AGGREGATE 在同一索引上进行聚合分析。FT.INFO 命令可用于检查索引状态,包括后台索引任务的进度。

JSON 文档同样支持索引。当启用 JSON 模块后,您可以在 schema 中使用 ON JSON 及 JSONPath 表达式。

由于 Valkey 与 Redis 协议兼容,任何 Redis 客户端都可直接使用。以下示例适用于官方 node-redis 客户端

import { createClient, SCHEMA_FIELD_TYPE } from 'redis';

const client = createClient({ url: process.env.STACKHERO_VALKEY_URL_TLS });
await client.connect();

// 启动时仅需创建一次索引
try {
  await client.ft.create(
    'productsIndex',
    {
      name: SCHEMA_FIELD_TYPE.TEXT,
      brand: SCHEMA_FIELD_TYPE.TAG,
      price: SCHEMA_FIELD_TYPE.NUMERIC
    },
    { ON: 'HASH', PREFIX: 'product:' }
  );
} catch (error) {
  if (!error.message.includes('Index already exists')) {
    throw error;
  }
}

await client.hSet('product:1', { name: 'Espresso machine', brand: 'Bianca', price: '459' });

const results = await client.ft.search('productsIndex', '@brand:{Bianca} @price:[0 500]');
console.log(results.total, results.documents);

await client.quit();

您可以通过 redis-py 客户端 调用 Valkey 搜索功能:

import os
import redis
from redis.commands.search.field import TextField, TagField, NumericField
from redis.commands.search.index_definition import IndexDefinition, IndexType
from redis.commands.search.query import Query

r = redis.from_url(os.environ['STACKHERO_VALKEY_URL_TLS'], decode_responses=True)

# 启动时仅需创建一次索引
try:
    r.ft('productsIndex').create_index(
        (TextField('name'), TagField('brand'), NumericField('price')),
        definition=IndexDefinition(prefix=['product:'], index_type=IndexType.HASH),
    )
except redis.ResponseError as error:
    if 'Index already exists' not in str(error):
        raise

r.hset('product:1', mapping={'name': 'Espresso machine', 'brand': 'Bianca', 'price': 459})

results = r.ft('productsIndex').search(Query('@brand:{Bianca} @price:[0 500]'))
print(results.total, results.docs)
  • 搜索模块提供了一组专注的命令FT.CREATEFT.SEARCHFT.AGGREGATEFT.INFOFT.DROPINDEXFT._LIST。这些命令高效支持标签、数值、文本和向量查询。如果您的应用需要更高级的全文检索功能(如拼写检查、同义词或建议词典),可参考 Redis 查询引擎 获取更丰富的能力。
  • 索引按数据库隔离,并在每个数据库内生效。 例如,在数据库 3 创建的索引,仅能在数据库 3 内被查询和访问。这是 Valkey 相较于 Redis 数据库选择 的扩展,后者仅支持在数据库 0 上建立索引。
  • 索引占用您的套餐内存,与数据一样。您可以在 Prometheus 指标 中监控 used_memory,以便在构建索引后查看内存占用。
  • 对现有 key 的索引在后台异步进行。 执行 FT.CREATE 后,搜索结果可能在几秒内不完整,待索引构建完成后即可正常使用。
  • 所有模块命令均已启用。 您的服务可使用所有模块的全部命令,无任何限制。

如需探索向量相似度、语义搜索或检索增强生成(RAG),请参阅 向量搜索指南