很难大规模操作有状态的分布式系统,Redis 也不例外。托管数据库通过承担大部分繁重工作使生活变得更轻松。但是您仍然需要一个健全的架构并在服务器(Redis)和客户端(应用程序)上应用最佳实践。
本文涵盖了一系列与 Redis 相关的最佳实践、提示和技巧,包括集群可扩展性、客户端配置、集成、指标等。虽然我会不时引用Amazon MemoryDB和ElastiCache for Redis,但大多数(如果不是全部) ) 一般适用于 Redis 集群。
无论如何,这并不意味着是一个详尽的清单。我只是选择了十,因为它是一个不错的、有益健康的数字!
让我们深入了解一下您在扩展 Redis 集群方面有哪些选择。
1. 可扩展性选项
您可以放大或缩小:
扩展(垂直)- 您可以增加单个节点/实例的容量,例如从Amazon EC2db.r6g.xlarge类型升级到db.r6g.2xlarge
Scaling Out (Horizontal) - 您可以向集群添加更多节点向外扩展的需求可能是由几个原因驱动的。
如果您需要处理读取繁重的工作负载,您可以选择添加更多副本节点。这适用于 Redis 集群设置(如MemoryDB)或非集群主副本模式,例如ElastiCache with cluster mode disabled的情况。
如果您想增加写入容量,您会发现自己受到主副本模式的限制,应该选择基于 Redis 集群的设置。您可以增加集群中的分片数量 - 这是因为只有主节点可以接受写入,并且每个分片只能有一个主节点。
这还具有增加整体高可用性的额外好处。
图 1:Redis(已禁用集群模式)和 Redis(已启用集群模式)集群 – ElastiCache for Redis 文档
2. 扩展集群后,最好使用这些副本!
大多数 Redis 集群客户端(包括 )的默认行为redis-cli是将所有读取重定向到主节点。如果您添加了只读副本来扩展读取流量,它们将处于空闲状态!
您需要切换到READONLY模式以确保副本处理所有读取请求,而不仅仅是被动参与者。确保正确配置您的 Redis 客户端 - 这将因客户端和编程语言而异。
例如,在Go Redis 客户端中,您可以设置ReadOnly为true:
client := redis.NewClusterClient(
&redis.ClusterOptions{
Addrs: []string{clusterEndpoint},
ReadOnly: true,
//..other options
})
为了进一步优化,您还可以使用RouteByLatency或RouteRandomly,这两个都自动开启ReadOnly模式。
您可以参考Java 客户端(例如 Lettuce)的工作原理。
3. 使用只读副本时要注意一致性特征
您的应用程序有可能从副本中读取过时的数据——这就是最终一致性。由于主副本节点复制是异步的,因此您发送到主节点的写入器可能尚未反映在只读副本中。当您拥有大量只读副本(尤其是跨多个可用区)时,可能会出现这种情况
如果这对您的用例来说是不可接受的,那么您也必须求助于使用主节点进行读取。
MemoryDB 或 ElastiCache for Redis 中的ReplicationLag 指标可用于检查副本在应用来自主节点的更改方面落后多长时间(以秒为单位)。
那么强一致性呢?
在这种情况下MemoryDB,来自主节点的读取是强一致的。这是因为客户端应用程序仅在写入(到主节点)写入持久多可用区事务日志后才会收到成功的写入确认。
4. 请记住,您可以影响密钥在 Redis 集群中的分布方式
Redis 没有使用一致性哈希(像许多其他分布式数据库一样),而是使用哈希槽的概念。总共有16384槽,为集群中的每个主节点分配一定范围的哈希槽,每个键属于特定的哈希槽(从而分配给特定节点)。如果键属于不同的哈希槽,则在 Redis 集群上执行的多键操作将无法进行。
但是,您并非完全受集群的支配!可以通过使用hashtags来影响键的位置。因此,您可以确保特定键具有相同的哈希槽。例如,如果您将客户 ID 的订单存储42在HASHnamedcustomer:42:orders中,并将客户资料信息存储在 中customer:42:profile,您可以使用花括号{}来定义将被散列的特定子字符串。在这种情况下,我们的键是{customer:42}:orders和{customer:42}:profile-{customer:42}现在驱动哈希槽的放置。现在我们可以确信这两个键都在同一个哈希槽中(因此是同一个节点)。
5. 您是否考虑过缩小(后退)?
您的应用程序很成功,它有很多用户和流量。你扩展了集群,事情仍然很顺利。惊人的!但是,如果您需要缩减规模怎么办?在执行此操作之前,您需要注意一些事项:
- 每个节点上是否有足够的可用内存?
- 这可以在非高峰时段进行吗?
- 它将如何影响您的客户端应用程序?
- 在此阶段您可以监控哪些指标?(例如CPUUtilization,CurrConnections等等)
请参阅MemoryDb for Redis 文档中的一些最佳实践,以更好地规划扩展。
6. 当事情出错时......
面对现实吧,失败是令人羡慕的。重要的是你是否为他们做好了准备。对于您的 Redis 集群,需要考虑以下几点:
- 您是否测试过您的应用程序/服务在遇到故障时的行为?如果没有,请做!借助 MemoryDB 和 ElastiCache for Redis
- 您可以利用故障转移 API模拟主节点故障并触发故障转移。
- 你有副本节点吗?如果您只有一个带有单个主节点的分片,那么如果该节点发生故障,您肯定会停机。
- 你有多个分片吗?如果您只有一个分片(主分片和副本分片),则在该分片的主节点故障的情况下,集群将无法接受任何写入。
- 您的分片是否跨越多个可用区?如果您有跨多个 AZ 的分片,您将更好地准备应对 AZ 故障。
在所有情况下,MemoryDB确保在节点更换或故障转移期间不会丢失数据
7. 无法连接Redis,求助!
Tl;DR:可能是网络/安全配置,这是一直困扰人们的事情!使用MemoryDB和ElastiCache,您的Redis 节点位于 VPC 中。如果您将客户端应用程序部署到AWS Lambda、EKS、ECS、App Runner等计算服务,则需要确保您拥有正确的配置 - 特别是在 VPC 和安全组方面。
这可能因您使用的计算平台而异。例如,您如何配置 Lambda 函数以访问 VPC 中的资源与 App Runner 的操作方式(通过VPC 连接器)甚至 EKS(尽管从概念上讲,它们是相同的)略有不同。
8. Redis 6 自带访问控制列表 - 使用它们!
没有理由不对 Redis 集群应用身份验证(用户名/密码)和授权(基于 ACL 的权限)。MemoryDB符合 Redis 6 并支持 ACL。但是,为了符合较旧的 Redis 版本,它为每个帐户配置一个默认用户(使用用户名default)和一个名为 的不可变 ACL open-access。如果您创建MemoryDB集群并将其与此 ACL 关联:
- 客户端无需身份验证即可连接
- 客户端可以在任何键上执行任何命令(也没有权限或授权)
作为最佳实践:
定义显式 ACL添加用户(连同密码),以及根据您的安全要求配置访问字符串。您应该监控身份验证失败。例如,MemoryDB 中的AuthenticationFailures指标为您提供失败的身份验证尝试总数 - 对此设置警报以检测未经授权的访问尝试。
不要忘记周边安全,如果您已经TLS在服务器上进行了配置,请不要忘记在您的客户端中也使用它!例如,使用 Go Redis:
client := redis.NewClusterClient(
&redis.ClusterOptions{
Addrs: []string{clusterEndpoint},
TLSConfig: &tls.Config{MaxVersion: tls.VersionTLS12},
//..other options
})
不使用它可能会给你的错误不够明显(例如泛型i/o timeout)并使事情难以调试 - 这是你需要小心的事情。
9.有些事情你不能做
作为托管数据库服务,MemoryDB或ElastiCache 限制对某些 Redis 命令的访问。例如,您不能使用与CLUSTER相关的命令的子集,因为集群管理(规模、分片等)由服务本身承担。
但是,在某些情况下,您可能会找到替代方案。以监控运行缓慢的查询为例。虽然您无法latency-monitor-threshold使用CONFIG SET进行配置,但您可以slowlog-log-slower-than在参数组中设置设置,然后使用slowlog get它进行比较。
10.使用连接池
您的 Redis 服务器节点(即使是功能强大的节点)资源有限。其中之一是能够支持一定数量的并发连接。大多数 Redis 客户端都提供连接池作为有效管理与 Redis 服务器的连接的一种方式。重用连接不仅有利于您的 Redis 服务器,而且由于开销减少,客户端性能也得到了提高——这在大容量场景中至关重要。
ElastiCache 提供了一些您可以跟踪的指标:
- CurrConnections:客户端连接数(不包括只读副本)
- NewConnections:特定时间段内服务器接受的连接总数。
11.(奖励)使用适当的连接模式
这一点很明显,但我还是要说出来,因为这是我目睹人们犯的最常见的“入门”错误之一。
您在客户端应用程序中使用的连接模式取决于您是使用独立的 Redis 设置还是 Redis 集群(很可能)。大多数 Redis 客户端对它们进行了明确的区分。例如,如果您使用启用了集群模式的Go Redis 客户端MemoryDB),则Elasticache需要使用NewClusterClient(而不是NewClient):
redis.NewClusterClient(&redis.ClusterOptions{//....})
有趣的是,有一个更加灵活的 UniversalClient 选项(在撰写本文时,这是在 Go Redis v9 中)如果你没有使用正确的连接模式,你会得到一个错误。但有时,根本原因会隐藏在一般错误消息的后面——因此您需要保持警惕。
结论
您所做的架构选择最终将取决于您的特定需求。