条条大路通罗马 —— 使用 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\nAmazon ElastiCache 是一种 Web 服务,即托管的分布式内存数据库。它提供了一种高性能、可扩展且经济高效的缓存解决方案,可有效消除部署和管理分布式缓存数据库的复杂性。ElastiCache 支持所谓集群模式和非集群模式,具体可参照下图:\n\n![image.png](https://dev-media.amazoncloud.cn/16ab2bb5081241a087f8234ae8cd5cfd_image.png)\n\nAmazon ElastiCache for Redis 的特性如下:\n\n- 自动检测节点故障并从中恢复。\n- 最高支持多达500个分片(启用集群模式)。\n- 与其他 Amazon 服务集成,例如 Amazon EC2、Amazon CloudWatch、Amazon CloudTrail 和 Amazon SNS。\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
目录
关闭