学习

每个开发人员都应该避免的 Redis 反模式

Ajeet Raina
作者
Ajeet Raina, Redis 前开发人员增长经理

开发人员不仅使用 Redis,他们还喜欢它。 Stack Overflow 的 2021 年年度开发者调查 连续五年将 Redis 评为最受欢迎的数据库平台!但同样重要的是要了解 Redis 的默认设置并不适合所有人。数百万开发人员使用 Redis 因为它速度快、性能好,但是确保它得到正确使用非常重要。

"反模式"基本上指的是那些最初看起来很适合,但在实施阶段却使代码变得更加复杂的做法和解决方案。让我们看看要避免的顶级 Redis 反模式

1. 在单个分片/Redis 实例上运行的大型数据库#

对于在单个分片/Redis 实例上运行的大型数据库,故障转移、备份和恢复都需要更长时间。因此,始终建议将分片保持在推荐的大小。一般的保守经验法则是 25 GB 或 25K 次操作/秒。

Redis Cloud 建议如果您的数据超过 25 GB 且操作次数很多,则进行分片。另一个方面是,如果您每秒的操作次数超过 25,000 次,那么分片可以提高性能。如果每秒的操作次数较少,它也可以处理高达 50 GB 的数据。

示例 #1 - redis-py#

让我们看一下使用连接池来管理与 Redis 服务器连接的 redis-py。默认情况下,您创建的每个 Redis 实例都会创建自己的连接池。您可以覆盖此行为并使用现有的连接池,方法是将已创建的连接池实例传递给 Redis 类的 connection_pool 参数。您可能选择这样做是为了实现客户端分片或对连接管理方式进行精细控制。

 >>> pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
 >>> r = redis.Redis(connection_pool=pool)

2. 直接连接到 Redis 实例#

对于大量客户端,重新连接洪流将能够简单地压倒单线程 Redis 进程并强制进行故障转移。因此,建议您应该使用合适的工具来减少到 Redis 服务器的打开连接数。

Redis Enterprise DMC 代理 允许您通过充当代理来减少到缓存服务器的连接数。还有其他第三方工具,如 Twemproxy。它是一个快速、轻量级的代理服务器,允许您减少到 Redis 服务器的打开连接数。它主要用于减少到后端缓存服务器的连接数。这与协议管道和分片一起使您能够水平扩展分布式缓存架构。

3. 多个辅助分片(Redis OSS)#

Redis OSS 使用基于分片的仲裁。建议至少使用 3 个数据副本(每个主分片 2 个副本分片)以防范脑裂情况。简而言之,Redis OSS 通过拥有奇数个分片(主分片 + 2 个副本)来解决仲裁挑战。

Redis Cloud 使用奇数个节点来解决仲裁挑战。Redis Cloud 仅使用 2 个数据副本来避免脑裂情况,这更加经济高效。此外,所谓的“仅仲裁节点”可用于将集群提升到奇数个节点,如果额外的(不是必需的数据)节点过于昂贵的话。

4. 执行单个操作#

按顺序执行多个操作会增加连接开销。相反,请使用 Redis 管道。管道是在不等待每个回复的情况下将多条消息发送到管道中,并且(通常)在它们到达时处理回复的过程。

管道完全是客户端侧的实现。它旨在解决高网络延迟环境中的响应延迟问题。因此,在网络上发送命令和读取响应所花费的时间越少越好。这可以通过缓冲有效地实现。客户端可以在发送到服务器之前在 TCP 堆栈中缓冲命令(也可能不会缓冲)。一旦它们被发送到服务器,服务器就会执行它们并在服务器端缓冲它们。管道的好处是协议性能大幅度提高。管道带来的加速效果从本地连接的 5 倍到缓慢互联网连接的至少 100 倍不等。

5. 不带 TTL 的缓存键#

Redis 主要充当键值存储。可以为这些键设置超时值。也就是说,超时过期会自动删除该键。此外,当我们使用删除或覆盖键内容的命令时,它将清除超时。Redis TTL 命令用于获取键过期剩余时间的秒数。TTL 返回具有超时的键的剩余生存时间。这种自省功能允许 Redis 客户端检查给定键将在数据集中保留多少秒。键会累积并最终被逐出。因此,建议为所有缓存键设置 TTL。

6. 无限的 Redis 复制循环#

当尝试通过缓慢或饱和的链接复制非常大的活动数据库时,由于持续的更新,复制永远无法完成。因此,建议调整从属服务器和客户端缓冲区以允许更慢的复制。查看 这篇详细的博客.

7. 热键#

Redis 能够轻松成为您应用程序操作数据的核心,存储有价值且经常访问的信息。但是,如果您将访问集中到少数持续访问的数据,您将创建所谓的热点问题。在 Redis 集群中,键实际上决定了数据在集群中的存储位置。数据根据键的哈希值存储在一个唯一的、主位置。因此,当您反复访问单个键时,实际上是反复访问单个节点/分片。换句话说,如果您有 99 个节点的集群,并且有一个键每秒接收一百万个请求,那么所有一百万个请求都会发送到单个节点,而不是分散到其他 98 个节点上。

Redis 甚至提供了工具来查找热点键所在位置。使用 redis-cli 并带有 –hotkeys 参数,以及您需要连接的任何其他参数。

 $ redis-cli --hotkeys

如果可能,最好的防御方法是避免创建这种局面的开发模式。将数据写入驻留在不同分片中的多个键,将允许您更频繁地访问相同数据。简而言之,拥有每个客户端操作都会访问的特定键。因此,建议使用哈希算法对热点键进行分片。您可以设置 LFU 策略并运行 redis-cli --hotkeys 来确定。

8. 使用 KEYS 命令#

在 Redis 中,KEYS 命令可用于对所有存储的键执行详尽的模式匹配。不建议这样做,因为在具有大量键的实例上运行此命令可能需要很长时间才能完成,并且会减慢 Redis 实例的速度。在关系数据库世界中,这相当于运行无界查询(没有 WHERE 子句的 SELECT...FROM)。谨慎执行此类操作,并采取必要的措施确保您的租户不会从其应用程序代码中执行 KEYS 操作。使用 SCAN,它将迭代分散到多个调用中,而不是一次占用整个服务器。

通过键名扫描键空间是一个非常慢的操作,并且将以 O(N) 运行,其中 N 是键的数量。建议使用 Redis Search 返回基于数据内容的信息,而不是遍历键空间。

 FT.SEARCH orders "@make: ford @model: explorer"
 2SQL: SELECT * FROM orders WHERE make=ford AND model=explorer"

9. 将短暂的 Redis 作为主数据库运行#

Redis 通常用作应用程序的主存储引擎。与将 Redis 用作缓存不同,将 Redis 用作主数据库需要两个额外的特性才能有效。任何主数据库都应该真正具有高可用性。如果缓存宕机,那么通常您的应用程序处于停滞状态。如果主数据库宕机,您的应用程序也会宕机。同样,如果缓存宕机并且您重新启动它为空,那也没关系。但是,对于主数据库来说,这是一个大问题。Redis 可以轻松处理这些情况,但通常需要与作为缓存运行不同的配置。将 Redis 用作主数据库很棒,但您必须通过打开正确的特性来支持它。

对于 Redis 开源,您需要设置 Redis Sentinel 以实现高可用性。在 Redis Cloud 中,这是您在创建数据库时只需打开的核心特性。至于持久性,Redis Cloud 和开源 Redis 都通过 AOF 或快照提供持久性,因此您的实例以您离开时的状态启动。

10. 在字符串中存储 JSON 块#

用多种语言编写的微服务可能无法以一致的方式编组/解组 JSON。应用程序逻辑将需要锁定/监视键以进行原子更新。JSON 操作通常是一个非常耗费计算的操作。因此,建议使用 HASH 数据结构以及 Redis JSON。

11. 将表格或 JSON 转换为 HASH 而不考虑查询模式#

唯一的查询机制是 SCAN,它需要读取数据结构,并将过滤限制为 MATCH 指令。建议将表格或 JSON 存储为字符串。使用 SET 或 SORTED SET 将索引分解为反向索引,并指向字符串的键。使用 SELECT 命令和单个 Redis 实例内的多个数据库

Salvatore(Redis 的创建者)将 SELECT 和单个 Redis 实例内的多个数据库的使用列为反模式。建议为每个数据库需求使用专门的 Redis 实例。这在微服务架构中尤其如此,在微服务架构中,客户端应用程序可能会互相干扰(噪音邻居、数据库设置/拆卸影响、维护、升级等)。

Redis 时间序列模块为时间序列数据库提供直接竞争。但如果唯一的查询基于排序,则没有必要复杂化。因此,建议使用 SORTED SET,每个值的得分均为 0。值将被追加。或者使用时间戳作为得分,用于简单的基于时间查询。

参考资料#