条条大路通罗马 —— 使用 redis-py 访问 Amazon ElastiCache for redis 集群

0
0
{"value":"#### **一.前言**\n\n##### **什么是 Redis?**\n\nRedis 是一个非常流行的开源(BSD 许可)内存型数据库,通常用作数据库、缓存或消息代理。 为了达到最佳性能,Redis 使用内存数据集。根据用户场景,Redis 可以通过定期将数据集转储到磁盘,或者将每个指令附加到日志来持久化您的数据。\n\n##### **什么是 Redis 集群?**\n\nRedis 可以以两种模式运行:启用集群模式和禁用集群模式。禁用集群模式后,Redis 集群有一个保存了整个数据集的主节点,而启用集群模式后,数据集被拆分为多个主节点,称为“分片”(Shards)。每个分片由一个主分片和零或多个副本组成。使用多个分片,能存储比单节点容纳的大得多的数据集,且可通过使用多个节点来增加集群的吞吐量。\n\n在集群模式下,Redis 为每个键分配一个哈希槽来分布其数据,散列槽是一个0到16383(共 16384个)范围内的整数,使用 CRC 函数计算。每个分片负责处理哈希槽的子集。例如,如果有两个分片,第一个可能处理哈希槽0-8191,第二个可能处理哈希槽8192-16383。用户可随时向 Redis 集群添加更多的分片,但每个哈希槽只能由一个分片处理。\n\n##### **ElastiCache for Redis**\n\n[Amazon ElastiCache](https://aws.amazon.com/cn/elasticache/?trk=cndc-detail) 是一种 Web 服务,即托管的分布式内存数据库。它提供了一种高性能、可扩展且经济高效的缓存解决方案,可有效消除部署和管理分布式缓存数据库的复杂性。ElastiCache 支持所谓集群模式和非集群模式,具体可参照下图:\n\n![image.png](https://dev-media.amazoncloud.cn/16ab2bb5081241a087f8234ae8cd5cfd_image.png)\n\n[Amazon ElastiCache](https://aws.amazon.com/cn/elasticache/?trk=cndc-detail) for Redis 的特性如下:\n\n- 自动检测节点故障并从中恢复。\n- 最高支持多达500个分片(启用集群模式)。\n- 与其他 Amazon 服务集成,例如 Amazon EC2、[Amazon CloudWatch](https://aws.amazon.com/cn/cloudwatch/?trk=cndc-detail)、Amazon CloudTrail 和 [Amazon SNS](https://aws.amazon.com/cn/sns/?trk=cndc-detail)。\n- 可通过主实例和只读副本的数据同步获得高可用性,当出现问题时可以自动故障转移到只读副本。\n- 可以通过使用 Amazon Identity and Access Management 定义权限来控制对 ElastiCache for Redis 集群的访问。\n- 通过使用 Global Datastore for Redis 功能,可以跨 Amazon 区域进行完全托管、快速、可靠和安全的复制。使用此功能,您可以为 ElastiCache for Redis 创建跨区域只读副本集群,以实现跨 Amazon 区域的低延迟读取和灾难恢复。\n- 数据分层功能,除了将数据存储在内存中之外,还通过在每个节点中使用低成本的 SSD 为 Redis 提供了一种高性价比方案。它非常适合定期访问20%的整体数据集的工作负载,且在访问 SSD 上的数据时可以容忍额外延迟的应用程序。有关详细信息,请参阅官网的[数据分层](https://docs.aws.amazon.com/zh_cn/AmazonElastiCache/latest/red-ug/data-tiering.html)。\n\n##### **关于 redis-py**\n\n目前业界有许多中编程语言的客户端,可用来连接到 Redis 集群。在本文中,我们将介绍一个基于 Python 的客户端 redis-py,以及介绍如何使用此客户端来访问 Redis 集群和非集群中的数据。\n\nredis-py 由 Andy McCurdy 发起的开源项目,目前已经被 Redis 官方收录,尽管 redis-py 维护得很好,但一直以来它缺乏对集群模式的支持,因此 Python 用户不得不选择另外一个开源项目 redis-py-cluster,该项目由 Grokzen 开发,基于 antirez 的 redis-rb-cluster。基于此现状,Amazon 积极与支持 redis-py 的开源社区展开合作,为该客户端添加了集群模式支持。即 redis-py 现在已原生支持集群模式,这意味着您可以使用 redis-py 与 Redis Cluster 交互,而无需安装任何第三方库。此特性在4.1.0-rc1中发布。\n\n![image.png](https://dev-media.amazoncloud.cn/ef26cdb91d4c4b4880db0966a0261b0d_image.png)\n\n#### **二.功能测试**\n\n##### **1. 前置准备**\n\n此系列 Redis 文章中,已经有其他文章介绍过使用 Amazon 控制台来创建 ElastiCache 集群等相关操作,本文则会描述如何通过 Amazon CLI 来创建 ElastiCache 集群。\n\n找到一台 Linux 服务器,安装 Python3、pip、redis-py、Amazon CLI 程序,并且完成 Amazon CLI 的配置,具体可以参考[这里](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html)。安装完成后执行 amazon configure list,检查配置的完成。\n\n![image.png](https://dev-media.amazoncloud.cn/b1890ba907ca49ab8a90bbd17a34de67_image.png)\n\n##### **2. 搭建测试环境**\n\n###### **2.1 创建子网组**\n\n在创建 ElastiCache 集群之前,需要先创建子网组(Subnet Group),所谓子网组是 VPC 子网(通常是私有)的集合,ElastiCache 集群将运行在这些子网中。使用 Amazon CLI 创建 ElastiCache 的子网组,指令如下:\n\n```\\naws elasticache create-cache-subnet-group \\\\\\n --cache-subnet-group-name \\"mytestsg\\" \\\\\\n --cache-subnet-group-description \\"my test subnet group\\" \\\\\\n--subnet-ids \\"subnet-7120xxxx\\" \\"subnet-999exxxx\\" \\"subnet-8db5xxxx\\" \\"subnet-2d70xxxx\\"\\n```\n\n参数解释:\n\n–cache-subnet-group-name:子网组名称\n\n–cache-subnet-group-description:子网组描述\n\n–subnet-ids:子网 id,可填写多个,如果要开启多可用区(Multi-AZ)功能,请至少填写两个在不同可用区的子网 id。\n\n###### **2.2 创建 ElastiCache 集群**\n\n使用 Amazon CLI 命令行创建一个开启 TLS 和 auth 的 ElastiCache 集群,分为3个 shards,每个 shard 由1个主节点和1个读副本节点组成,共6节点,实例类型为 cache.t3.micro,版本为6.2.5,指令如下:\n\n```\\naws elasticache create-replication-group \\\\\\n--replication-group-id my-redis-cluster \\\\\\n--replication-group-description \\"test cluster with sharding mode\\" \\\\\\n--num-node-groups 2 \\\\\\n--replicas-per-node-group 2 \\\\\\n--cache-node-type cache.t3.micro \\\\\\n--cache-parameter-group default.redis6.x.cluster.on \\\\\\n--engine redis \\\\\\n--engine-version 6.2 \\\\\\n--cache-subnet-group-name mysg \\\\\\n--automatic-failover-enabled \\\\\\n--multi-az-enabled \\\\\\n--transit-encryption-enabled \\\\\\n--auth-token mytest1234567890\\n```\n\n![image.png](https://dev-media.amazoncloud.cn/5ec7a220ed7e48959b49acb521813de3_image.png)\n\n参数解释:\n\n–replication-group-id:集群 id\n\n–replication-group-description:集群描述\n\n–num-node-groups:shard 数量\n\n–replicas-per-node-group:每个 shard 中的读副本数量\n\n–cache-node-type:节点机型\n\n–cache-parameter-group:参数组\n\n–engine:缓存引擎类型\n\n–engine-version:缓存版本\n\n–cache-subnet-group-name:子网组名称\n\n–automatic-failover-enabled:启用故障转移\n\n–multi-az-enabled:启用多可用区\n\n–transit-encryption-enabled:启用传输中加密,必须与访问密码同时开启\n\n–auth-token:访问密码,必须与传输中加密同时开启,密码规则为:\n\n- 长度必须至少为16个字符且不超过128个字符。\n- 允许的特殊字符是为!、&、#、\$、^、<、>和-。\\n\\n通过如下指令,反复查看集群状态,当状态由 creating 变为 available 时,则代表此 ElastiCache 集群创建完毕。\\n\\n```\\naws elasticache describe-replication-groups --replication-group-id my-redis-cluster\\n```\\n\\n![image.png](https://dev-media.amazoncloud.cn/a9098ca61ca74f2f97fca29f9efc8fcb_image.png)\\n\\n创建不开启 TLS 以及 Auth 的 ElastiCache 集群方法同上,去掉上述指令中的 –transit-encryption-enabled 和 –auth-token 两个参数即可,具体的创建过程不再复述。\\n\\n###### **2.3 创建 ElastiCache Python 客户端**\\n\\nElastiCache 集群提供了一个 ConfigurationEndpoint,基于 redis-py 的客户端可连接到此 Endpoint,来与 ElastiCache 集群交互,查找 ConfigurationEndpoint 节点的指令如下:\\n\\n```\\naws elasticache describe-replication-groups --replication-group-id my-redis-cluster | grep -A 3 ConfigurationEndpoint\\n```\\n\\n![image.png](https://dev-media.amazoncloud.cn/7419011567b9413bb56595d10d18dc32_image.png)\\n\\n记录 Address 以及 Port,供 Python 客户端使用;连接到开启 TLS 的 ElastiCache 集群 Python 客户端的代码如下:\\n\\n```\\nimport redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\nprint(rc.ping())\\n```\\n\\n**请注意:**\\n\\n- 上述客户端代码基于 redis-py,是用来连接 ElastiCache 集群类型实例的,对于 ElastiCache 非集群类型实例,此代码无法正常工作。\\n- 上述客户端代码是用来连接到开启了 TLS 和 Auth 的 ElastiCache 集群的,如果要求连接到未开启 TLS 和 Auth 的 ElastiCache 集群,请在代码中去掉 ssl 和 password 两个参数。\\n- 代码中没有指定数据库(db)编号,因为 ElastiCache 集群仅支持一个数据库,且该数据库的分配编号始终为0。\\n- 代码中将 ElastiCache 集群中 ConfigurationEndpoint 节点的 IP/端口信息传递给客户端的构造函数,但其实可以选择集群中的任何节点。redis-py 是所谓的 smart 客户端,其初始化流程会自动发现集群中的所有其他节点,并且能够知道哪些节是主节点,哪些是读副本。\\n\\n###### **2.4 使用ElastiCache Python客户端**\\n\\n**常用指令**\\n\\n在成功连接到 ElastiCache 集群后,我们就可以与 ElastiCache 进行一些交互了,例如简单的 set、get 操作,具体示例代码如下:\\n\\n```\\nimport redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\nprint(rc.set('test', 'redis'))\\nprint(rc.get('test'))\\n\\n```\\n**Multi keys 指令**\\n\\nredis-py 支持集群模式下的 multi-key 指令,如 mset 和 mget,但需要确保所有 key 都被 hash 到同一个槽(slot),否则将会触发 RedisClusterException。Redis 官方为此实现了一个称为 hash tags 的概念,可用于强制将这些键存储在同一个哈希槽中,具体示例如下所示:\\n\\n```\\nimport redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\nprint(rc.mset({'{test}1': 'aws1', '{test}2': 'aws2'}))\\nprint(rc.mget('{test}1', '{test}2'))\\n\\n```\\n由上述示例,可以看到 hash tags 强制键存储在同一个哈希槽中的实现方式为,当 key 中包含 {} 的时候,不对整个 key 做 hash,而只对 {} 包括的字符串做 hash。\\n\\n**注意**:hash tags 可以让不同的 key 拥有相同的 hash 值,从而分配在同一个哈希槽里;但是 hash tags 可能会带来集群数据分配不均的问题,需要:(1)调整不同节点中哈希槽的数量,使数据分布尽量均匀;(2)避免对热点数据使用 hash tags 导致的分布不均。\\n\\n**Cluster Pipeline**\\n\\n当向 ElastiCache 服务器发送指令时,需要等待命令到达服务器并等待响应的返回。这称为往返时间 (RTT)。当有很多指令想要执行,可以生成一个指令列表,一次性执行发送,然后收到一个响应列表,其中响应的顺序对应于请求的顺序。这样我们只为整个指令列表产生一个 、RTT,从而提升批量指令执行的性能,即所谓 Pipeline。redis-py 支持集群模式下的 Pipeline,客户端示例代码如下:\\n\\n```\\nimport redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\np = rc.pipeline()\\np.set('redis','primary').set('elasticache','replica').get('redis').get('elasticache')\\nprint(p.execute())\\n```\\n\\nPipelines 指令流程如下:\\n\\n- 每个被缓存的 pipeline 指令都被分配到对应的 Redis shard 节点。\\n- 客户端向所有节点发送缓冲的指令。所有的指令都是并行发送到节点的,所以不需要在发送下一个指令之前等待上一个指令的 Response。\\n- 客户端等待所有节点的 Response。\\n- 所有节点的 Response 会被排序,对应于请求时的顺序,并最终返回给客户端。\\n- 注意:RedisCluster pipelines 当前只支持基于 key 的指令,而不支持管理指令。\\n\\n**redis-py 连接池**\\n\\n在集群模式下,redis-py 客户端在其内部为每一个 Shard 都创建了一个 Redis 实例。这些 Redis 实例都维护了一个连接池,并允许客户端与 Shard 通信时重用连接以降低性能损耗。\\n\\n**Read Only 模式**\\n\\n默认情况下,ElastiCache 集群的所有读写请求都只在主节点上进行,而读副本节点是热备(Standby),只同步主节点的数据,不处理任何读请求,其作用主要是出现异常情况下的故障转移,如果有接收到读请求,读副本会向请求的客户端返回MOVE指令的响应。通过在构建 RedisCluster 的时候设置 read_from_replicas=True 参数,启用 ReadOnly 模式,可以让读副本也能处理读请求,来分担主节点上的读压力。需要注意的是,由于 ElastiCache 集群主副之间的数据复制是异步的,存在一定延迟,故开启读副本 ReadOnly 模式可能会导致客户端读取到脏数据。示例代码如下:\\n\\n```\\nimport redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', read_from_replicas=True, ssl=True, password='mytest1234567890', port=6379)\\nprint(rc.get('test'))\\n```\\n\\n另外值得一提的是,redis-py 支持使用指定目标节点的方式与 ElastiCache 集群交互,如可指定所有节点、主节点、读副本节点等;注意此功能仅限于非 key-based 的指令,具体如下述代码所示:\\n\\n```\\nimport redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\nrc.cluster_meet('127.0.0.1', 6379, target_nodes=Redis.ALL_NODES)\\n# ping all replicas\\nrc.ping(target_nodes=Redis.REPLICAS)\\n# ping a random node\\nrc.ping(target_nodes=Redis.RANDOM)\\n# get the keys from all cluster nodes\\nrc.keys(target_nodes=Redis.ALL_NODES)\\n[b'foo1', b'foo2']\\n# execute bgsave in all primaries\\nrc.bgsave(Redis.PRIMARIES)\\n```\\n\\n#### **三.故障转移测试**\\n\\n本章描述 ElastiCache 集群的故障转移测试,主要关注在故障转移期间, ElastiCache 集群和 redis-py 的相关表现,比如影响到的数据范围、故障转移耗费的时长、客户端是否能自动重连等问题。\\n\\n##### **测试环境配置:**\\n\\nElastiCache 集群:\\n\\n- ElastiCache 版本:6.2.5\\n- Multi-AZ:Enabled\\n- Auto-failover:Enabled\\n- Node type:cache.t3.small\\n\\n客户端:\\n\\n- EC2:Amazon Linux 2\\n- Python 版本:3.8.5\\n- redis-py 版本:4.3.1\\n\\n测试代码:\\n\\n下述代码通过 redis-py 连接到 ElastiCache 集群,并且开启 ReadOnly 模式,并且为了减少自动恢复的时间,设置异常 retry 的次数为1。然后在循环中不停写入和读取数据,每次读写间隔300毫秒。\\n\\n```\\nimport redis\\nimport time\\nfrom datetime import datetime\\nrc = redis.RedisCluster(host='shards-3.ysor0h.clustercfg.usw2.cache.amazonaws.com', read_from_replicas=True, cluster_error_retry_attempts=1)\\nwhile True:\\n try:\\n current_time = datetime.now().strftime(\\"%H%M%S\\")\\n _key = \\"foo\\" + current_time\\n _value = \\"bar\\" + current_time\\n rc.set(_key, _value)\\n print(rc.get(_key))\\n except:\\n continue\\n time.sleep(0.3)\\n\\n```\\n##### **测试场景设计:**\\n\\n由于1 Shard 场景没有实际意义,故本测试主要覆盖2/3/4个Shards,每个 Shard 包含1 Primary + 2 Read Replica 的场景,具体如下表格所示:\\n\\n![image.png](https://dev-media.amazoncloud.cn/a3173b5dffcc4d069226c34c06523c11_image.png)\\n\\n##### **验证过程:**\\n\\n**场景1:2 shards with 1 Primary + 2 Read Replica / Shard**\\n\\n##### **测试结论:**\\n\\n在 Shard 1中执行 Test Failover,主副切换过程会经历30秒左右(多轮测试结果),在此期间 Shard 1不可用;另外此时整个集群状态为 fail,即 Shard 2也不可用。\\n\\n##### **测试过程:**\\n\\n执行下述命令,触发 Shard 1的 Failover:\\n\\n```\\naws elasticache test-failover --replication-group-id \\"my-redis-cluster\\" --node-group-id \\"0001\\"\\n```\\n\\n参数解释:\\n\\n–replication-group-id:表示ElastiCache 集群的 id\\n\\n–node-group-id:表示第几个shard,其值为类似”0001″、”0002″、”0003″。\\n\\n![image.png](https://dev-media.amazoncloud.cn/7d73e07d7e9d468c801bf721029f545b_image.png)\\n\\n当 Failover 被触发后,可执行如下指令查看整个 Failover 过程中的事件:\\n\\naws elasticache describe-events –duration 180\\n\\n参数解释:\\n\\n–duration:代表查询最近时间内的事件,单位为分钟。\\n\\n![image.png](https://dev-media.amazoncloud.cn/a32c6e32336c434fa66ac0f7d741e589_image.png)\\n\\n这里从下至上详细解释下截图中的各个事件:\\n\\n1. 10点13分32秒,ElastiCache 集群 Shard 1的 Failover 动作被触发,从此时间点开始,Shard 1不可用。\\n\\n此时测试脚本报错 Cluster is Down,如下图,与 Shard 1不可用的状态吻合。\\n\\n![image.png](https://dev-media.amazoncloud.cn/6b7af1f9bdf541cd96e76c96b226b0e4_image.png)\\n\\n使用 redis-cli 登录 ElastiCache 集群执行 cluster info 指令,可以看到此时集群的状态变为 fail,如下图所示,此状态代表整个集群不可用,即 Shard 2也不可用。\\n\\n![image.png](https://dev-media.amazoncloud.cn/6fc1778f4f9e4cfe8d24c36b9f828605_image.png)\\n\\n2. 10点14分59秒,Failover 动作执行完毕,可以看到读副本 my-redis-cluster-0001-003被提升为了主节点。直到此时间点,Shard 1恢复可用。\\n\\n此时测试脚本恢复正常,如下图,与 Shard 1变为可用的状态吻合。\\n\\n![image.png](https://dev-media.amazoncloud.cn/15bf1625022749eeba1379bc9db61099_image.png)\\n\\n使用 redis-cli 登录 ElastiCache \\n集群执行 cluster info 指令,可以看到此时 ElastiCache 集群的状态变为 ok,如下图所示,整个集群恢复可用,即 Shard 2也恢复可用。\\n\\n![image.png](https://dev-media.amazoncloud.cn/6416d311212549e698093289d8bf48e0_image.png)\\n\\n3. 10点17分23秒,之前的主节点 my-redis-cluster-0001-002被设置为读副本,开始从主节点复制数据。\\n4. 10点25分4秒,my-redis-cluster-0001-002节点数据恢复结束。直到此时间点,该节点恢复可用,整个集群完全恢复正常。\\n\\n在集群为2 Shards 场景下的 Failover 测试过程中,在步骤1和步骤2之间,为什么整个集群不可用,这里笔者经过仔细研究,发现原因如下,首先介绍 Failover 主副切换的具体步骤:\\n\\n1. Replica 发现自己的 Master 变为 FAIL\\n2. 将自己记录的集群 currentEpoch 加1,并广播 Failover Request 信息\\n3. 其他节点收到该信息,但只有集群中其他 Shard 的 master 能响应,判断请求者的合法性,并发送 FAILOVER_AUTH_ACK,对每一个 epoch 只发送一次 ack\\n4. 尝试 Failover 的 Replica 收集 FAILOVER_AUTH_ACK\\n5. 收到超过半数的 Master 回复后, Replica 开始执行 Failover,变为新的 Master\\n6. 清理复制链路,重置集群拓扑结构信息\\n7. 向集群内所有节点广播\\n\\n这里可以看到步骤5中,Failover 的执行需要集群中其他 Shard 超过半数的 Master 投票确认,但集群中只有2个 Shard,即只有2个 Master,此时没有超过半数的 Master 可以给出投票了。\\n\\n进一步猜测,整个集群不可用,是否与此时集群中没有超过半数的活跃 Master 相关?通过翻看 Redis 代码,https://github.com/redis/redis/blob/6.2/src/cluster.c 中的clusterFailoverReplaceYourMaster 函数即为 Failover 的实现,其中有调用到函数 clusterUpdateState,即对应上述步骤6的动作,进入此函数,看到代码实现为活跃 Master 数小于集群 Master 半数时,会将整个集群状态设置为 Fail,具体如下图:\\n\\n![image.png](https://dev-media.amazoncloud.cn/6db28c84c8c0499d94e1c0a6f6500626_image.png)\\n\\n至此,原因找到,即 Failover 触发后,集群会更新拓扑结构,当发现活跃 Master 数小于集群 Master 半数时,便将整个集群设置为 Fail 不可用,此时集群不接受读写请求,直到 Failover 完成,新 Master 重新上线,集群恢复可用。\\n\\n另外,Redis 官方建议 Redis 集群的创建,至少需要3个 Master(3 Shards),想必就是基于上述原因,故请大家尽量和保证在生产环境中的 ElastiCache 集群至少有3个 Master (3 Shards),保证在 Failover 的时候,不会引起整个集群不可用。\\n\\n**场景2:3 shards with 1 Primary + 2 Read Replica / Shard**\\n\\n##### **测试结论**:\\n\\n在 Shard 1中执行 Test Failover,主副切换过程会经历30秒左右(多轮测试结果),在此期间 Shard 1不可用;其他 Shard 仍然可用。\\n\\n##### **测试过程:**\\n\\n执行下述命令,触发 Shard 1的 Failover,具体可参照场景1中的描述。\\n\\n```\\naws elasticache test-failover --replication-group-id \\"my-redis-cluster\\" --node-group-id \\"0001\\"\\n```\\n\\n当 Failover 被触发后,可执行如下指令查看整个 Failover 过程中的事件,具体如下图:\\n\\n```\\naws elasticache describe-events --duration 20\\n```\\n\\n![image.png](https://dev-media.amazoncloud.cn/d47f6c84c77b4d87a42bb071c9a1bcef_image.png)\\n\\n1. 13点53分19秒,ElastiCache 集群 Shard 1的 Failover 动作被触发,从此时间点开始,Shard 1不可用,而其他 Shards 可用。\\n2. 13点53分54秒,Failover 动作执行完毕,可以看到读副本 shards-3-0001-002被提升为了主节点。直到此时间点,Shard 1恢复可用。\\n3. 13点55分08秒,之前的主节点 shards-3-0001-003被设置为读副本,开始从主节点复制数据。\\n4. 13点59分57秒,shards-3-0001-002节点数据恢复结束。直到此时间点,该节点恢复可用,整个集群完全恢复正常。\\n\\n**场景3:4 shards with 1 Primary + 2 Read Replica / Shard**\\n\\n##### **测试结论:**\\n\\n在 Shard 1中执行 Test Failover,主副切换过程会经历30秒左右(多轮测试结果),在此期间 \\nShard 1不可用;其他 Shard 仍然可用。\\n\\n##### **测试过程:**\\n\\n场景3的测试过程与场景2类似,此处就不在复述。\\n\\n#### **四.总结**\\n\\n本文带领大家了解了如何在 ElastiCache 启用集群模式的情况下使用 redis-py,我们还研究了 Multi-Key、Pipeline 等指令在集群下的工作模式,以及在集群 Failover 下的表现,您也可以移步 redis-py 的 github,以了解更多的使用细节。\\n\\n#### **相关博客**\\n\\n[条条大路通罗马 —— 使用 redisson连接 Amazon ElastiCache for redis 集群](https://aws.amazon.com/cn/blogs/china/connecting-amazon-elasticache-for-redis-cluster-using-redisson/)\\n\\n[条条大路通罗马 —— 使用 go-redis 连接 Amazon ElastiCache for Redis 集群](https://aws.amazon.com/cn/blogs/china/all-roads-lead-to-rome-use-go-redis-to-connect-amazon-elasticache-for-redis-cluster/)\\n\\n[条条大路通罗马系列- 使用 Hiredis-cluster 连接Amazon ElastiCache for Redis 集群](https://aws.amazon.com/cn/blogs/china/all-roads-to-rome-series-connect-amazon-elasticache-for-redis-cluster-with-hiredis-cluster/)\\n\\n#### **本篇作者**\\n\\n![image.png](https://dev-media.amazoncloud.cn/111a9138a0f44297969f2ad1453d29d7_image.png)\\n\\n#### **陈超**\\n\\nAmazon 迁移解决方案架构师,主要负责 Amazon 迁移相关的技术支持工作,同时致力于 Amazon 云服务在国内的应用及推广。加入 Amazon 之前,曾在阿里巴巴工作8年,历任研发工程师、云计算解决方案架构师等,熟悉传统企业 IT 、互联网架构,在企业应用架构方面有多年实践经验。","render":"<h4><a id=\\"_0\\"></a><strong>一.前言</strong></h4>\\n<h5><a id=\\"_Redis_2\\"></a><strong>什么是 Redis?</strong></h5>\\n<p>Redis 是一个非常流行的开源(BSD 许可)内存型数据库,通常用作数据库、缓存或消息代理。 为了达到最佳性能,Redis 使用内存数据集。根据用户场景,Redis 可以通过定期将数据集转储到磁盘,或者将每个指令附加到日志来持久化您的数据。</p>\\n<h5><a id=\\"_Redis__6\\"></a><strong>什么是 Redis 集群?</strong></h5>\\n<p>Redis 可以以两种模式运行:启用集群模式和禁用集群模式。禁用集群模式后,Redis 集群有一个保存了整个数据集的主节点,而启用集群模式后,数据集被拆分为多个主节点,称为“分片”(Shards)。每个分片由一个主分片和零或多个副本组成。使用多个分片,能存储比单节点容纳的大得多的数据集,且可通过使用多个节点来增加集群的吞吐量。</p>\\n<p>在集群模式下,Redis 为每个键分配一个哈希槽来分布其数据,散列槽是一个0到16383(共 16384个)范围内的整数,使用 CRC 函数计算。每个分片负责处理哈希槽的子集。例如,如果有两个分片,第一个可能处理哈希槽0-8191,第二个可能处理哈希槽8192-16383。用户可随时向 Redis 集群添加更多的分片,但每个哈希槽只能由一个分片处理。</p>\\n<h5><a id=\\"ElastiCache_for_Redis_12\\"></a><strong>ElastiCache for Redis</strong></h5>\\n<p>Amazon ElastiCache 是一种 Web 服务,即托管的分布式内存数据库。它提供了一种高性能、可扩展且经济高效的缓存解决方案,可有效消除部署和管理分布式缓存数据库的复杂性。ElastiCache 支持所谓集群模式和非集群模式,具体可参照下图:</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/16ab2bb5081241a087f8234ae8cd5cfd_image.png\\" alt=\\"image.png\\" /></p>\\n<p>Amazon ElastiCache for Redis 的特性如下:</p>\\n<ul>\\n<li>自动检测节点故障并从中恢复。</li>\\n<li>最高支持多达500个分片(启用集群模式)。</li>\\n<li>与其他 Amazon 服务集成,例如 Amazon EC2、Amazon CloudWatch、Amazon CloudTrail 和 Amazon SNS。</li>\\n<li>可通过主实例和只读副本的数据同步获得高可用性,当出现问题时可以自动故障转移到只读副本。</li>\\n<li>可以通过使用 Amazon Identity and Access Management 定义权限来控制对 ElastiCache for Redis 集群的访问。</li>\\n<li>通过使用 Global Datastore for Redis 功能,可以跨 Amazon 区域进行完全托管、快速、可靠和安全的复制。使用此功能,您可以为 ElastiCache for Redis 创建跨区域只读副本集群,以实现跨 Amazon 区域的低延迟读取和灾难恢复。</li>\\n<li>数据分层功能,除了将数据存储在内存中之外,还通过在每个节点中使用低成本的 SSD 为 Redis 提供了一种高性价比方案。它非常适合定期访问20%的整体数据集的工作负载,且在访问 SSD 上的数据时可以容忍额外延迟的应用程序。有关详细信息,请参阅官网的<a href=\\"https://docs.aws.amazon.com/zh_cn/AmazonElastiCache/latest/red-ug/data-tiering.html\\" target=\\"_blank\\">数据分层</a>。</li>\\n</ul>\\n<h5><a id=\\"_redispy_28\\"></a><strong>关于 redis-py</strong></h5>\\n<p>目前业界有许多中编程语言的客户端,可用来连接到 Redis 集群。在本文中,我们将介绍一个基于 Python 的客户端 redis-py,以及介绍如何使用此客户端来访问 Redis 集群和非集群中的数据。</p>\\n<p>redis-py 由 Andy McCurdy 发起的开源项目,目前已经被 Redis 官方收录,尽管 redis-py 维护得很好,但一直以来它缺乏对集群模式的支持,因此 Python 用户不得不选择另外一个开源项目 redis-py-cluster,该项目由 Grokzen 开发,基于 antirez 的 redis-rb-cluster。基于此现状,Amazon 积极与支持 redis-py 的开源社区展开合作,为该客户端添加了集群模式支持。即 redis-py 现在已原生支持集群模式,这意味着您可以使用 redis-py 与 Redis Cluster 交互,而无需安装任何第三方库。此特性在4.1.0-rc1中发布。</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/ef26cdb91d4c4b4880db0966a0261b0d_image.png\\" alt=\\"image.png\\" /></p>\\n<h4><a id=\\"_36\\"></a><strong>二.功能测试</strong></h4>\\n<h5><a id=\\"1__38\\"></a><strong>1. 前置准备</strong></h5>\\n<p>此系列 Redis 文章中,已经有其他文章介绍过使用 Amazon 控制台来创建 ElastiCache 集群等相关操作,本文则会描述如何通过 Amazon CLI 来创建 ElastiCache 集群。</p>\\n<p>找到一台 Linux 服务器,安装 Python3、pip、redis-py、Amazon CLI 程序,并且完成 Amazon CLI 的配置,具体可以参考<a href=\\"https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html\\" target=\\"_blank\\">这里</a>。安装完成后执行 amazon configure list,检查配置的完成。</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/b1890ba907ca49ab8a90bbd17a34de67_image.png\\" alt=\\"image.png\\" /></p>\\n<h5><a id=\\"2__46\\"></a><strong>2. 搭建测试环境</strong></h5>\\n<h6><a id=\\"21__48\\"></a><strong>2.1 创建子网组</strong></h6>\\n<p>在创建 ElastiCache 集群之前,需要先创建子网组(Subnet Group),所谓子网组是 VPC 子网(通常是私有)的集合,ElastiCache 集群将运行在这些子网中。使用 Amazon CLI 创建 ElastiCache 的子网组,指令如下:</p>\\n<pre><code class=\\"lang-\\">aws elasticache create-cache-subnet-group \\\\\\n --cache-subnet-group-name &quot;mytestsg&quot; \\\\\\n --cache-subnet-group-description &quot;my test subnet group&quot; \\\\\\n--subnet-ids &quot;subnet-7120xxxx&quot; &quot;subnet-999exxxx&quot; &quot;subnet-8db5xxxx&quot; &quot;subnet-2d70xxxx&quot;\\n</code></pre>\\n<p>参数解释:</p>\\n<p>–cache-subnet-group-name:子网组名称</p>\\n<p>–cache-subnet-group-description:子网组描述</p>\\n<p>–subnet-ids:子网 id,可填写多个,如果要开启多可用区(Multi-AZ)功能,请至少填写两个在不同可用区的子网 id。</p>\\n<h6><a id=\\"22__ElastiCache__67\\"></a><strong>2.2 创建 ElastiCache 集群</strong></h6>\\n<p>使用 Amazon CLI 命令行创建一个开启 TLS 和 auth 的 ElastiCache 集群,分为3个 shards,每个 shard 由1个主节点和1个读副本节点组成,共6节点,实例类型为 cache.t3.micro,版本为6.2.5,指令如下:</p>\\n<pre><code class=\\"lang-\\">aws elasticache create-replication-group \\\\\\n--replication-group-id my-redis-cluster \\\\\\n--replication-group-description &quot;test cluster with sharding mode&quot; \\\\\\n--num-node-groups 2 \\\\\\n--replicas-per-node-group 2 \\\\\\n--cache-node-type cache.t3.micro \\\\\\n--cache-parameter-group default.redis6.x.cluster.on \\\\\\n--engine redis \\\\\\n--engine-version 6.2 \\\\\\n--cache-subnet-group-name mysg \\\\\\n--automatic-failover-enabled \\\\\\n--multi-az-enabled \\\\\\n--transit-encryption-enabled \\\\\\n--auth-token mytest1234567890\\n</code></pre>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/5ec7a220ed7e48959b49acb521813de3_image.png\\" alt=\\"image.png\\" /></p>\\n<p>参数解释:</p>\\n<p>–replication-group-id:集群 id</p>\\n<p>–replication-group-description:集群描述</p>\\n<p>–num-node-groups:shard 数量</p>\\n<p>–replicas-per-node-group:每个 shard 中的读副本数量</p>\\n<p>–cache-node-type:节点机型</p>\\n<p>–cache-parameter-group:参数组</p>\\n<p>–engine:缓存引擎类型</p>\\n<p>–engine-version:缓存版本</p>\\n<p>–cache-subnet-group-name:子网组名称</p>\\n<p>–automatic-failover-enabled:启用故障转移</p>\\n<p>–multi-az-enabled:启用多可用区</p>\\n<p>–transit-encryption-enabled:启用传输中加密,必须与访问密码同时开启</p>\\n<p>–auth-token:访问密码,必须与传输中加密同时开启,密码规则为:</p>\\n<ul>\\n<li>长度必须至少为16个字符且不超过128个字符。</li>\\n<li>允许的特殊字符是为!、&amp;、#、\$、^、&lt;、&gt;和-。</li>\\n</ul>\n<p>通过如下指令,反复查看集群状态,当状态由 creating 变为 available 时,则代表此 ElastiCache 集群创建完毕。</p>\n<pre><code class=\\"lang-\\">aws elasticache describe-replication-groups --replication-group-id my-redis-cluster\\n</code></pre>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/a9098ca61ca74f2f97fca29f9efc8fcb_image.png\\" alt=\\"image.png\\" /></p>\n<p>创建不开启 TLS 以及 Auth 的 ElastiCache 集群方法同上,去掉上述指令中的 –transit-encryption-enabled 和 –auth-token 两个参数即可,具体的创建过程不再复述。</p>\n<h6><a id=\\"23__ElastiCache_Python__131\\"></a><strong>2.3 创建 ElastiCache Python 客户端</strong></h6>\\n<p>ElastiCache 集群提供了一个 ConfigurationEndpoint,基于 redis-py 的客户端可连接到此 Endpoint,来与 ElastiCache 集群交互,查找 ConfigurationEndpoint 节点的指令如下:</p>\n<pre><code class=\\"lang-\\">aws elasticache describe-replication-groups --replication-group-id my-redis-cluster | grep -A 3 ConfigurationEndpoint\\n</code></pre>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/7419011567b9413bb56595d10d18dc32_image.png\\" alt=\\"image.png\\" /></p>\n<p>记录 Address 以及 Port,供 Python 客户端使用;连接到开启 TLS 的 ElastiCache 集群 Python 客户端的代码如下:</p>\n<pre><code class=\\"lang-\\">import redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\nprint(rc.ping())\\n</code></pre>\\n<p><strong>请注意:</strong></p>\\n<ul>\\n<li>上述客户端代码基于 redis-py,是用来连接 ElastiCache 集群类型实例的,对于 ElastiCache 非集群类型实例,此代码无法正常工作。</li>\n<li>上述客户端代码是用来连接到开启了 TLS 和 Auth 的 ElastiCache 集群的,如果要求连接到未开启 TLS 和 Auth 的 ElastiCache 集群,请在代码中去掉 ssl 和 password 两个参数。</li>\n<li>代码中没有指定数据库(db)编号,因为 ElastiCache 集群仅支持一个数据库,且该数据库的分配编号始终为0。</li>\n<li>代码中将 ElastiCache 集群中 ConfigurationEndpoint 节点的 IP/端口信息传递给客户端的构造函数,但其实可以选择集群中的任何节点。redis-py 是所谓的 smart 客户端,其初始化流程会自动发现集群中的所有其他节点,并且能够知道哪些节是主节点,哪些是读副本。</li>\n</ul>\\n<h6><a id=\\"24_ElastiCache_Python_156\\"></a><strong>2.4 使用ElastiCache Python客户端</strong></h6>\\n<p><strong>常用指令</strong></p>\\n<p>在成功连接到 ElastiCache 集群后,我们就可以与 ElastiCache 进行一些交互了,例如简单的 set、get 操作,具体示例代码如下:</p>\n<pre><code class=\\"lang-\\">import redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\nprint(rc.set('test', 'redis'))\\nprint(rc.get('test'))\\n\\n</code></pre>\\n<p><strong>Multi keys 指令</strong></p>\\n<p>redis-py 支持集群模式下的 multi-key 指令,如 mset 和 mget,但需要确保所有 key 都被 hash 到同一个槽(slot),否则将会触发 RedisClusterException。Redis 官方为此实现了一个称为 hash tags 的概念,可用于强制将这些键存储在同一个哈希槽中,具体示例如下所示:</p>\n<pre><code class=\\"lang-\\">import redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\nprint(rc.mset({'{test}1': 'aws1', '{test}2': 'aws2'}))\\nprint(rc.mget('{test}1', '{test}2'))\\n\\n</code></pre>\\n<p>由上述示例,可以看到 hash tags 强制键存储在同一个哈希槽中的实现方式为,当 key 中包含 {} 的时候,不对整个 key 做 hash,而只对 {} 包括的字符串做 hash。</p>\n<p><strong>注意</strong>:hash tags 可以让不同的 key 拥有相同的 hash 值,从而分配在同一个哈希槽里;但是 hash tags 可能会带来集群数据分配不均的问题,需要:(1)调整不同节点中哈希槽的数量,使数据分布尽量均匀;(2)避免对热点数据使用 hash tags 导致的分布不均。</p>\\n<p><strong>Cluster Pipeline</strong></p>\\n<p>当向 ElastiCache 服务器发送指令时,需要等待命令到达服务器并等待响应的返回。这称为往返时间 (RTT)。当有很多指令想要执行,可以生成一个指令列表,一次性执行发送,然后收到一个响应列表,其中响应的顺序对应于请求的顺序。这样我们只为整个指令列表产生一个 、RTT,从而提升批量指令执行的性能,即所谓 Pipeline。redis-py 支持集群模式下的 Pipeline,客户端示例代码如下:</p>\n<pre><code class=\\"lang-\\">import redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\np = rc.pipeline()\\np.set('redis','primary').set('elasticache','replica').get('redis').get('elasticache')\\nprint(p.execute())\\n</code></pre>\\n<p>Pipelines 指令流程如下:</p>\n<ul>\\n<li>每个被缓存的 pipeline 指令都被分配到对应的 Redis shard 节点。</li>\n<li>客户端向所有节点发送缓冲的指令。所有的指令都是并行发送到节点的,所以不需要在发送下一个指令之前等待上一个指令的 Response。</li>\n<li>客户端等待所有节点的 Response。</li>\n<li>所有节点的 Response 会被排序,对应于请求时的顺序,并最终返回给客户端。</li>\n<li>注意:RedisCluster pipelines 当前只支持基于 key 的指令,而不支持管理指令。</li>\n</ul>\\n<p><strong>redis-py 连接池</strong></p>\\n<p>在集群模式下,redis-py 客户端在其内部为每一个 Shard 都创建了一个 Redis 实例。这些 Redis 实例都维护了一个连接池,并允许客户端与 Shard 通信时重用连接以降低性能损耗。</p>\n<p><strong>Read Only 模式</strong></p>\\n<p>默认情况下,ElastiCache 集群的所有读写请求都只在主节点上进行,而读副本节点是热备(Standby),只同步主节点的数据,不处理任何读请求,其作用主要是出现异常情况下的故障转移,如果有接收到读请求,读副本会向请求的客户端返回MOVE指令的响应。通过在构建 RedisCluster 的时候设置 read_from_replicas=True 参数,启用 ReadOnly 模式,可以让读副本也能处理读请求,来分担主节点上的读压力。需要注意的是,由于 ElastiCache 集群主副之间的数据复制是异步的,存在一定延迟,故开启读副本 ReadOnly 模式可能会导致客户端读取到脏数据。示例代码如下:</p>\n<pre><code class=\\"lang-\\">import redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', read_from_replicas=True, ssl=True, password='mytest1234567890', port=6379)\\nprint(rc.get('test'))\\n</code></pre>\\n<p>另外值得一提的是,redis-py 支持使用指定目标节点的方式与 ElastiCache 集群交互,如可指定所有节点、主节点、读副本节点等;注意此功能仅限于非 key-based 的指令,具体如下述代码所示:</p>\n<pre><code class=\\"lang-\\">import redis\\nrc = redis.RedisCluster(host='clustercfg.my-redis-cluster.ysor0h.usw2.cache.amazonaws.com', ssl=True, password='mytest1234567890', port=6379)\\nrc.cluster_meet('127.0.0.1', 6379, target_nodes=Redis.ALL_NODES)\\n# ping all replicas\\nrc.ping(target_nodes=Redis.REPLICAS)\\n# ping a random node\\nrc.ping(target_nodes=Redis.RANDOM)\\n# get the keys from all cluster nodes\\nrc.keys(target_nodes=Redis.ALL_NODES)\\n[b'foo1', b'foo2']\\n# execute bgsave in all primaries\\nrc.bgsave(Redis.PRIMARIES)\\n</code></pre>\\n<h4><a id=\\"_235\\"></a><strong>三.故障转移测试</strong></h4>\\n<p>本章描述 ElastiCache 集群的故障转移测试,主要关注在故障转移期间, ElastiCache 集群和 redis-py 的相关表现,比如影响到的数据范围、故障转移耗费的时长、客户端是否能自动重连等问题。</p>\n<h5><a id=\\"_239\\"></a><strong>测试环境配置:</strong></h5>\\n<p>ElastiCache 集群:</p>\n<ul>\\n<li>ElastiCache 版本:6.2.5</li>\n<li>Multi-AZ:Enabled</li>\n<li>Auto-failover:Enabled</li>\n<li>Node type:cache.t3.small</li>\n</ul>\\n<p>客户端:</p>\n<ul>\\n<li>EC2:Amazon Linux 2</li>\n<li>Python 版本:3.8.5</li>\n<li>redis-py 版本:4.3.1</li>\n</ul>\\n<p>测试代码:</p>\n<p>下述代码通过 redis-py 连接到 ElastiCache 集群,并且开启 ReadOnly 模式,并且为了减少自动恢复的时间,设置异常 retry 的次数为1。然后在循环中不停写入和读取数据,每次读写间隔300毫秒。</p>\n<pre><code class=\\"lang-\\">import redis\\nimport time\\nfrom datetime import datetime\\nrc = redis.RedisCluster(host='shards-3.ysor0h.clustercfg.usw2.cache.amazonaws.com', read_from_replicas=True, cluster_error_retry_attempts=1)\\nwhile True:\\n try:\\n current_time = datetime.now().strftime(&quot;%H%M%S&quot;)\\n _key = &quot;foo&quot; + current_time\\n _value = &quot;bar&quot; + current_time\\n rc.set(_key, _value)\\n print(rc.get(_key))\\n except:\\n continue\\n time.sleep(0.3)\\n\\n</code></pre>\\n<h5><a id=\\"_275\\"></a><strong>测试场景设计:</strong></h5>\\n<p>由于1 Shard 场景没有实际意义,故本测试主要覆盖2/3/4个Shards,每个 Shard 包含1 Primary + 2 Read Replica 的场景,具体如下表格所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/a3173b5dffcc4d069226c34c06523c11_image.png\\" alt=\\"image.png\\" /></p>\n<h5><a id=\\"_281\\"></a><strong>验证过程:</strong></h5>\\n<p><strong>场景1:2 shards with 1 Primary + 2 Read Replica / Shard</strong></p>\\n<h5><a id=\\"_285\\"></a><strong>测试结论:</strong></h5>\\n<p>在 Shard 1中执行 Test Failover,主副切换过程会经历30秒左右(多轮测试结果),在此期间 Shard 1不可用;另外此时整个集群状态为 fail,即 Shard 2也不可用。</p>\n<h5><a id=\\"_289\\"></a><strong>测试过程:</strong></h5>\\n<p>执行下述命令,触发 Shard 1的 Failover:</p>\n<pre><code class=\\"lang-\\">aws elasticache test-failover --replication-group-id &quot;my-redis-cluster&quot; --node-group-id &quot;0001&quot;\\n</code></pre>\\n<p>参数解释:</p>\n<p>–replication-group-id:表示ElastiCache 集群的 id</p>\n<p>–node-group-id:表示第几个shard,其值为类似”0001″、”0002″、”0003″。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/7d73e07d7e9d468c801bf721029f545b_image.png\\" alt=\\"image.png\\" /></p>\n<p>当 Failover 被触发后,可执行如下指令查看整个 Failover 过程中的事件:</p>\n<p>aws elasticache describe-events –duration 180</p>\n<p>参数解释:</p>\n<p>–duration:代表查询最近时间内的事件,单位为分钟。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/a32c6e32336c434fa66ac0f7d741e589_image.png\\" alt=\\"image.png\\" /></p>\n<p>这里从下至上详细解释下截图中的各个事件:</p>\n<ol>\\n<li>10点13分32秒,ElastiCache 集群 Shard 1的 Failover 动作被触发,从此时间点开始,Shard 1不可用。</li>\n</ol>\\n<p>此时测试脚本报错 Cluster is Down,如下图,与 Shard 1不可用的状态吻合。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/6b7af1f9bdf541cd96e76c96b226b0e4_image.png\\" alt=\\"image.png\\" /></p>\n<p>使用 redis-cli 登录 ElastiCache 集群执行 cluster info 指令,可以看到此时集群的状态变为 fail,如下图所示,此状态代表整个集群不可用,即 Shard 2也不可用。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/6fc1778f4f9e4cfe8d24c36b9f828605_image.png\\" alt=\\"image.png\\" /></p>\n<ol start=\\"2\\">\\n<li>10点14分59秒,Failover 动作执行完毕,可以看到读副本 my-redis-cluster-0001-003被提升为了主节点。直到此时间点,Shard 1恢复可用。</li>\n</ol>\\n<p>此时测试脚本恢复正常,如下图,与 Shard 1变为可用的状态吻合。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/15bf1625022749eeba1379bc9db61099_image.png\\" alt=\\"image.png\\" /></p>\n<p>使用 redis-cli 登录 ElastiCache<br />\\n集群执行 cluster info 指令,可以看到此时 ElastiCache 集群的状态变为 ok,如下图所示,整个集群恢复可用,即 Shard 2也恢复可用。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/6416d311212549e698093289d8bf48e0_image.png\\" alt=\\"image.png\\" /></p>\n<ol start=\\"3\\">\\n<li>10点17分23秒,之前的主节点 my-redis-cluster-0001-002被设置为读副本,开始从主节点复制数据。</li>\n<li>10点25分4秒,my-redis-cluster-0001-002节点数据恢复结束。直到此时间点,该节点恢复可用,整个集群完全恢复正常。</li>\n</ol>\\n<p>在集群为2 Shards 场景下的 Failover 测试过程中,在步骤1和步骤2之间,为什么整个集群不可用,这里笔者经过仔细研究,发现原因如下,首先介绍 Failover 主副切换的具体步骤:</p>\n<ol>\\n<li>Replica 发现自己的 Master 变为 FAIL</li>\n<li>将自己记录的集群 currentEpoch 加1,并广播 Failover Request 信息</li>\n<li>其他节点收到该信息,但只有集群中其他 Shard 的 master 能响应,判断请求者的合法性,并发送 FAILOVER_AUTH_ACK,对每一个 epoch 只发送一次 ack</li>\n<li>尝试 Failover 的 Replica 收集 FAILOVER_AUTH_ACK</li>\n<li>收到超过半数的 Master 回复后, Replica 开始执行 Failover,变为新的 Master</li>\n<li>清理复制链路,重置集群拓扑结构信息</li>\n<li>向集群内所有节点广播</li>\n</ol>\\n<p>这里可以看到步骤5中,Failover 的执行需要集群中其他 Shard 超过半数的 Master 投票确认,但集群中只有2个 Shard,即只有2个 Master,此时没有超过半数的 Master 可以给出投票了。</p>\n<p>进一步猜测,整个集群不可用,是否与此时集群中没有超过半数的活跃 Master 相关?通过翻看 Redis 代码,https://github.com/redis/redis/blob/6.2/src/cluster.c 中的clusterFailoverReplaceYourMaster 函数即为 Failover 的实现,其中有调用到函数 clusterUpdateState,即对应上述步骤6的动作,进入此函数,看到代码实现为活跃 Master 数小于集群 Master 半数时,会将整个集群状态设置为 Fail,具体如下图:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/6db28c84c8c0499d94e1c0a6f6500626_image.png\\" alt=\\"image.png\\" /></p>\n<p>至此,原因找到,即 Failover 触发后,集群会更新拓扑结构,当发现活跃 Master 数小于集群 Master 半数时,便将整个集群设置为 Fail 不可用,此时集群不接受读写请求,直到 Failover 完成,新 Master 重新上线,集群恢复可用。</p>\n<p>另外,Redis 官方建议 Redis 集群的创建,至少需要3个 Master(3 Shards),想必就是基于上述原因,故请大家尽量和保证在生产环境中的 ElastiCache 集群至少有3个 Master (3 Shards),保证在 Failover 的时候,不会引起整个集群不可用。</p>\n<p><strong>场景2:3 shards with 1 Primary + 2 Read Replica / Shard</strong></p>\\n<h5><a id=\\"_363\\"></a><strong>测试结论</strong>:</h5>\\n<p>在 Shard 1中执行 Test Failover,主副切换过程会经历30秒左右(多轮测试结果),在此期间 Shard 1不可用;其他 Shard 仍然可用。</p>\n<h5><a id=\\"_367\\"></a><strong>测试过程:</strong></h5>\\n<p>执行下述命令,触发 Shard 1的 Failover,具体可参照场景1中的描述。</p>\n<pre><code class=\\"lang-\\">aws elasticache test-failover --replication-group-id &quot;my-redis-cluster&quot; --node-group-id &quot;0001&quot;\\n</code></pre>\\n<p>当 Failover 被触发后,可执行如下指令查看整个 Failover 过程中的事件,具体如下图:</p>\n<pre><code class=\\"lang-\\">aws elasticache describe-events --duration 20\\n</code></pre>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/d47f6c84c77b4d87a42bb071c9a1bcef_image.png\\" alt=\\"image.png\\" /></p>\n<ol>\\n<li>13点53分19秒,ElastiCache 集群 Shard 1的 Failover 动作被触发,从此时间点开始,Shard 1不可用,而其他 Shards 可用。</li>\n<li>13点53分54秒,Failover 动作执行完毕,可以看到读副本 shards-3-0001-002被提升为了主节点。直到此时间点,Shard 1恢复可用。</li>\n<li>13点55分08秒,之前的主节点 shards-3-0001-003被设置为读副本,开始从主节点复制数据。</li>\n<li>13点59分57秒,shards-3-0001-002节点数据恢复结束。直到此时间点,该节点恢复可用,整个集群完全恢复正常。</li>\n</ol>\\n<p><strong>场景3:4 shards with 1 Primary + 2 Read Replica / Shard</strong></p>\\n<h5><a id=\\"_390\\"></a><strong>测试结论:</strong></h5>\\n<p>在 Shard 1中执行 Test Failover,主副切换过程会经历30秒左右(多轮测试结果),在此期间<br />\\nShard 1不可用;其他 Shard 仍然可用。</p>\n<h5><a id=\\"_395\\"></a><strong>测试过程:</strong></h5>\\n<p>场景3的测试过程与场景2类似,此处就不在复述。</p>\n<h4><a id=\\"_399\\"></a><strong>四.总结</strong></h4>\\n<p>本文带领大家了解了如何在 ElastiCache 启用集群模式的情况下使用 redis-py,我们还研究了 Multi-Key、Pipeline 等指令在集群下的工作模式,以及在集群 Failover 下的表现,您也可以移步 redis-py 的 github,以了解更多的使用细节。</p>\n<h4><a id=\\"_403\\"></a><strong>相关博客</strong></h4>\\n<p><a href=\\"https://aws.amazon.com/cn/blogs/china/connecting-amazon-elasticache-for-redis-cluster-using-redisson/\\" target=\\"_blank\\">条条大路通罗马 —— 使用 redisson连接 Amazon ElastiCache for redis 集群</a></p>\\n<p><a href=\\"https://aws.amazon.com/cn/blogs/china/all-roads-lead-to-rome-use-go-redis-to-connect-amazon-elasticache-for-redis-cluster/\\" target=\\"_blank\\">条条大路通罗马 —— 使用 go-redis 连接 Amazon ElastiCache for Redis 集群</a></p>\\n<p><a href=\\"https://aws.amazon.com/cn/blogs/china/all-roads-to-rome-series-connect-amazon-elasticache-for-redis-cluster-with-hiredis-cluster/\\" target=\\"_blank\\">条条大路通罗马系列- 使用 Hiredis-cluster 连接Amazon ElastiCache for Redis 集群</a></p>\\n<h4><a id=\\"_411\\"></a><strong>本篇作者</strong></h4>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/111a9138a0f44297969f2ad1453d29d7_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"_415\\"></a><strong>陈超</strong></h4>\\n<p>Amazon 迁移解决方案架构师,主要负责 Amazon 迁移相关的技术支持工作,同时致力于 Amazon 云服务在国内的应用及推广。加入 Amazon 之前,曾在阿里巴巴工作8年,历任研发工程师、云计算解决方案架构师等,熟悉传统企业 IT 、互联网架构,在企业应用架构方面有多年实践经验。</p>\n"}
目录
亚马逊云科技解决方案 基于行业客户应用场景及技术领域的解决方案
联系亚马逊云科技专家
亚马逊云科技解决方案
基于行业客户应用场景及技术领域的解决方案
联系专家
0
目录
关闭