开发者不只是使用 Redis,他们热爱它。 Stack Overflow 2021 年度开发者调查 已连续五年将 Redis 列为最受欢迎的数据库平台!但同样重要的是要理解,Redis 的默认设置并不适用于所有人。数百万开发者因其速度和性能而使用 Redis,但确保正确使用它也很重要。
“反模式”基本上是指那些最初看起来很合适,但在实施阶段会使你的代码变得更加复杂的实践和解决方案。让我们看看需要避免的主要 Redis 反模式
在单个分片/Redis 实例上运行大型数据库时,故障转移、备份和恢复都可能会花费更长时间。因此,始终建议将分片保持在建议的大小。一般的保守经验法则是 25GB 或 25K OPS/秒。
Redis Cloud 建议,如果您的数据超过 25GB 且操作数量很高,则进行分片。另一方面,如果您的每秒操作数超过 25,000,那么分片可以提高性能。如果每秒操作数较低,它也可以处理高达 50GB 的数据。
让我们看看使用连接池管理与 Redis 服务器连接的 redis-py。默认情况下,您创建的每个 Redis 实例都会创建自己的连接池。您可以通过将已创建的连接池实例传递给 Redis 类的 connection_pool 参数来覆盖此行为并使用现有连接池。您可以选择这样做,以实现客户端分片或对连接管理进行精细控制。
>>> pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
>>> r = redis.Redis(connection_pool=pool)
当客户端数量众多时,重连洪流可能会轻易压垮单线程的 Redis 进程并强制触发故障转移。因此,建议您使用合适的工具来减少与 Redis 服务器的打开连接数量。
Redis Enterprise DMC 代理 通过充当代理来减少与缓存服务器的连接数。还有其他第三方工具,例如 Twemproxy。它是一个快速轻量级的代理服务器,可以减少与 Redis 服务器的打开连接数量。它主要用于减少后端缓存服务器的连接数量。这与协议管道化和分片结合使用,使您能够横向扩展分布式缓存架构。
Redis OSS 使用基于分片的仲裁。为了防止脑裂情况,建议至少使用 3 份数据(每个主分片对应 2 个副本分片)。总而言之,Redis OSS 通过拥有奇数个分片(主分片 + 2 个副本)来解决仲裁挑战。
Redis Cloud 通过奇数个节点来解决仲裁挑战。Redis Cloud 仅使用 2 份数据即可避免脑裂情况,这更具成本效益。此外,如果增加一个非必要的数据节点成本过高,可以使用所谓的“仅仲裁节点”来使集群节点数达到奇数。
串行执行多个操作会增加连接开销。相反,请使用 Redis Pipelining(管道)。管道是指在不等待每个消息回复的情况下将多个消息发送到管道中,然后(通常)在收到回复时再处理它们的过程。
管道完全是客户端实现。它旨在解决高网络延迟环境下的响应延迟问题。因此,在网络上传输命令和读取响应所花费的时间越少越好。这通过缓冲有效地实现。客户端可以在将命令发送到服务器之前(或不)在 TCP 栈上缓冲命令(如其他答案所述)。一旦发送到服务器,服务器会执行这些命令并在服务器端进行缓冲。管道的好处是显著提高了协议性能。管道化带来的加速效果对于本地连接可达五倍,对于较慢的互联网连接则至少可达一百倍。
Redis 主要用作键值存储。可以为这些键设置超时值。也就是说,超时过期会自动删除键。此外,当我们使用删除或覆盖键内容的命令时,它会清除超时。Redis TTL 命令用于获取键过期剩余时间的秒数。TTL 返回设置了超时的键的剩余生存时间。这种内省能力允许 Redis 客户端检查给定键将继续作为数据集一部分的剩余秒数。键会累积并最终被逐出。因此,建议对所有缓存键设置 TTL。
当试图通过慢速或饱和的链接复制一个非常大的活跃数据库时,由于持续更新,复制永远无法完成。因此,建议调整从库和客户端缓冲区,以允许较慢的复制。请查看 这篇详细博客。
Redis 可以轻松成为您应用操作数据的核心,存储有价值且频繁访问的信息。然而,如果您将访问集中到少数几个不断被访问的数据片段上,就会产生所谓的热键问题。在 Redis 集群中,键实际上决定了数据在集群中的存储位置。数据根据该键的哈希存储在一个单一的主要位置。因此,当您反复访问同一个键时,实际上是反复访问同一个节点/分片。换句话说,如果您有一个由 99 个节点组成的集群,并且某个键在一秒内收到一百万次请求,那么所有一百万次请求都将发送到同一个节点,而不会分散到其他 98 个节点上。
Redis 甚至提供了工具来查找您的热键位于何处。使用带有 –hotkeys 参数的 redis-cli,并附带您连接所需的任何其他参数。
$ redis-cli --hotkeys
如果可能,最好的防御是避免产生这种情况的开发模式。将数据写入位于不同分片的多个键,可以使您更频繁地访问相同的数据。总之,避免每个客户端操作都访问特定键。因此,建议使用哈希算法对热键进行分片。您可以将策略设置为 LFU 并运行 redis-cli --hotkeys 来确定。
在 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"
Redis 通常被用作应用程序的主存储引擎。与将 Redis 用作缓存不同,将 Redis 用作主数据库需要两个额外的特性才能有效。任何主数据库都应该具有高可用性。如果缓存发生故障,您的应用程序通常会处于部分中断状态。如果主数据库发生故障,您的应用程序也会停机。类似地,如果缓存发生故障并且您重新启动它时为空,这没什么大不了的。然而,对于主数据库来说,这影响巨大。Redis 可以轻松处理这些情况,但通常需要与作为缓存运行时不同的配置。将 Redis 用作主数据库很棒,但您必须通过开启正确的功能来支持它。
使用 Redis 开源版本,您需要设置 Redis Sentinel 来实现高可用性。在 Redis Cloud 中,这是一项核心功能,您只需在创建数据库时开启即可。至于持久性,Redis Cloud 和开源 Redis 都通过 AOF 或快照提供持久性,以便您的实例可以恢复到您离开时的状态。
用多种语言编写的微服务可能无法以一致的方式对 JSON 进行 marshal/unmarshal。需要应用程序逻辑来锁定/监视一个键以进行原子更新。JSON 操作通常是一个计算成本非常高的操作。因此,建议使用 HASH 数据结构以及 Redis JSON。
唯一的查询机制是 SCAN,它需要读取数据结构并将过滤限制在 MATCH 指令。建议将表或 JSON 存储为字符串。使用 SET 或 SORTED SET 将索引分解为反向索引,并指向回字符串的键。在同一个 Redis 实例中使用 SELECT 命令和多个数据库
Salvatore(Redis 的创建者)曾提到在同一个 Redis 实例中使用 SELECT 和多个数据库是一种反模式。建议为每个数据库需求使用独立的 Redis 实例。这在微服务架构中尤其如此,因为客户端应用程序可能会互相干扰(吵闹的邻居、数据库设置/拆卸的影响、维护、升级等)。
Redis Time Series 模块直接与时序数据库竞争。但是,如果唯一的查询是基于排序,那就没有必要增加复杂性。因此,建议使用 SORTED SET,将每个值的分数设为 0。值会被追加。或者使用时间戳作为分数进行简单的基于时间的查询。