Redis 8.8:新数组数据结构、速率限制器、性能改进

Hacker News Top 产品

摘要

Redis 8.8 现已推出,包含新的数组数据结构、窗口计数器限流器、哈希字段的子键通知、时间序列查询中的多个聚合器,以及各种操作的显著性能改进。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/05 14:08

# Redis 8.8:全新数组数据结构与开源特性 来源:https://redis.io/blog/announcing-redis-8-8/ Redis 8.8 现已正式发布,带来了性能提升和一系列强大的新功能。亮点包括:全新通用数据结构——数组、窗口计数器速率限制器、流消息 NACK、哈希字段子键通知、JSON 数值数组存储的显式控制、单次时间序列查询中的多个聚合器,以及用于有序集合并集和交集的新 COUNT 聚合器。 ## 8.8 性能改进概览 Redis 8.8 引入了显著的端到端吞吐量提升: | 数据类型 | 操作 | 端到端吞吐量提升 | |----------|------|------------------| | 字符串 | MGET (管道化,使用 I/O 线程) | 最高 68% | | 字符串 | MGET (管道化,单线程) | 最高 50% | | 字符串 | MSET | 最高 8% | | 哈希 | HGETALL | 最高 25% (1K+ 字段) | | 流 | XREADGROUP | 最高 83% (COUNT 100) | | 有序集合 | ZADD, ZINCRBY, ZRANGEBYSCORE | 最高 74% | | 位图 | 位图操作 | 最高 28% (x86) | | HyperLogLog | PFCOUNT | 最高 18% (x86) | | (多个) | SCAN, HSCAN, SSCAN, ZSCAN | 最高 40% | 此外,持久化和复制(全量同步)现在速度提升了最高 60%。 ## 8.8 新功能概览 Redis 一直强调为任务选择合适的数据结构。在 Redis 8.8 中,我们引入了一种全新的通用数据结构:**数组**。数组是一个按索引寻址的字符串值集合。每个数组元素存储在一个数字索引处,并且可以极快地访问。数组是动态的、稀疏友好的、可感知计算的容器,能够支持新的用例,并为现有用例提供更好的灵活性和效率(由 @antirez (https://github.com/antirez) 贡献)。 **速率限制**是 Redis 最常见的用例之一。传统上,用户使用服务器端 Lua 脚本结合客户端逻辑来实现速率限制器。在 Redis 8.8 中,我们引入了一个**窗口计数器速率限制器**(由 @raffertyyu (https://github.com/raffertyyu) 与 Redis 团队共同贡献)。 我们持续投资改进 **Redis Streams**。 - Redis 8.2 (https://redis.io/blog/redis-82-streams-bitmap/) 简化了跨多个消费者组的消息确认和删除 - Redis 8.4 (https://redis.io/blog/redis-8-4-open-source-ga/) 使消费者更容易同时读取新消息和空闲的待处理消息 - Redis 8.6 (https://redis.io/blog/announcing-redis-86-performance-improvements-streams/) 引入了幂等生产 在此基础上,Redis 8.8 增加了对**消息 NACK** 的支持,允许消费者显式释放待处理消息,使其立即可用并优先被其他消费者消费。 在 Redis 7.4 (https://redis.io/blog/announcing-redis-community-edition-and-redis-stack-74/) 中,我们引入了哈希字段过期——这一功能得到了广泛采用。随后一个常见的需求是提供字段级别的通知,类似于现有的键级别通知 (https://redis.io/docs/latest/develop/pubsub/keyspace-notifications/)。Redis 8.8 通过**哈希字段的子键通知**实现了这一点,允许客户端订阅字段过期和删除等事件。这些通知包含键、子键(字段名)和事件类型。 检索多个时间序列聚合器是一种常见操作。例如,蜡烛图依赖于 MIN、MAX、FIRST 和 LAST 聚合。在 Redis 8.8 之前,这需要多个命令。Redis 8.8 现在支持**单个时间序列命令中的多个聚合器**,减少了往返次数并简化了客户端逻辑。 Redis 8.4 (https://redis.io/blog/redis-8-4-open-source-ga/) 引入了对 JSON 中同质数值数组的支持,实现了高达 92% 的内存节省——这对 AI 工作负载尤其有价值。在 Redis 8.8 中,用户可以**显式控制数值数组的存储方式**(BF16、FP16、FP32 或 FP64),从而能够更好地与源数据、向量索引需求以及内存/精度权衡对齐。 最后,Redis 8 扩展了**有序集合的并集和交集操作**,新增了一个 `COUNT` **聚合器**。这使得每个元素的分值可以反映它出现在输入集合中的次数,或这些集合的加权和,从而在排名、评分和分析中解锁新的用例。 ## 新功能详解 ### 数组:一种全新的通用数据结构 Redis 一直强调为任务选择合适的数据结构。Redis 传统上提供几种核心数据结构,包括列表、哈希、集合和有序集合。在 Redis 8.8 中,我们引入了一种全新的通用数据结构:**数组**。 **什么是数组?** 数组是一个**按索引寻址的字符串值集合**。每个元素存储在一个数字索引处,并且可以极快地访问。 数组远不止基本的索引存储。它们灵活、内存高效且可感知计算。数组具备一些**能力**,能够启用新的用例,并为许多现有用例提供更好的灵活性和效率: - **数组不需要固定大小**:数组动态增长和收缩。元素可以设置在任何索引(0 到 2^64-1)上,数组会根据需要高效增长。元素可以被删除,数组会相应收缩。 - **数组可以是密集的或稀疏的**:使用的索引不必是连续的,但内存占用与元素数量成正比,按索引访问仍然极快。例如:数组索引可以表示产品 ID,值则保存产品名称或详细信息。类似地,索引可以表示时间戳,值保存日志事件。 - **数组可以用作环形缓冲区(滑动窗口)**:数组可以充当有界的滚动缓冲区:保留最后 n 个元素,保持插入顺序,并自动覆盖旧条目。想象一个日志文件或事件流,您希望高效地保留最后 n 条日志条目、事件或测量值,并频繁获取最后(最多)n 个值。这在需要将数据输入规则引擎、处理欺诈检测窗口、持续更新图表或执行安全验证时特别有用。 - **数组可以聚合数据**:当值为数值(例如实时传感器报告或股票报价)时,数组支持对索引范围进行服务器端计算,包括 `SUM`、`MIN` 和 `MAX`。当值为二进制标志时,还支持布尔聚合器(`AND`、`OR`、`XOR`)。服务器端聚合器非常适合传感器数据、金融行情和实时指标。与环形缓冲区语义结合时,数组能够实现滑动窗口分析,例如实时异常检测。 - **数组可以被搜索**:数组可以表示文本文件(例如 .txt、.csv、.log),其中每个元素按行号索引并保存一行文本。用户可以遍历这些行进行分析,并可以使用精确或部分字符串、glob 风格模式或正则表达式搜索特定行。使用环形缓冲区语义,数组可以持续保存最后 n 条日志行,使用户和代理能够基于最近的事件对传入事件进行上下文化或丰富化。 总而言之,数组是一个**动态、灵活、高性能、按索引寻址、可感知计算的容器**,它结合了: - 列表(有序数据) - 时间序列(滑动窗口) - 稀疏映射(非连续索引) - 分析引擎(聚合和搜索) **随机元素访问:数组 vs 列表 vs 哈希** 在大量元素下对数组与最接近的列表和哈希等价物进行随机访问基准测试,数组的优势清晰可见: | 操作 (100K 元素;1 KB 值) | 数组 | 列表 | 哈希 | |--------------------------|------|------|------| | 读取随机元素 | 675K ops/sec | 133K ops/sec | 626K ops/sec | | 写入随机元素 | 757K ops/sec | 137K ops/sec | 689K ops/sec | | 删除随机元素 | 841K ops/sec | — | 730K ops/sec | \* Redis 8.8,单实例运行在 Intel Sapphire Rapids m7i.metal-24xl 机器上 对于随机元素操作,数组的吞吐量比哈希高 8-15%,比列表快至少 5 倍。 在内存方面,列表最为紧凑。数组每个元素所需内存比列表多约 18%,而哈希比列表多 30-46%,具体取决于元素大小: | 元素大小 (100K 元素) | 数组 | 列表 | 哈希 | |---------------------|------|------|------| | 100 bytes | 122 bytes/element | 104 bytes/element | 151 bytes/element | | 1 Kbyte | 1290 bytes/element | 1035 bytes/element | 1337 bytes/element | **环形缓冲区:数组 vs 列表** 在 Redis 中,一种常见模式是使用列表作为有界环形缓冲区:客户端使用 `RPUSH` 推入新条目,并使用 `LTRIM` 修剪列表以保持恒定数量的元素。数组提供了 `ARRING`,可以在一个原子命令中完成同样的操作。 | 环大小;元素大小 | 数组 (ARRING) | 列表 (RPUSH+LTRIM) | 数组的优势 | |------------------|---------------|-------------------|------------| | 1K 元素;100 bytes | 1.11M inserts/sec | 512K inserts/sec | × 2.2 | | 100K 元素;100 bytes | 1.12M inserts/sec | 528K inserts/sec | × 2.1 | | 1K 元素;1 Kbyte | 840K inserts/sec | 424K inserts/sec | × 2.0 | | 100K 元素;1 Kbyte | 837K inserts/sec | 413K inserts/sec | × 2.0 | \* Redis 8.8,单实例运行在 Intel Sapphire Rapids m7i.metal-24xl 机器上 `ARRING` 的吞吐量(插入/秒)是等效 `RPUSH`+`LTRIM` 习语的两倍,且不受环大小影响。内存占用与上表一致:数组比列表多约 18% 的内存。 **何时使用数组?** 数组在以下情况下非常有用: - 需要极快的按索引或按索引范围访问 - 需要对最近的数据使用滑动窗口 - 需要服务器端聚合 - 需要搜索匹配元素 **数组不适合做什么?** 数组不能替代其他数据结构。 - 如果需要推入/弹出操作或在元素之间插入,请使用列表。 - 如果需要基于字段名称的访问而不是数字索引,请使用哈希。 **在哪里可以了解更多?** 数组文档:https://redis.io/docs/staging/DOC-6334/develop/data-types/arrays/ 数组命令:https://redis.io/docs/latest/commands/?group=array 深入探索 Redis 的新数组数据类型:https://redis.io/blog/diving-deep-into-rediss-new-array-data-type/ ## 窗口计数器速率限制器 窗口计数器速率限制器,包括固定窗口、带惰性重置的固定窗口和滑动窗口计数器变体,使用一个或多个固定持续时间的时间窗口。每个窗口维护一个计数器,在窗口创建时初始化为 0,同时维护一个最大容量,表示该窗口生命周期内允许的令牌数。 在 Redis 8.8 之前,实现窗口计数器速率限制器需要 Lua 脚本。在 8.8 中,我们引入了一个用于处理窗口计数器的新命令: 思路很简单:每个窗口有一个**持续时间**(通过 `EX` 或 `PX` 指定)和一个**令牌容量**(通过 `UBOUND` 指定)。请求的令牌数可以通过 `BYINT increment` 指定(默认为 1)。`INCREX` 尝试按请求的令牌数增加计数器。如果键不存在,则会创建该键。 为了使该命令适用于速率限制器用例,除了基本的递增语义外,`INCREX` 相比现有的 `INCR` 命令系列引入了三个新能力: 1. `INCREX` 同时返回新的计数器值和实际应用的增量,允许调用者立即判断请求应该被允许还是拒绝。 2. 当指定 `ENX` 时,仅当键尚未有过期时间时才设置过期时间。这确保仅在窗口创建时设置其 TTL,而在其生命周期内的后续请求中不会修改。 3. 边界强制执行:如果请求会超出定义的边界,则被拒绝。使用 `SATURATE` 时,请求可能被“部分接受”,计数器被钳制到指定边界(“饱和”)。 除了速率限制,`INCREX` 可以看作是 `INCR`、`INCRBY`、`INCRBYFLOAT` 以及 `DECR` 和 `DECRBY`(通过负增量)的通用形式,并增加了对边界和过期控制的支持。 ## 流:NACK 消息 在现实世界的应用中,流消费者并不总能成功处理它们消费的消息。失败可能由多种原因引起: - 消费者可能遇到与消息本身无关的内部问题。例如,无法到达处理消息所需的外部服务。 - 消费者可能需要关闭并释放未处理的消息。 - 资源受限的消费者(CPU、内存)可能无法处理某些消息(至少无法及时处理)。 - 消息可能格式错误、有毒甚至恶意。 在 Redis 8.8 之前,消费者无法显式拒绝(NACK)消息。他们要么确认(ACK),要么让其保持待处理状态。实际上,这意味着消费者组中的其他消费者必须使用 `XREADGROUP ... CLAIM`、`XPENDING`+`XCLAIM` 或 `XAUTOCLAIM` 来恢复这些消息。 这种方法会引入延迟,因为消息会在待处理条目列表(PEL)中保持空闲状态,直到另一个消费者认领它们——这对时间敏感的系统来说是个问题。 Redis 8.8 引入了一个新命令来直接解决这个问题: `XNACK key group [SILENT|FAIL|FATAL] IDS numids id [id ...]` 该命令允许消费者显式地将消息释放回流中,使其立即可用于重新投递。 `XNACK` 支持三种模式,每种模式针对不同的现实场景设计: - `SILENT` —— 用于失败与消息无关的情况(例如,关闭或瞬态内部错误)。投递计数器减 1,实际上撤销了消息添加到 PEL 时发生的递增。 - `FAIL` —— 用于此消费者无法处理但可能在别处成功处理的消息(例如,需要更多资源)。投递计数器保持不变(它已经在添加到组的 PEL 时递增了 1)。 - `FATAL` —— 用于格式错误、有毒或潜在恶意的消息。投递计数器设置为 `LLONG_MAX`,便于检测并路由到死信队列。 这些模式自然地映射到生产场景:优雅关闭或瞬态故障、基于资源的故障以及有毒消息处理。 当消息被 NACK 时,它将: - 被标记为无主(其最后一个消费者设置为空字符串) - 被分配最后投递时间为 0 - 被放置在 PEL 的 NACK 部分的末尾 PEL 的头部保留给所有 NACK 的消息,按 FIFO 顺序在它们之间排列,后跟既未被 ACK 也未被 NACK 的待处理消息(保持其原有顺序)。这保证了 NACK 的消息总是优先于空闲的待处理消息。 `XREADGROUP` 上的投递顺序相应更新: - 当指定 `CLAIM min-idle-time` 时: - NACK 的消息(*新行为*) - 至少空闲了 min-idle-time 的待处理消息 - 从未投递过的消息 - 如果未指定 `CLAIM`: - 仅返回从未投递过的消息(*行为不变*) ## 哈希:子键通知 Redis 的键级别通知 (https://redis.io/docs/latest/develop/pubsub/keyspace-notifications/) 允许客户端通过发布/订阅通道实时订阅与键相关的事件。有两种类型的通道: - **键空间通知**:客户端订阅特定键;每条消息包含一个事件类型。 - **键事件通知**:客户端订阅特定事件;每条消息包含一个键名。 在 Redis 7.4 (https://redis.io/blog/announcing-redis-community-edition-and-redis-stack-74/) 中,我们引入了哈希字段过期。这一功能得到了广泛采用,随后一个常见的需求是:支持**哈希字段级别的通知**,因为键级别通知不包含字段名。 Redis 8.8 引入了**子键级别通知**。

相似文章

Redis 数组实验场

Simon Willison's Blog

Redis 正在通过一个 PR 添加一种新的数组数据类型,并且已经构建了一个基于 WASM 的交互式实验场,用于尝试新的命令。

Redis 与野心的代价

Lobsters Hottest

本文批评了 Redis 最近的战略方向,重点指出了许可变更引发的冲突、功能冗余泛滥,以及其向“AI 上下文引擎”定位的转变。文章分析了宏大的企业目标如何影响了项目的开放性和简洁性。