当前位置: 首页 > article >正文

Redis Stack十部曲之四:与Redis数据之间的交互

文章目录

  • 搜索和查询
    • 基础结构
    • 创建模式
    • 字段类型
      • NUMERIC
      • TAG
      • TEXT
    • 配置参数
    • 索引JSON文档
      • 创建JSON索引
      • 索引JSON对象
      • 多值索引(JSON数组和通配符JSONPath)
      • 限制
    • 查询
      • 返回值
      • 高亮搜索词
      • 精确查询
        • NUMERIC 字段
        • TAG字段
        • TEXT字段
      • 范围查询
        • 开始和结束值
        • 结果集
      • 全文搜索
        • 单词
        • 短语
        • 单词前缀
        • 单词后缀
        • 模糊查询
      • 连接查询
        • AND
        • OR
        • NOT
      • 聚合查询
        • 简单映射
        • 分组聚合
        • 不使用分组的聚合
        • 不应用数学聚合的分组
  • 事务
    • 如何使用
    • 事务中的错误
    • 事务回滚
    • 终止事务
    • 乐观锁
  • 发布和订阅
    • 消息传递语义
    • 推送消息的格式
    • 数据库与作用域
    • 模式匹配订阅
    • 分片发布/订阅

搜索和查询

你可以使用 Redis Stack 作为一个强大的搜索和查询引擎。它允许你创建索引,并对结构化数据进行高效的查询,还可以对非结构化数据进行基于文本和向量的搜索。

基础结构

  • 文档:文档是信息的基本单位,可以是任何哈希JSON 数据对象。每个文档都有一个唯一的键名来标识。
  • 字段:一个文档由多个字段组成,每个字段代表文档的一个属性或特征。字段可以包含不同类型的数据,例如字符串、数字、地理位置,甚至更复杂的结构如向量。通过为这些字段创建索引,可以根据字段值进行高效的查询和搜索。
  • 字段索引:并不是所有字段都需要索引,索引过多的字段可能会增加系统的开销。因此,您可以根据需要选择哪些字段进行索引,以优化搜索性能。索引字段后,Redis Stack 会创建一个索引结构,用于提高该字段的搜索效率。未索引的字段不会参与搜索结果,但在获取搜索结果时,仍然可以作为文档内容的一部分被返回。
  • 模式:索引的结构由模式定义,模式指定了字段的类型和其他重要信息。创建索引时,您需要为文档集合定义一个模式。模式可以帮助决定如何存储和索引每个字段,以优化查询效率。

创建模式

索引结构由模式(schema)定义,模式指定了字段、字段类型、是否应该创建索引或存储,以及其他附加配置选项。通过正确配置模式,可以优化搜索性能并控制索引的存储需求。

FT.CREATE idx 
    ON HASH 
    PREFIX 1 blog:post: 
SCHEMA 
    title TEXT WEIGHT 5.0
    content TEXT
    author TAG
    created_date NUMERIC SORTABLE
    views NUMERIC

在这个示例中,定义了名为 idx 的索引,索引所有键名以 blog:post: 开头的哈希文档。模式包含了字段 titlecontentauthorcreated_dateviewstitlecontent 是文本类型,author 是标签类型,created_dateviews 是数值类型。此外,title 字段的权重设为 5.0,以提高其在搜索结果中的优先级,created_date 被标记为 SORTABLE,以便可以基于该字段进行排序。可以在 FT.CREATE 页面 查看更多可用的字段类型和选项。

字段类型

Redis Stack提供了各种字段类型,允许您在索引中存储和搜索不同类型的数据。本节解释了可用的字段类型、它们的特性以及如何有效地使用它们。

NUMERIC

数字字段用于存储非文本的可计数值。它们可以保存整数或浮点数值。数字字段是可排序的,这意味着您可以执行基于范围的查询,并根据特定的数值条件检索文档。您可以使用以下语法将数字字段添加到FT.CREATE中的模式中:

FT.CREATE ... SCHEMA ... {field_name} NUMBER [SORTABLE] [NOINDEX]

其中:

  • SORTABLE 表示字段可排序。这对于执行范围查询并根据数值对搜索结果进行排序非常有用。
  • NOINDEX 表示该字段未被索引。这对于存储您不希望搜索但希望在搜索结果中检索的数值很有用。

TAG

标签字段用于存储文本数据,表示一组数据标签或分类。标签字段的特点是低基数,即通常只有有限数量的不同值。与文本字段不同,标签字段以原样存储,不进行分词或词干提取。这使得它们在组织和分类数据时非常有用,可以更方便地根据特定标签过滤和检索文档。可以使用以下语法将标签字段添加到模式中:

FT.CREATE ... SCHEMA ... {field_name} TAG [SEPARATOR {sep}] [CASESENSITIVE]
  • SEPARATOR:默认为逗号(,),可以是任何可打印的 ASCII 字符,用于分隔字段值中的标签。例如,如果字段值为 hello,world,则标签为 helloworld

  • CASESENSITIVE:指示字段是否区分大小写。默认情况下,标签字段不区分大小写。

有关标签字段的更多信息,请参见标签字段文档。

TEXT

文本字段专门用于存储自然语言文本。在对文本字段进行索引时,Redis Stack 会执行一系列优化处理。首先,文本会被转换为小写,以便进行不区分大小写的搜索。接着,数据会被分词,将其拆分为独立的词或标记,从而实现高效的全文搜索功能。文本字段还可以通过权重机制赋予不同的字段不同的搜索优先级。此外,文本字段的值可以排序,从而根据相关性或其他条件对搜索结果进行排序。可以使用以下语法将文本字段添加到模式中:

FT.CREATE ... SCHEMA ... {field_name} TEXT [WEIGHT] [NOSTEM] [PHONETIC {matcher}] [SORTABLE] [NOINDEX] [WITHSUFFIXTRIE]

各参数的含义如下:

  • WEIGHT:为字段设置权重,用于在搜索操作中赋予特定字段更高的优先级。

  • NOSTEM:表示该字段不进行词干处理,适用于不希望对内容进行分词处理的文本(如 URL 或电子邮件地址)。

  • PHONETIC {matcher}:声明该文本字段支持语音匹配。匹配器参数指定了使用的语音算法和语言。支持以下几种匹配器:

    • dm:en:英文的双语音码算法(Double Metaphone)
    • dm:fr:法文的双语音码算法
    • dm:pt:葡萄牙文的双语音码算法
    • dm:es:西班牙文的双语音码算法
  • SORTABLE:表示该字段可用于排序,适用于基于文本值进行范围查询或结果排序的场景。

  • NOINDEX:表示该字段不创建索引,适用于存储你不需要进行搜索但希望在搜索结果中检索到的文本内容。

  • WITHSUFFIXTRIE:表示该字段将使用后缀 Trie 进行索引。该选项用于优化包含 (*foo*) 或后缀 (*foo) 查询。如果某些字段存在后缀 Trie,则这些查询将禁用其他字段的此类查询,以避免暴力搜索。

配置参数

在Redis Stack中进行查询和搜索可以通过多个配置参数进行调优。其中一些参数只能在加载时设置,而其他参数可以在加载时或运行时设置。

  • 在模块加载时设置配置参数:在加载时设置配置参数是通过在从命令行启动服务器时在 --loadmodule 参数后添加参数,或者在 Redis 配置文件中的 loadmodule 指令后添加参数。例如:
#在redis.conf
loadmodule ./redisearch.so [OPT VAL]...
#在Redis CLI
127.0.0.6379> MODULE LOAD redisearch.so [OPT VAL]...
#在命令行
$ redis-server --loadmodule ./redisearch.so [OPT VAL]...
  • 在运行时设置配置参数:RediSearch 提供了 FT.CONFIG 端点,允许在运行时设置和检索配置参数,使用FT.CONFIG SET设置的值在服务器重启后不会被保留。要在运行时设置配置参数的值,只需运行:
FT.CONFIG SET OPT1 VAL1

类似地,您可以使用以下命令检索当前配置参数值:

FT.CONFIG GET OPT1
FT.CONFIG GET *

有关哪些参数可以设置以及它们的含义请查阅官方文档。

索引JSON文档

创建JSON索引

使用 FT.CREATE 命令创建索引时,包含 ON JSON 关键字来索引数据库中的所有现有和未来的 JSON 文档。此时可以使用 JSONPath 表达式。每个 JSONPath 表达式的结果都会被索引,并与一个称为属性的逻辑名称关联。您可以在查询中使用这些属性:

FT.CREATE {index_name} ON JSON SCHEMA {json_path} AS {attribute} {type}

在你创建索引后,Redis Stack 会自动索引数据库中任何现有的、已修改的或新创建的 JSON 文档。对于现有文档,索引在后台异步运行,因此在文档可用之前可能需要一些时间。而已修改和新创建的文档则是同步索引的,因此在添加或修改命令完成时,文档就可以使用了。

索引JSON对象

你不能索引JSON对象。如果JSONPath表达式返回一个对象,它将被忽略。要索引JSON对象的内容,你需要在创建索引时将对象中的每个元素作为单独的属性进行索引。例如,要索引connection JSON对象,可以在创建索引时将$.connection.wireless$.connection.type字段定义为单独的属性:

127.0.0.1:6379> FT.CREATE itemIdx3 ON JSON SCHEMA $.connection.wireless AS wireless TAG $.connection.type AS connectionType TEXT
"OK"

多值索引(JSON数组和通配符JSONPath)

可以使用以下两种方式索引具有多值术语的 JSON 字段:

  • 首选方法是使用 JSON 数组。数组中的每个值都会被索引,这些值必须是标量。
  • 还可以使用带有通配符指向多个值的JSONPath。

限制

在创建索引时,需要将JSON元素映射到模式字段,如下所示:

  • 字符串作为TEXTTAG
  • 数字作为NUMERIC
  • 布尔值作为TAG
  • JSON数组:
    • 字符串数组作为TAGTEXT
    • 数字数组作为NUMERIC
  • 索引类型不正确的值会导致索引失败
  • null值会被忽略
  • SORTBY 只根据第一个值进行排序
  • 不支持 HIGHLIGHTSUMMARIZE

查询

Redis Stack区分FT.SEARCHFT.AGGREGATE查询命令。如果你只想执行选择和投影,使用FT.SEARCH;如果还需要应用映射函数、分组或聚合数据,则使用FT.AGGREGATE命令。

  • 选择:返回满足特定条件的所有文档。
  • 投影:用于返回结果集的特定字段,也可以映射/投影到计算字段值。
  • 聚合:收集并总结多个字段的数据。

以下是使用自行车数据集的简短SQL比较:

类型SQLRedis Stack
选择SELECT * FROM bicycles WHERE price >= 1000FT.SEARCH idx:bicycle "@price:[1000 +inf]"
简单投影SELECT id, price FROM bicyclesFT.SEARCH idx:bicycle "*" RETURN 2 __key, price
计算投影SELECT id, price-price*0.1 AS discounted FROM bicyclesFT.AGGREGATE idx:bicycle "*" LOAD 2 __key price APPLY "@price-@price*0.1" AS discounted
聚合SELECT condition, AVG(price) AS avg_price FROM bicycles GROUP BY conditionFT.AGGREGATE idx:bicycle "*" GROUPBY 1 @condition REDUCE AVG 1 @price AS avg_price

返回值

当你运行搜索查询时,可以使用RETURN关键字指定希望在搜索结果中包含哪些属性。同时,你还需要指定返回字段的数量。例如,这个查询仅返回每副耳机的名称和价格:

127.0.0.1:6379> FT.SEARCH itemIdx '@description:(headphones)' RETURN 2 name price
1) "2"
2) "item:1"
3) 1) "name"
   2) "噪声取消蓝牙耳机"
   3) "price"
   4) "99.98"
4) "item:2"
5) 1) "name"
   2) "无线耳塞"
   3) "price"
   4) "64.99"

你可以在RETURN语句中使用JSONPath表达式,以提取JSON文档的任何部分,即使是未在模式中定义的字段。例如,以下查询使用JSONPath表达式$.stock返回每个项的库存,此外还包括名称和价格属性:

127.0.0.1:6379> FT.SEARCH itemIdx '@description:(headphones)' RETURN 3 name price $.stock
1) "2"
2) "item:1"
3) 1) "name"
   2) "噪声取消蓝牙耳机"
   3) "price"
   4) "99.98"
   5) "$.stock"
   6) "25"
4) "item:2"
5) 1) "name"
   2) "无线耳塞"
   3) "price"
   4) "64.99"
   5) "$.stock"
   6) "17"

请注意,返回的属性名称就是JSONPath表达式本身:"$.stock"。你可以使用AS选项为返回的属性指定别名:

127.0.0.1:6379> FT.SEARCH itemIdx '@description:(headphones)' RETURN 5 name price $.stock AS stock
1) "2"
2) "item:1"
3) 1) "name"
   2) "噪声取消蓝牙耳机"
   3) "price"
   4) "99.98"
   5) "stock"
   6) "25"
4) "item:2"
5) 1) "name"
   2) "无线耳塞"
   3) "price"
   4) "64.99"
   5) "stock"
   6) "17"

注意:

  • 如果通过 RETURN 指定的是一个模式属性,其 JSONPath 指向多个值,则只返回第一个值(作为 JSON 字符串)
  • 如果通过 RETURN 指定的是 JSONPath,而不是 Schema 属性,则会返回所有值(作为 JSON 字符串)

高亮搜索词

你可以在任何已索引的TEXT属性中高亮相关的搜索词。对于FT.SEARCH,你需要在RETURNHIGHLIGHT参数后明确设置要高亮的属性。使用可选的TAGS关键字来指定将包围(或高亮)匹配搜索词的字符串。例如,用HTML的粗体标签高亮项名称和描述中的"bluetooth"这个词:

127.0.0.1:6379> FT.SEARCH itemIdx '(@name:(bluetooth))|(@description:(bluetooth))' RETURN 3 name description price HIGHLIGHT FIELDS 2 name description TAGS '<b>' '</b>'
1) "2"
2) "item:1"
3) 1) "name"
   2) "噪声取消 <b>Bluetooth</b> 耳机"
   3) "description"
   4) "无线 <b>Bluetooth</b> 耳机,带有噪声取消技术"
   5) "price"
   6) "99.98"
4) "item:2"
5) 1) "name"
   2) "无线耳塞"
   3) "description"
   4) "无线 <b>Bluetooth</b> 入耳式耳机"
   5) "price"
   6) "64.99"

在这个查询中,"bluetooth"这个词在结果中被包围在<b></b>标签之间,从而实现高亮显示。

精确查询

精确匹配查询允许你选择所有字段值与特定值匹配的文档。你可以在多个字段类型上使用精确匹配查询。查询语法会根据类型的不同而有所变化。本文中的示例使用以下字段的模式:

字段名称字段类型
descriptionTEXT
conditionTAG
priceNUMERIC
NUMERIC 字段

要在数字字段上执行精确匹配查询,你需要构建一个范围查询,其起始值和结束值相同:

  • FT.SEARCH index "@field:[value value]"

或者

  • FT.SEARCH index "@field:[value]" DIALECT 2 # 需要v2.10

或者

  • FT.SEARCH index "@field==value" DIALECT 2 # 需要v2.10

或者:

  • FT.SEARCH index "*" FILTER field start end

以下示例展示了如何查询价格正好为270美元的自行车:

> FT.SEARCH idx:bicycle "@price:[270 270]"
1) (integer) 1
2) "bicycle:0"
3) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-74.0610 40.7578, ..."

> FT.SEARCH idx:bicycle "@price:[270]" # 需要v2.10
1) (integer) 1
2) "bicycle:0"
3) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-74.0610 40.7578, ..."

> FT.SEARCH idx:bicycle "@price==270" # 需要v2.10
1) (integer) 1
2) "bicycle:0"
3) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-74.0610 40.7578, ..."

> FT.SEARCH idx:bicycle "*" FILTER price 270 270
1) (integer) 1
2) "bicycle:0"
3) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-74.0610 40.7578, ..."
TAG字段

标签是短文本序列,例如“new”或“Los Angeles”。重要的是,如果你需要查询短文本,请使用标签查询,而不是全文查询。标签字段在存储索引条目时更节省空间,并且通常会导致精确匹配查询的复杂性降低。你可以通过以下方式构建单个标签的标签查询(大括号是标签查询的强制要求):

FT.SEARCH index "@field:{tag}"

以下是查询新自行车的示例:

> FT.SEARCH idx:bicycle "@condition:{new}"
1) (integer) 5
2) "bicycle:0"
3) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-74.0610 40.7578, ..."
4) "bicycle:5"
5) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((-0.1778 51.5524, ..."
6) "bicycle:6"
7) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((2.1767 48.9016, ..."
8) "bicycle:7"
9) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((13.3260 52.5700, ..."
10) "bicycle:8"
11) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((1.9450 41.4301, ..."

在涉及特殊字符的标签的精确匹配查询中,应该同时使用双引号和DIALECT 2。在涉及双引号标签的查询中,唯一需要转义的字符是双引号字符。以下是使用包含特殊字符的双引号标签的示例:

> FT.CREATE idx:email ON JSON PREFIX 1 key: SCHEMA $.email AS email TAG
OK
> JSON.SET key:1 $ '{"email": "test@redis.com"}'
OK
> FT.SEARCH idx:email '@email:{"test@redis.com"}' DIALECT 2
1) (integer) 1
2) "key:1"
3) 1) "$"
   2) "{\"email\":\"test@redis.com\"}"
TEXT字段

要进行全文查询,可以使用以下格式来查找文本字段中确切匹配的短语(短语必须用转义的双引号包裹,以进行精确匹配):

FT.SEARCH index "@field:\"phrase\""

以下是查找所有描述中包含确切文本“rough terrain”的自行车的示例:

> FT.SEARCH idx:bicycle "@description:\"rough terrain\""
1) (integer) 1
2) "bicycle:8"
3) 1) "$"
   2) "{\"pickup_zone\":\"POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, 1.9450 41.1987, 1.9450 41.4301))\",\"store_location\":\"2.1734, 41.3851\",\"brand\":\"nHill\",\"model\":\"Summit\",\"price\":1200,\"description\":\"This budget mountain bike from nHill performs well both on bike paths and on the trail. The fork with 100mm of travel absorbs rough terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. The Shimano Tourney drivetrain offered enough gears for finding a comfortable pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. Whether you want an affordable bike that you can take to work, but also take trail in mountains on the weekends or you\xe2\x80\x99re just after a stable, comfortable ride for the bike path, the Summit gives a good value for money.\",\"condition\":\"new\"}"

范围查询

要在数值字段上进行范围查询,可以使用以下格式,返回介于给定起始值和结束值之间的值:

FT.SEARCH index "@field:[start end]"

此外,您还可以使用 FILTER 参数,但需要注意的是,查询执行计划不同,因为过滤是在查询字符串(例如 *)评估之后应用的:

FT.SEARCH index "*" FILTER field start end

这两种方法都可以用于查询满足特定数值范围的文档。

开始和结束值

默认情况下,开始和结束值是包含在范围内的。如果要排除某个值,可以在该值前添加 ( 符号。 此外,-infinf+inf 是有效值,允许您定义开放范围。例如:

FT.SEARCH index "@field:[100 (200]"  # 包含100,排除200
FT.SEARCH index "@field:[-inf 100]"  # 查询小于等于100的值
FT.SEARCH index "@field:[200 +inf]"  # 查询大于等于200的值
结果集

使用 LIMITSORTBY 参数可以更好地控制结果集的大小和顺序。以下是示例:

FT.SEARCH index "@field:[100 200]" SORTBY price LIMIT 0 10

在这个查询中,SORTBY price 按价格排序结果,LIMIT 0 10 返回前10个匹配的文档。您可以调整 LIMIT 的参数来浏览更大的结果集。有关更多信息,可以参考 FT.SEARCH 命令参考。

全文搜索

在 Redis 中,您可以使用 FT.SEARCH 命令进行全文搜索。您可以在特定的文本字段中搜索单词或短语,或者在所有文本字段中进行搜索。示例中的模式包含以下字段:

字段名称字段类型
brandTEXT
modelTEXT
descriptionTEXT
单词

要在所有文本字段中搜索一个单词,可以构建以下简单查询:

FT.SEARCH index "word"

如果您想将搜索限制在特定的文本字段,可以使用:

FT.SEARCH index "@field: word"

在自然语言中,像 “the” 或 “a” 这样的常见词不会被索引,因此不会返回搜索结果。有关更多详细信息,请参阅停用词。以下示例搜索描述中包含 “kids” 一词的所有自行车:

FT.SEARCH idx:bicycle "@description: kids"
短语

见TEXT字段。

单词前缀

您还可以搜索与给定前缀(前缀至少需要两个字符长)匹配的单词。

FT.SEARCH index "prefix*"
FT.SEARCH index "@field: prefix*"

以下示例显示如何搜索品牌以“ka”开头的自行车:

FT.SEARCH idx:bicycle "@model: ka*"
单词后缀

与前缀类似,也可以搜索具有相同后缀的单词。

FT.SEARCH index "*suffix"

您还可以在查询表达式中结合前缀和后缀搜索。

FT.SEARCH index "*infix*"

以下示例查找所有以“bikes”结尾的品牌:

FT.SEARCH idx:bicycle "@brand:*bikes"
模糊查询

模糊搜索允许您查找与搜索词近似匹配的文档。要执行模糊搜索,可以用成对的%字符包裹搜索词。一对表示距离为一,两对表示距离为二,三对(最大距离)表示距离为三。以下命令在所有文本字段中以距离为一进行搜索:

FT.SEARCH index "%word%"

以下示例查找所有包含与拼写错误的“optamized”相距为一的单词的文档。可以看到,它匹配了单词“optimized”。

FT.SEARCH idx:bicycle "%optamized%"

如果您想将最大单词距离增加到二,可以使用以下查询:

FT.SEARCH idx:bicycle "%%optamised%%"

连接查询

连接查询是多种查询类型的结合,例如:

  • 精确匹配
  • 范围查询
  • 全文搜索

您可以使用逻辑查询运算符将数值、标签和文本字段的查询表达式组合在一起。本文中的示例使用以下模式:

字段名称字段类型
descriptionTEXT
conditionTAG
priceNUMERIC
vectorVECTOR
AND

(空格)用于交集两个或多个表达式的结果。您可以使用以下格式执行交集查询:

FT.SEARCH index "(expr1) (expr2)"

如果您想在特定文本字段中基于多个值进行交集,则应使用以下简化表示法:

FT.SEARCH index "@text_field:( value1 value2 ... )"

以下示例展示了一个查询,查找新状态且价格范围在 500 美元到 1000 美元之间的自行车:

FT.SEARCH idx:bicycle "@price:[500 1000] @condition:{new}"

您可能还对儿童自行车感兴趣。以下查询展示了如何将全文搜索与之前查询的条件结合:

FT.SEARCH idx:bicycle "kids (@price:[500 1000] @condition:{used})"
OR

您可以使用二元运算符 |(竖线)执行并集查询。

FT.SEARCH index "(expr1) | (expr2)"

如果您想在单个标签或文本字段中基于多个值执行并集,则应使用以下简化表示法:

FT.SEARCH index "@text_field:( value1 | value2 | ... )"
FT.SEARCH index "@tag_field:{ value1 | value2 | ... }"

以下查询展示了如何查找包含单词 ‘kids’ 或 ‘small’ 的二手自行车:

FT.SEARCH idx:bicycle "(kids | small) @condition:{used}"

上面的查询在所有文本字段中搜索。以下示例展示了如何将搜索限制在描述字段中:

FT.SEARCH idx:bicycle "@description:(kids | small) @condition:{used}"

如果您想扩展搜索到新自行车,以下示例展示了如何做到这一点:

FT.SEARCH idx:bicycle "@description:(kids | small) @condition:{new | used}"
NOT

在查询表达式前加上-(减号)可以否定该表达式。

FT.SEARCH index "-(expr)"

如果您想在之前的价格范围内排除新自行车,可以使用以下查询:

FT.SEARCH idx:bicycle "@price:[500 1000] -@condition:{new}"

聚合查询

聚合查询允许您执行以下操作:

  • 应用简单映射函数。
  • 基于字段值对数据进行分组。
  • 对分组后的数据应用聚合函数。

本文中的示例使用以下字段模式:

字段名称字段类型
conditionTAG
priceNUMERIC
简单映射

APPLY子句允许您对基于查询表达式返回的结果集应用简单映射函数。

FT.AGGREGATE index "query_expr" LOAD n "field_1" .. "field_n" APPLY "function_expr" AS "result_field"

以下示例计算新自行车的折后价格:

FT.AGGREGATE idx:bicycle "@condition:{new}" LOAD 2 "__key" "price" APPLY "@price - (@price * 0.1)" AS "discounted"
分组聚合

您可以根据一个或多个标准对数据进行分组和聚合:

FT.AGGREGATE index "query_expr" ... GROUPBY n "field_1" .. "field_n" REDUCE AGG_FUNC m "@field_param_1" .. "@field_param_m" AS "aggregated_result_field"

以下示例根据条件字段分组,并应用价格类别的聚合:

FT.AGGREGATE idx:bicycle "*" LOAD 1 price APPLY "@price<1000" AS price_category GROUPBY 1 @condition REDUCE SUM 1 "@price_category" AS "num_affordable"
不使用分组的聚合

您可以通过额外的APPLY步骤将类型属性添加到每个文档中,然后计数所有文档:

FT.AGGREGATE idx:bicycle "*" APPLY "'bicycle'" AS type GROUPBY 1 @type REDUCE COUNT 0 AS num_total
不应用数学聚合的分组

如果您需要分组而不应用聚合函数,可以使用TOLIST函数。以下示例根据条件对所有自行车进行分组:

FT.AGGREGATE idx:bicycle "*" LOAD 1 "__key" GROUPBY 1 "@condition" REDUCE TOLIST 1 "__key" AS bicylces

事务

Redis 事务允许在单个步骤中执行一组命令,它们围绕着 MULTIEXECDISCARDWATCH 命令展开。Redis 事务提供了两个重要的保证:

  • 事务中的所有命令都是串行化的,按顺序执行。另一个客户端发送的请求绝不会在 Redis 事务执行过程中间被处理。这确保了命令作为单个孤立操作执行。
  • EXEC 命令触发执行事务中的所有命令,因此如果客户端在调用 EXEC 命令之前在事务上下文中丢失与服务器的连接,则不会执行任何操作。此外,Redis 通过AOF文件保证事务的持久性,但如果服务器崩溃或被硬性终止,可能导致部分操作丢失。Redis 在重新启动时会检测到此情况,并退出以防止数据损坏。可以使用 redis-check-aof 工具修复AOF文件以确保服务器能够重新启动。

如何使用

Redis 事务通过 MULTI 命令进入。该命令始终返回 OK。此时,用户可以发出多个命令。Redis 不会立即执行这些命令,而是将它们排队。所有命令都将在调用 EXEC 时执行。以下示例原子地增加了键 foobar 的值:

> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

正如上面的会话所示,EXEC 返回一个回复数组,其中每个元素是事务中单个命令的回复,按照命令发出的顺序排列。当 Redis 连接处于 MULTI 请求的上下文中时,所有命令都会回复 QUEUED 字符串。排队的命令仅在调用 EXEC 时被安排执行。

事务中的错误

在事务期间可能会遇到两种类型的命令错误:

  1. 命令可能无法入队,因此在调用 EXEC 之前可能会出现错误。例如,命令可能在语法上错误(参数数量错误、命令名称错误等),或者可能存在一些关键条件,如内存不足(如果服务器使用 maxmemory 指令配置了内存限制)。
  2. 在调用 EXEC 后,命令可能失败,例如因为我们对具有错误值的键执行了操作(例如对字符串值调用列表操作)。

Redis是通过以下策略处理事务中的错误:

  • 对于错误一:服务器将在积累命令过程中检测错误,如果检测到错误,Redis将拒绝执行事务,并在调用 EXEC 过程中返回错误并丢弃该事务。
  • 对于错误二:如果所有命令都入队成功,那么在执行过程中,即使一个命令失败,队列中的所有其他命令也会被处理即Redis不会停止处理命令。

事务回滚

Redis不支持事务回滚,因为支持回滚会对Redis的简单性和性能产生重大影响。

终止事务

DISCARD 可以用于中止一个事务。在这种情况下,不会执行任何命令,并且连接的状态恢复到正常状态。

> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"

乐观锁

WATCH 用于为 Redis 事务提供CAS行为。该命令会监视指定的键,并检测对它们的更改。如果在执行 EXEC 命令之前至少有一个被监视的键被修改,整个事务将会中止,并且 EXEC 返回一个空回复以通知事务失败。

假设我们需要通过 1 来原子地增加一个键的值(假设 Redis 没有 INCR 命令)。第一次尝试可能是这样的:

val = GET mykey
val = val + 1
SET mykey $val

这种方法只有在特定时间内只有一个客户端执行操作时才能可靠运行。如果多个客户端在大约相同的时间尝试递增键,就会发生竞态条件。例如,客户端 A 和 B 将读取旧值,例如 10。两个客户端都将将值递增为 11,最终将其设置为键的值。因此,最终值将为 11 而不是 12。有了 WATCH,我们能够很好地模拟这个问题:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

使用上述代码,如果存在竞争条件,并且另一个客户端在我们调用 WATCHEXEC 之间的时间内修改了 val 的结果,事务将失败。

还可以使用 UNWATCH 命令(无参数)来刷新所有已监视的键。有时这很有用,因为我们可能会乐观地锁定几个键,因为可能需要执行事务来修改这些键,但在读取键的当前内容后我们不想继续。当发生这种情况时,我们只需调用 UNWATCH,以便连接可以自由地用于新的事务。

发布和订阅

SUBSCRIBEUNSUBSCRIBEPUBLISH 命令实现了发布/订阅消息传递模式,发送者(发布者)不会将消息发送到特定的接收者(订阅者)。相反,发布的消息会根据频道进行分类,发布者不需要知道是否有订阅者存在。订阅者则表达对一个或多个频道的兴趣,且只会收到自己感兴趣的消息,而无需了解是否有发布者。这种发布者与订阅者的解耦设计允许系统具有更高的可扩展性和更灵活的网络拓扑。

例如,客户端可以通过 SUBSCRIBE 命令订阅名为 “channel11” 和 “ch:00” 的频道:

SUBSCRIBE channel11 ch:00

其他客户端向这些频道发送的消息会推送给所有已订阅的客户端。订阅者会按消息发布的顺序接收消息。

已订阅一个或多个频道的客户端通常不能执行其他命令,虽然它可以继续订阅 (SUBSCRIBE) 或取消订阅 (UNSUBSCRIBE) 其他频道。订阅和取消订阅操作的回复也以消息形式发送,客户端可以读取这些消息流以获取相关信息,消息的第一个元素指示消息的类型。已订阅客户端可以执行的命令包括:

  • PING
  • PSUBSCRIBE
  • PUNSUBSCRIBE
  • QUIT
  • RESET
  • SSUBSCRIBE
  • SUBSCRIBE
  • SUNSUBSCRIBE
  • UNSUBSCRIBE

消息传递语义

Redis 的发布/订阅采用最多一次(at-most-once)消息传递语义。顾名思义,消息会最多传递一次,消息一旦被 Redis 服务器发送,就不会再发送。如果订阅者无法处理消息(例如由于错误或网络断开),消息将永久丢失。如果应用需要更强的消息传递保证,可以考虑使用 Redis Streams,流中的消息是持久化的,并支持最多一次(at-most-once)和至少一次(at-least-once)的消息传递语义。

推送消息的格式

一条消息是包含三个元素的数组回复:

  • 第一个元素表示消息的类型:
    • subscribe:表示成功订阅了返回的频道,第三个元素表示当前订阅的频道总数。
    • unsubscribe:表示成功取消订阅,第三个元素表示当前剩余订阅的频道总数。如果该值为零,表示不再订阅任何频道,客户端可以执行任何 Redis 命令。
    • message:表示由其他客户端通过 PUBLISH 命令发送的消息,第二个元素是频道名称,第三个元素是实际的消息内容。

数据库与作用域

发布/订阅与键空间无关,不会与数据库编号等产生干扰。例如,在数据库 10 上发布的消息,可以被数据库 1 上的订阅者接收到。

如果需要某种作用域,可以为频道添加前缀(如 teststagingproduction)。

模式匹配订阅

Redis 的发布/订阅支持模式匹配。客户端可以订阅符合某种模式的所有频道。例如:

PSUBSCRIBE news.*

这会接收所有发布到类似 news.art.figurativenews.music.jazz 等频道的消息。

分片发布/订阅

从 Redis 7.0 开始,引入了分片发布/订阅(Sharded Pub/Sub),通过 SSUBSCRIBESUNSUBSCRIBESPUBLISH 命令实现。分片发布/订阅有助于在集群模式下扩展使用发布/订阅功能,将消息传播限制在集群的某个分片内。


http://www.kler.cn/news/340110.html

相关文章:

  • 十年网络安全工程师谈学习网络安全的正确顺序
  • C++语言学习要点
  • 《Windows PE》5.1 导出表
  • 手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
  • NodePad++离线安装compare插件
  • Nginx07-静态资源访问
  • 金慧-综合管理信息系统 LoginBegin.aspx SQL注入复现
  • 国产兆易创新Cortex-M7 GD32H459适配OpenHarmony轻量系统,代码开源!
  • 解开 Golang‘for range’的神秘面纱:易错点剖析与解读
  • LeetCode 2390. 从字符串中移除星号【栈】1347
  • GC9118S替代TMI8118的优势分析,可应用在牙刷,电子锁,医疗设备等产品中
  • 网络中的生成树协议(Spanning Tree Protocol,STP)
  • vue3+vite@4+ts+elementplus创建项目详解
  • Pikachu-Cross-Site Scripting-DOM型xss
  • 基于深度学习的视频中的姿态跟踪
  • Kubernetes中部署ELK Stack日志收集平台
  • C# HttpClient请求URL重定向后丢失Authorization认证头信息 .Net Core Web Api
  • Android 智能手机使用技巧:适用于 Android 的最佳图案锁移除程序列表
  • 一起了解AI的发展历程和AGI的未来展望
  • 布局性能优化