亚马逊AWS官方博客

如何在 Amazon ElastiCache for Redis 上使用集群模式

Original URL: https://amazonaws-china.com/cn/blogs/database/work-with-cluster-mode-on-amazon-elasticache-for-redis/

对于AWS,我个人最欣赏的一点就是它能提供用于解决各类技术用例的基础技术单元。其中Amazon ElastiCache for Redis正是这类技术单元之一。除了最常规的数据库缓存解决方案之外,ElastiCache本身还具有非常出色的灵活性与速度表现(微秒级别)。在之前的文章中,我们已经探讨过如何利用ELastiCache执行地理空间查询以及构建实时仪表板

在本文中,我们将了解如何在启用集群模式的情况下,利用ElastiCache for Redis增强系统可靠性与可用性(且几乎不对现有工作负载做出任何变更)。集群模式的主要优势,在于对Redis集群进行横向规模伸缩,且整个过程几乎不会损害到集群的性能表现。关于这一点,我们将在后文中通过示例具体说明。相信很多朋友都遇到过Redis集群配置过度或配置不足问题,抑或是希望更好地了解Redis集群模式的工作原理——今天的文章刚好帮大家解决这些疑问。

在深入探讨细节之前,让我们简要回顾一下ElastiCache for Redis集群的启动配置选项。如下图所示,大家可以从三种集群配置当中做出选择:(1)单节点;(2)禁用集群模式;(3)启用集群模式。无论如何选择,特定集群中的所有节点都将采用相同的节点类型(即相同的底层EC2实例类型)与配置方式。

三种模式的主要区别在于可靠性、可用性以及规模伸缩行为。对于生产工作负载,建议大家考虑使用包含复制机制的配置以增强数据保护。除了主节点之外,如果其他任何节点遭遇突发故障,复制机制都将保证您的数据不致意外丢失。不过一旦发生主节点性故障,复制操作会因此滞后,并导致部分数据丢失。下表所示为ElastiCache for Redis各配置选项间的主要区别:

单节点 禁用集群模式 启用集群模式
复制? 是(每节点最多5个副本) 是(每节点最多5个副本)
数据分区? 否(单分片) 是(最多90个分片)
规模伸缩 变更节点类型(纵向伸缩) 变更节点类型(纵向伸缩) 添加/移除分片并重新平衡(横向伸缩)
多可用区? 可选,至少需要1个副本 必选

集群模式上手指南

如上所述,在构建生产工作负载时,除非能够轻松重新创建数据,否则建议大家尽可能使用包含复制机制的配置选项。启用集群模式还将在集群扩展方面带来种种其他收益。简而言之,集群模式允许用户随意上调或下调分片数量(横向伸缩),或对节点类型进行上调/下调(纵向伸缩)。这意味着集群模式可扩展至最多90个分片这一极高存储量水平(可能高达100 TB),相比之下单节点模式的存储上限无法突破存储实例类型所能承载的数据极限。

对于存储需求不明或者写入操作强度较高的新工作负载,集群模式还能为我们带来更高的灵活性。而对于读取强度较高的工作负载,我们可以添加最多5个读取副本以扩展单一分片,并配合集群模式提供的额外写入端点同步提升写入工作负载的处理能力。

启用集群模式的ElastiCache for Redis在本质上是将缓存键空间分布在多个分片之上。如此一来,您的数据以及指向该数据的读取/写入访问也将分布至多个Redis节点当中。负载分散不仅能帮助我们提高系统可用性、在需求高峰期减少性能瓶颈,同时也能提供远超单节点模式的内存空间。在后文中,我们将进一步讨论如何通过Redis客户端指定单一端点,而后以内部透明方式映射至集群中的节点。

数据分片

Redis采用分片形式,其中的每一个缓存键都映射至一个“哈希槽”。在集群中,共有16384个哈希槽可用,供集群内的各个分片使用。在默认情况下,ElastiCache会将哈希槽平均分配给各个分片,但大家也可以根据需求自行定义分配方案。

在向集群写入或读取数据时,客户端会通过以下简单算法计算要使用的哈希槽:CRC16(键) mod 16384。对于ELastiCache for Redis,客户端本体负责根据键空间确定需要使用的分片。通过将一部分计算任务分配给客户端,我们允许客户端访问集群内的任意分片,从而降低了发生潜在单点故障的几率。通过下图可以看到,在与Redis集群进行初始连接时,客户端会解析并管理键空间映射,并利用此映射判断在哪个节点上寻找特定的哈希键。

下图所示,为Redis键空间如何在所需数量的分片上分布,以及客户端应用程序如何确定将数据存储在哪个分片之上。在下一章节中,我们将进一步讲解如何在ElastiCache上配置集群模式。

启动一个ElastiCache for Redis集群

您可以利用AWS Management Console、CLI或者SDK轻松便捷地在ElastiCache中启动新的Redis集群。在这里,我们将使用Management Console启动一个带有集群模式的新集群:

在您常用的浏览器内打开AWS Management Console,而后导航至ElastiCache。单击页面中央处的蓝色“Create”按钮。在此集群内,我们将选择Redis引擎并启用集群模式。

接下来,为集群提供名称与描述。您也可以在这一步选择引擎版本,但我们一般建议大家直接使用Redis引擎的最新可用版本。我们还可以指定分片数量与各个分片中的副本数。我们的集群将由指定数量的分片(最多90个)组成,每个分片包含指定数量的副本。在本示例中,3个分片(每个分片拥有2个副本)构成的集群共包含9个节点(3个主节点+每个分片2个副本节点)。

ElastiCache将根据您的存储与性能需求,提供多种缓存实例类型加以选择。选择不同的节点类型,意味着我们对各个分片中的节点容量以及集群整体容量进行纵向扩展调整。通过使用集群模式(而非单节点模式),我们能够减少因扩展而导致的中断事件数量,同时更好地适应不断变化的工作负载需求,从而潜在节约成本。集群中的所有节点将由相同的节点类型组成。

在“Advanced Redis settings”之下,我们可以控制集群中键空间的分布。您也可以根据需求指定自定义分布,但ElastiCache会默认在各个分片之间平均分配键。如果您预计自己集群中的键值对数量相对较少,那么自定义分布方法可能更合适。通过自定义操作,我们可以将一个或者多个较大的对象移动至特定分片当中。

在集群的网络、备份以及安全配置工作完成之后,单击页面底部的“Create”以开始创建新的集群。几分钟之后集群创建完成,即可正常使用:

接入您的集群

当集群状态为“available”之后,即代表可以正常使用。大家可以通过AWS Management Console中列出的Configuration Endpoint接入该集群。例如,我们可以使用Redis CLI接入集群并查询目标集群中的键空间/槽信息:

$ redis-cli -c -h mycluster.cache.amazonaws.com -p 6379

mycluster:6379> CLUSTER SLOTS
1) 1) (integer) 0
   2) (integer) 5461
   3) 1) "172.31.26.164"
      2) (integer) 6379
      3) "bb550a5c91a35d88caa9e636bffa1830093d4895"
   4) 1) "172.31.49.34"
      2) (integer) 6379
      3) "0200ff55ccc7e5cb6b4387443ebdb9652af23504"
   5) 1) "172.31.8.200"
      2) (integer) 6379
      3) "31646876f16baaab749043c406e06d0553287655"
2) ...
3) ...

mycluster:6379> CLUSTER KEYSLOT foo

(integer) 12182

通过以上响应,可以看到哈希范围在0到5461之间的键被存储在IP地址为172.31.26.164的Redis节点上(后续项为该节点的端口与唯一标识符)。该节点的副本则分别位于172.31.49.34及172.31.8.200两个IP地址上。集群上的其他两个分片也将返回类似的数据。我们可以在Management Console中找到槽范围:

虽然这部分信息的使用频率不高,但却给Redis客户端提供了重要信息:哈希槽与节点之间的映射关系。与Redis CLI一样,您使用的任何Redis SDK都需要具备集群化支持能力。虽然Amazon ElastiCache团队一直在努力扩展支持范围,但仍有部分SDK不支持集群模式。具体来讲,要支持集群模式,SDK需要实现以下两项功能:(1)支持键哈希算法;(2)能够维护槽到节点的映射。目前,我们已经在Java、Ruby、Go、Python等多种编程语言中提供集群模式支持。

在Python中,集群模式下ElastiCache for Redis的接入方式非常简单:

pip install redis-py-cluster
>>> import rediscluster
>>> startup_nodes = [{ "host": “mycluster.cache.amazonaws.com”, "port": "6379" }]
>>> client = rediscluster.RedisCluster(startup_nodes=[dict(host="MYHOST", port=6379)], decode_responses=True, skip_full_coverage_check=True)

# Retrieve the slot mapping (equivalent of "CLUSTER SLOTS")
>>> client.cluster('slots')
# Calculate hash slot for the key 'foo'
>>> client.cluster('keyslot', 'foo')
12182
# We can set a key without regard for which shard it will be stored on
# as the Redis client SDK will manage this mapping for us.
>>> client.set('foo', 'bar')
True
>>> client.get('foo')
'bar'

集群规模伸缩

随着应用程序规模的扩张与使用者数量的提升,大家可能需要进一步增加ElastiCache集群的容量。集群模式允许您通过添加或删除分片以实现横向扩展,而不必对单一节点进行纵向扩展。虽然在扩展过程中仍会对性能造成些许影响,但这种方法无需重新启动,因此能够显著提高集群可用性。

从概念上讲,服务器端的集群横向扩展其实并不难理解——直接添加或删除分片即可。在新节点准备就绪之后,集群将根据配置重新分配或平衡节点上的键空间。在使用ElastiCache for Redis时,重新平衡操作会自动进行。

如前所述,客户端负责维护自己的一套键空间与节点映射。不过客户端处理重新平衡的方式略有不同,这是因为键映射的变更不会通报至客户端。相反,集群会在客户端执行下一项操作时,通知其键已经发生移动:

$ redis-cli -c -h mycluster.cache.amazonaws.com -p 6379

mycluster:6379> GET foo

-MOVED 12182 172.31.8.18:6379

虽然在这种情况下,重新平衡需要经历两个步骤,而且可能导致一些额外的延迟。但无论如何,客户端已经知晓键映射有所更新这一信息。接下来,客户端可以快速恢复正常操作,继续充分利用集群中的各可用分片。

测试运行:集群扩展前与扩展后

ElastiCache会向Amazon CloudWatch发送多项指标,旨在帮助用户轻松监控集群的使用情况。其中几项重要指标有助于深入理解ElastiCache性能表现,并结合当前工作负载确定何时对集群进行规模伸缩。

为了实际演示规模伸缩过程,我们创建了如下图所示的简单测试工具。该测试工具中包含一个启用了集群模式的新ElastiCache for Redis集群。这个新集群最初包含一个分片与一个cache.r5.large类型的节点。我们使用AWS Lambda函数读取数据并将其写入该集群。此函数将捕捉数据读取/写入操作的响应时间,并将结果写入至Amazon CloudWatch。AWS Fargate Task提供的高人气负载测试工具Artillery能够将流量驱动至应用程序负载均衡器,进而调用该函数。需要注意的是,在此项测试中,每次函数调用都会打开一个指向ElastiCache的新连接。Lambda最佳实践要求大家在每次调用间重用执行环境时,同时重用该连接。

我们的测试工具能够有效模拟网站上的实际负载,例如将会话数据存储在ElastiCache当中。在Artillery的帮助下,我们计划运行多种测试方案,产生不同程度的对应负载,并从ELastiCache写入及读取会话数据。随着网站使用强度的不断提升(在模拟环境中),我们可能需要扩展集群以提高其性能表现。

上图中的CloudWatch仪表板展示了其中一轮测试的模拟结果。大约十五分钟之后,随着使用率的攀升(虽然集群中并未出现性能问题),我们开始向集群中添加一个新的分片,同时继续记录延迟指标。在新分片创建完成后,ElastiCache自动在两个分片之间重新平衡键空间,并将进度显示在Management Console当中……

……直到重新平衡完成:

如上所述,我们在Lambda函数中捕捉了指向ElastiCache的读取与写入响应时间,而后将该数据写入至CloudWatch Logs。使用CloudWatch Logs Insights,我们以30秒为增量对数据进行了分组,而后计算百分位数以分析这些操作的相应延迟。

在评估指标(例如延迟)时,百分位数往往比平均值更有意义,因为一、两个异常值就有可能改变平均值或者数据的整体分布态势。在以下图表中,我们列出了p50(第50百分位)、p90以及p95的对应值。这些指标意味着在特定时间窗口之内,有95%的请求获取响应的实际时长低于上报的指标(例如从10:06开始的30秒内,从Redis向Lambda函数返回的读取结果中有95%在1.2毫秒或更短时间内完成,包括网络延迟)。

需要强调的是,p90与p95两项指标在读取(低于2毫秒)及写入(低于6毫秒)方面表现出总体一致性。ElastiCache不仅速度快,而且在处理读取与写入操作方面也保持着良好的性能水准。如前所述,此项模拟采用cache.m5.large节点类型运行,节点类型的变化可能会对结果造成影响。如果您打算试试自己的模拟结果,可以在 Github上找到这项测试工具的源代码。

总结

Amazon ElastiCache for Redis极大降低了内存内数据存储方案的部署与运行门槛。在启用集群模式之后,您可以轻松实现缓存容量的规模伸缩,且完全不必担心影响到集群可用性或超出单一节点的容量上限。

正如前文所提到,ElastiCache for Redis将键空间映射的管理工作交由客户端负责。这种方式不仅降低了潜在单点故障的发生几率,同时也让服务器能够以最低延迟实现更高吞吐量。通常管理键空间的任何额外复杂性都会由客户端解决,但AWS团队正在积极努力拓宽兼容的客户端SDK。

ElastiCache在除基本缓存之外的各种用例中都能发挥积极作用。凭借集群模式,您可以将Redis扩展至数百TB容量并实现极高的写入操作能力。我们期待看到大家在实际应用中充分发挥ElastiCache的惊人实力。如果您有任何反馈意见,请在AWS ElastiCache论坛上与我们联系。

本篇作者

Josh Kahn

Amazon Web Services公司解决方案架构师。他与AWS客户一道为数据库项目提供技术指导与协助,帮助项目方利用AWS的强大功能提升解决方案价值。