条条大路通罗马 —— 使用 go-redis 连接 Amazon ElastiCache for Redis 集群

0
0
{"value":"### 1. 前言\n[Amazon ElastiCache](https://aws.amazon.com/cn/elasticache/?trk=cndc-detail) 是一种 Web 服务,可让用户在云中轻松设置、管理和扩展分布式内存数据存储或缓存环境。它可以提供高性能、可扩展且具有成本效益的缓存解决方案。同时,它可以帮助消除与部署和管理分布式缓存环境相关的复杂性。\n\nElastiCache for Redis 集群是一个或多个缓存节点的集合,其中所有节点都运行 Redis 缓存引擎软件的实例。ElastiCache for Redis 启用集群模式比之禁用集群模式拥有更好的可扩展性尤其是写入可扩展性,更强的高可用性以及更高的资源上限,因而现在越来越多的客户选择 ElastiCache for Redis 启用集群模式。要使用 ElastiCache for Redis 集群(启用集群模式),您需要使用可以支持redis集群模式的客户端。\n\n当您使用 Go 程序连接 ElastiCache 集群时,目前目前主流的SDK是 Go-Redis项目,本篇 Blog 将为您介绍如何使用 go-redis 连接和使用 ElastiCache for Redis 集群。\n\n除此以外,我们还推出了一系列博客,展示了如何在不同语言中,使用不同的支持 ElastiCache 集群模式的客户端对 ElastiCache 集群进行连接和操作,欢迎大家阅读。\n\n### 2. Go-Redis测试环境搭建\n[Go-Redis](https://redis.uptrace.dev/) 是目前排名最高的 Go 语言版本的 Redis client,支持连接哨兵和集群模式的 Redis,并且提供了高级的Api封装,区别于另一个比较常用的 Go 语言 Redis client 库:Redigo,在服务集成过程中提供更多的功能支持,并且保障 Redis 数据类型安全支持。可以参考 [go-redis 和 redigo 对比](https://redis.uptrace.dev/guide/go-redis-vs-redigo.html) 去了解更多的差异。\n\n**2.1 ElastiCache for Redis 集群搭建**\n在亚马逊云科技上搭建 ElastiCache for Redis 集群,可以参考本篇的系列 Blog,[条条大路通罗马 —— 使用 Redisson 连接 Amazon ElastiCache for Redis 集群](https://aws.amazon.com/cn/blogs/china/connecting-amazon-elasticache-for-redis-cluster-using-redisson/) 的2.1章节,这里就不再赘述。(Redis Cluster,打开Auth + TLS模式)\n\n**2.2 构建Golang SDK 测试代码工程的目录结构**\n```\\n[ec2-user src]\$ tree test-redis-sdk/\\ntest-redis-sdk/\\n|-- cmd\\n| `-- test-redis\\n| `-- redis_test.go\\n|-- go.mod\\n`-- go.sum\\n```\n2.2 使用go-redis 最新的版本分支 v8版本 ,下图总结了初始化 cluster client 常用参数,PoolSize 和 MinIdleConns控制请求的连接池。go-redis 支持TLS 连接,本示例主要演示打开 TLS + Password 的 ElastiCache for Redis 集群如何接入,如下图所示,参数Password已设置,参数TLSConfig控制TLS开关已打开。如果您连接的 ElastiCache for Redis 集群没有开启 TLS 接入,只需要把 Password 参数置为空字符串,去除 TLSConfig 配置即可(TLSConfig 默认关闭)。\n\n![image.png](https://dev-media.amazoncloud.cn/67e049ace0004150a94337ccfc231042_image.png)\n\n**2.3 使用 go-redis SDK 初始化 cluster client,包括读请求的测试逻辑(源代码)**\n```\\n[ec2-user test-redis]\$ cat redis_test.go\\npackage main\\n\\nimport (\\n \\"context\\"\\n \\"crypto/tls\\"\\n \\"fmt\\"\\n goredis \\"github.com/go-redis/redis/v8\\"\\n \\"strconv\\"\\n \\"sync\\"\\n \\"testing\\"\\n \\"time\\"\\n)\\n\\nfunc TestGoRedisCluster(t *testing.T) {\\n var ctx = context.Background()\\n\\n rdb := goredis.NewClusterClient(&goredis.ClusterOptions{\\n\\tAddrs: []string{\\"cluster-configuration-endpoint:6379\\"}, \\n\\tPassword: \\"password\\", //密码\\n //连接池容量及闲置连接数量\\n PoolSize: 10, // 连接池最大socket连接数,默认为4倍CPU数, 4 * runtime.NumCPU\\n MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量;。\\n\\n //超时\\n DialTimeout: 5 * time.Second, //连接建立超时时间,默认5秒。\\n ReadTimeout: 3 * time.Second, //读超时,默认3秒, -1表示取消读超时\\n WriteTimeout: 3 * time.Second, //写超时,默认等于读超时\\n PoolTimeout: 4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒。\\n\\n //闲置连接检查包括IdleTimeout,MaxConnAge\\n IdleCheckFrequency: 60 * time.Second, //闲置连接检查的周期,默认为1分钟,-1表示不做周期性检查,只在客户端获取连接时对闲置连接进行处理。\\n IdleTimeout: 5 * time.Minute, //闲置超时,默认5分钟,-1表示取消闲置超时检查\\n MaxConnAge: 0 * time.Second, //连接存活时长,从创建开始计时,超过指定时长则关闭连接,默认为0,即不关闭存活时长较长的连接\\n\\n //命令执行失败时的重试策略\\n MaxRetries: 10, // 命令执行失败时,最多重试多少次,默认为0即不重试\\n MinRetryBackoff: 8 * time.Millisecond, //每次计算重试间隔时间的下限,默认8毫秒,-1表示取消间隔\\n MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限,默认512毫秒,-1表示取消间隔\\n\\n TLSConfig: &tls.Config{\\n InsecureSkipVerify: true,\\n },\\n\\n // ReadOnly = true,只择 Slave Node\\n // ReadOnly = true 且 RouteByLatency = true 将从 slot 对应的 Master Node 和 Slave Node, 择策略为: 选择PING延迟最低的点\\n // ReadOnly = true 且 RouteRandomly = true 将从 slot 对应的 Master Node 和 Slave Node 选择,选择策略为: 随机选择\\n\\n ReadOnly: true,\\n RouteRandomly: true,\\n RouteByLatency: true,\\n })\\n defer rdb.Close()\\n\\n rdb.Set(ctx, \\"test-0\\", \\"value-0\\", 0)\\n rdb.Set(ctx, \\"test-1\\", \\"value-1\\", 0)\\n rdb.Set(ctx, \\"test-2\\", \\"value-2\\", 0)\\n\\n AllMaxRun := 6\\n\\n wg := sync.WaitGroup{}\\n wg.Add(AllMaxRun)\\n\\n for i := 0; i < AllMaxRun; i ++ {\\n go func(wg *sync.WaitGroup, idx int) {\\n defer wg.Done()\\n\\n for i := 0; i < 50000; i++ {\\n key := \\"test-\\" + strconv.Itoa(i % 3)\\n val, err := rdb.Get(ctx, key).Result()\\n if err == goredis.Nil {\\n fmt.Println(\\"job-\\" + strconv.Itoa(idx) + \\" \\" + key + \\" does not exist\\")\\n } else if err != nil {\\n fmt.Printf(\\"err : %s\\", err.Error())\\n } else {\\n fmt.Printf(\\"%s Job-%d %s = %s-%d \\\\n\\", time.Now().Format(\\"2006-01-02 15:04:05\\"), idx, key, val, i)\\n }\\n time.Sleep(500 * time.Millisecond)\\n }\\n }(&wg, i)\\n }\\n\\n wg.Wait()\\n\\n stats := rdb.PoolStats()\\n fmt.Printf(\\"Hits=%d Misses=%d Timeouts=%d TotalConns=%d IdleConns=%d StaleConns=%d\\\\n\\",\\n stats.Hits, stats.Misses, stats.Timeouts, stats.TotalConns, stats.IdleConns, stats.StaleConns)\\n}\\n```\n### 3. go-redis 读写分离控制测试\nRedis cluster 是有 Master 和 Slave 节点,go-redis 支持对Slave 节点的访问,通过配置 ReadOnly 参数,控制 Master 和 Slave 节点的读写管理。下面我们通过不同的配置去做测试验证。\n\n**3.1 ReadOnly 配置规则说明**\n```\\n// 默认false,即只能在主节点上执行读写操作,如果置为true则允许在从节点上执行只含读操作的命令\\nReadOnly: false, \\n\\n// 默认false。 置为true则ReadOnly自动置为true,表示在处理只读命令时,可以在一个slot对应的主节点和所有从节点中选取Ping()的响应时长最短的一个节点来读数据\\nRouteByLatency: false,\\n\\n// 默认false。置为true则ReadOnly自动置为true,表示在处理只读命令时,可以在一个slot对应的主节点和所有从节点中随机挑选一个节点来读数据\\nRouteRandomly: false,\\n```\n#### 3.2 关闭ReadOnly配置测试**\n修改测试代码,关闭 ReadOnly 配置(三个 ReadOnly 参数配置都置为 false ),观察监控仍然是维持10个 conn,但是按照配置说明,服务不会从读节点读取数据\n```\\n[ec2-user ~]\$ netstat -a | grep 6379 | grep ESTABLISHED | awk '{print \$5}' | sort | uniq -c | sort -rn\\n 10 ip-172-31-18-215.a:6379\\n 10 ip-172-31-46-118.a:6379\\n 10 ip-172-31-34-217.a:6379\\n 10 ip-172-31-31-193.a:6379\\n 10 ip-172-31-15-157.a:6379\\n 10 ip-172-31-10-163.a:6379\\n```\n观察对应的连接数,连接数仍然保持在10个\n\n![image.png](https://dev-media.amazoncloud.cn/b52d6345e2ee473fa4ad945792fb8864_image.png)\n\n调整测试代码,增大查询压力,观察GetTypeCmds监控指标,可以看到只有master节点上是所有的读请求,判断所有的读压力都是分布在所有的 master 节点上。\n\n![image.png](https://dev-media.amazoncloud.cn/dfdee142168a4a6b85ddf5bb9d9a0d72_image.png)\n**3.3 打开ReadOnly配置测试**\n修改测试代码,打开 ReadOnly 配置(或者 RouteByLatency 和 RouteRandomly 任意一个),观察监控仍然是维持10个 conn,但是按照配置说明,服务是会从读节点读取数据,可以判断 go-redis 默认和所有的 Master/Slave 节点都有长连接。\n```\\n[ec2-user ~]\$ netstat -a | grep 6379 | grep ESTABLISHED | awk '{print \$5}' | sort | uniq -c | sort -rn\\n 10 ip-172-31-18-215.a:6379\\n 10 ip-172-31-46-118.a:6379\\n 10 ip-172-31-34-217.a:6379\\n 10 ip-172-31-31-193.a:6379\\n 10 ip-172-31-15-157.a:6379\\n 10 ip-172-31-10-163.a:6379\\n```\n参考 CloudWatch Metrics观察对应的连接数,连接数仍然保持,没有变化,和客户端连接数统计一致。\n\n![image.png](https://dev-media.amazoncloud.cn/d074254475ea427e88ba2104062cda62_image.png)\n\n如果ReadOnly打开,在适当压力情况下,观察GetTypeCmds监控,可以看到Master 和 Slave 节点都均匀分布读请求,可以判断读的压力是均匀分配到Master + Slave 节点上\n\n![image.png](https://dev-media.amazoncloud.cn/1c279ba9b0b94fa185319a1107132aba_image.png)\n### 4. 多值查询测试\n**4.1 go-redis 可以支持在 Redis 非集群和集群模式下 Pipeline 命令正确执行,以下给出 Pipeline 的代码示例。**\n```\\nfunc TestRedisClusterPipline(t *testing.T) {\\n var ctx = context.Background()\\n\\n rdb := goredis.NewClusterClient(&goredis.ClusterOptions{\\n\\tAddrs: []string{\\"cluster-configuration-endpoint:6379\\"}, \\n\\tPassword: \\"password\\", //密码\\n\\n //连接池容量及闲置连接数量\\n PoolSize: 10, // 连接池最大socket连接数,默认为4倍CPU数, 4 * runtime.NumCPU\\n MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量;。\\n\\n //超时\\n DialTimeout: 5 * time.Second, //连接建立超时时间,默认5秒。\\n ReadTimeout: 3 * time.Second, //读超时,默认3秒, -1表示取消读超时\\n WriteTimeout: 3 * time.Second, //写超时,默认等于读超时\\n PoolTimeout: 4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒。\\n\\n //闲置连接检查包括IdleTimeout,MaxConnAge\\n IdleCheckFrequency: 60 * time.Second, //闲置连接检查的周期,默认为1分钟,-1表示不做周期性检查,只在客户端获取连接时对闲置连接进行处理。\\n IdleTimeout: 5 * time.Minute, //闲置超时,默认5分钟,-1表示取消闲置超时检查\\n MaxConnAge: 0 * time.Second, //连接存活时长,从创建开始计时,超过指定时长则关闭连接,默认为0,即不关闭存活时长较长的连接\\n\\n //命令执行失败时的重试策略\\n MaxRetries: 10, // 命令执行失败时,最多重试多少次,默认为0即不重试\\n MinRetryBackoff: 8 * time.Millisecond, //每次计算重试间隔时间的下限,默认8毫秒,-1表示取消间隔\\n MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限,默认512毫秒,-1表示取消间隔\\n\\nTLSConfig: &tls.Config{\\n InsecureSkipVerify: true,\\n },\\n\\n ReadOnly: true,\\n })\\n\\n rdb.Set(ctx, \\"test-0\\", \\"value-0\\", 0)\\n rdb.Set(ctx, \\"test-1\\", \\"value-1\\", 0)\\n rdb.Set(ctx, \\"test-2\\", \\"value-2\\", 0)\\n\\n pipe := rdb.Pipeline()\\n pipe.Get(ctx, \\"test-0\\").Result()\\n pipe.Get(ctx, \\"test-1\\").Result()\\n pipe.Get(ctx, \\"test-2\\").Result()\\n cmders, err := pipe.Exec(ctx)\\n if err != nil {\\n fmt.Println(\\"err\\", err)\\n }\\n\\n for idx, cmder := range cmders {\\n cmd := cmder.(*goredis.StringCmd)\\n strVal, err := cmd.Result()\\n if err != nil {\\n fmt.Println(\\"err\\", err)\\n }\\n fmt.Println(\\"strVal_\\" + strconv.Itoa(idx) + \\":\\", strVal)\\n }\\n}\\n```\n**4.2 go-redis 不支持 Redis 集群模式下,对不在一个 Shard 的多个Key执行MGet / MSet 操作,如果有类似的使用场景,建议使用 Redis-Go-Cluster开源项目,源码链接:Redis-Go-Cluster,以下为相应的代码示例。**\n```\\nfunc TestRedisClusterMGetMSet(t *testing.T) {\\n cluster, err := rediscluster.NewCluster(\\n &rediscluster.Options{\\n StartNodes: []string{\\"cluster-configuration-endpoint:6379\\"},\\n ConnTimeout: 100 * time.Millisecond,\\n ReadTimeout: 100 * time.Millisecond,\\n WriteTimeout: 100 * time.Millisecond,\\n KeepAlive: 16,\\n AliveTime: 60 * time.Second,\\n })\\n\\n if err != nil {\\n fmt.Println(err.Error())\\n return\\n }\\n\\n _, err = cluster.Do(\\"MSET\\", \\"test-0\\", \\"value-0\\", \\"test-1\\", \\"value-1\\", \\"test-2\\", \\"value-2\\")\\n if err != nil {\\n fmt.Println(\\"MSET\\" + err.Error())\\n return\\n }\\n\\n replys, err := rediscluster.Values(cluster.Do(\\"MGET\\", \\"test-0\\", \\"test-1\\", \\"test-2\\"))\\n if err != nil {\\n fmt.Println(\\"MGET\\" + err.Error())\\n return\\n }\\n\\n for i := 0; i < 3; i++ {\\n fmt.Println(rediscluster.String(replys[i], nil))\\n }\\n}\\n```\n### 5. Failover测试\n**5.1 执行 go test 做测试,持续的做读请求**\n```\\n[ec2-user test-redis]\$ go test -v redis_test.go -run TestGoRedisCluster -timeout 100m\\n=== RUN TestGoRedisCluster\\n2022-04-18 12:27:37 Job-4 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-1 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-0 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-5 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-2 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-3 test-0 = value-0-0\\n```\n在 Idle 和 PoolSize 相等的配置下,可以观察到 Redis 客户端服务和 Master 和 Slave 都是建立 10 个连接\n```\\n[ec2-user ~]\$ netstat -a | grep 6379 | grep ESTABLISHED | awk '{print \$5}' | sort | uniq -c | sort -rn\\n 10 ip-172-31-18-215.a:6379\\n 10 ip-172-31-46-118.a:6379\\n 10 ip-172-31-34-217.a:6379\\n 10 ip-172-31-31-193.a:6379\\n 10 ip-172-31-15-157.a:6379\\n 10 ip-172-31-10-163.a:6379\\n```\n在 go test 开始之前,cluster 的 avg 连接数\n\n![image.png](https://dev-media.amazoncloud.cn/4f7e6eb2913a4c3e98d2a6009b7b0b37_image.png)\n\n在 go test 执行开始,3个 master 和 3 个 slave 都新增了 10 个 conn\n\n![image.png](https://dev-media.amazoncloud.cn/3575c4cd3ce24da59661fa7a6dc56a25_image.png)\n**5.2 测试对 redis cluster的第一个 shard 做主动的 failover**\n::: hljs-center\n\n![image.png](https://dev-media.amazoncloud.cn/bcbc67bf27444d77aa908b635ce684d2_image.png)\n\n:::\n**5.3 在命令行输出观察到压测代码发生中断**\n![image.png](https://dev-media.amazoncloud.cn/b4abf9b513f1472e94303380452a7358_image.png)\n#### 5.4 在 ElastiCache Dashboard Events 观察 Failover 过程\n![image.png](https://dev-media.amazoncloud.cn/5c8a8c3d65f2416f85a896932899aa89_image.png)\n\n可以观察到 8:54:13 PM ~ 8:54:36 PM UTC+8 经过 23s完成 Failover,测试代码的时间戳是 12:54:16 ~ 12:54:26 UTC,实际服务中断只有 10s 时间\n\n在 12:53 UTC 时刻,连接正常\n\n![image.png](https://dev-media.amazoncloud.cn/aa45078d6f3e48348fb1fa2136f5109e_image.png)\n\n在 12:54 UTC 时刻,故障节点断开连接\n\n![image.png](https://dev-media.amazoncloud.cn/df9d558da08a40d1adc73a0107c40d6e_image.png)\n\n在 13:00 UTC 时刻,故障节点开始恢复连接,但是所有服务请求未受到影响\n\n![image.png](https://dev-media.amazoncloud.cn/037a3229722e410c888cef2d6629172d_image.png)\n\n在 13:02 UTC 时刻,所有连接完全恢复\n\n![image.png](https://dev-media.amazoncloud.cn/95253a6fdd2441e2a8f966cd84947104_image.png)\n\n**5.5 在 ReadOnly = False 时,做Failover 时**\n\n```\\n2022-04-18 15:04:27 Job-5 test-1 = value-1-601\\n2022-04-18 15:04:27 Job-0 test-1 = value-1-601\\n2022-04-18 15:04:27 Job-1 test-1 = value-1-601\\n\\nerr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:38 Job-3 test-0 = value-0-603\\n2022-04-18 15:04:38 Job-5 test-0 = value-0-603\\n2022-04-18 15:04:39 Job-0 test-0 = value-0-603\\nerr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:39 Job-2 test-0 = value-0-603\\n2022-04-18 15:04:39 Job-4 test-0 = value-0-603\\n2022-04-18 15:04:39 Job-3 test-1 = value-1-604\\n2022-04-18 15:04:39 Job-5 test-1 = value-1-604\\n2022-04-18 15:04:40 Job-0 test-1 = value-1-604\\n2022-04-18 15:04:40 Job-1 test-0 = value-0-603\\n2022-04-18 15:04:40 Job-2 test-1 = value-1-604\\n2022-04-18 15:04:40 Job-4 test-1 = value-1-604\\n2022-04-18 15:04:41 Job-1 test-1 = value-1-604\\nerr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:50 Job-3 test-0 = value-0-606\\nerr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:50 Job-2 test-0 = value-0-606\\nerr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:51 Job-3 test-1 = value-1-607\\n2022-04-18 15:04:51 Job-5 test-0 = value-0-606\\n2022-04-18 15:04:51 Job-2 test-1 = value-1-607\\n2022-04-18 15:04:52 Job-1 test-0 = value-0-606\\n2022-04-18 15:04:52 Job-4 test-0 = value-0-606\\n2022-04-18 15:04:52 Job-0 test-0 = value-0-606\\n```\nFailover 时,中断时间\n\n![image.png](https://dev-media.amazoncloud.cn/0dce96076fe74399add6963317392e43_image.png)\n\n可以观察到 11:04:28 PM ~ 11:05:03 PM UTC+8 经过 35s完成 Failover,测试代码的时间戳是 15:04:27 ~ 15:04:51 UTC,实际服务中断为 24s 时间\n\n![image.png](https://dev-media.amazoncloud.cn/91f70b31b4404c098d99384af3068903_image.png)\n\n在 15:03 UTC 连接正常\n\n![image.png](https://dev-media.amazoncloud.cn/68fe8b89dbdf4d9caf89753863fc1b71_image.png)\n\n在 15:04 UTC Failover 开始断开一个节点\n\n![image.png](https://dev-media.amazoncloud.cn/3334dd889f2f4c22801c7a3afe7bff6c_image.png)\n\n在 15:11 UTC 开始恢复一个节点\n\n![image.png](https://dev-media.amazoncloud.cn/284f38e8383e4c8fa9d6274bf0334a94_image.png)\n\n在 15:13 UTC 完全恢复\n\n![image.png](https://dev-media.amazoncloud.cn/1fb52caf8cfa4775892a8a8c7b727c0b_image.png)\n\n### 6. 小结\n本博客为大家展示了如何在 Golang 程序中通过 go-redis 连接和操作 ElastiCache 集群,从这个简单的 Demo 中我们可以看到 go-redis 能很好地支持 ElastiCache 集群开启 TLS 及 Auth 的功能,并自动完成读写分离,负载均衡,Failover 等工作。在第5小结的Failover的测试中观察到打开 ReadOnly 可以加速故障恢复,建议实际使用基于ReadOnly 更好的提升服务读写 Redis Cluster 的性能。通过 go-redis,我们可以便捷,高效地使用 ElastiCache 集群。\n\n除了本博客以外,我们还推出了一系列博客,展示了如何在不同语言中使用不同的客户端对 ElastiCache 集群进行连接和操作,欢迎大家阅读。\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[条条大路通罗马 —— 使用redis-py访问Amazon ElastiCache for redis集群](https://aws.amazon.com/cn/blogs/china/use-redis-py-to-access-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![image.png](https://dev-media.amazoncloud.cn/f03a2a4b7a8e4a05ae3087b763af18e3_image.png)\n\n**唐健**\nAmazon 解决方案架构师,负责基于 Amazon 的云计算方案的架构设计,同时致力于 Amazon 云服务在移动应用与互联网行业的应用和推广。拥有多年移动互联网研发及技术团队管理经验,丰富的互联网应用架构项目经历。","render":"<h3><a id=\\"1__0\\"></a>1. 前言</h3>\\n<p>Amazon ElastiCache 是一种 Web 服务,可让用户在云中轻松设置、管理和扩展分布式内存数据存储或缓存环境。它可以提供高性能、可扩展且具有成本效益的缓存解决方案。同时,它可以帮助消除与部署和管理分布式缓存环境相关的复杂性。</p>\n<p>ElastiCache for Redis 集群是一个或多个缓存节点的集合,其中所有节点都运行 Redis 缓存引擎软件的实例。ElastiCache for Redis 启用集群模式比之禁用集群模式拥有更好的可扩展性尤其是写入可扩展性,更强的高可用性以及更高的资源上限,因而现在越来越多的客户选择 ElastiCache for Redis 启用集群模式。要使用 ElastiCache for Redis 集群(启用集群模式),您需要使用可以支持redis集群模式的客户端。</p>\n<p>当您使用 Go 程序连接 ElastiCache 集群时,目前目前主流的SDK是 Go-Redis项目,本篇 Blog 将为您介绍如何使用 go-redis 连接和使用 ElastiCache for Redis 集群。</p>\n<p>除此以外,我们还推出了一系列博客,展示了如何在不同语言中,使用不同的支持 ElastiCache 集群模式的客户端对 ElastiCache 集群进行连接和操作,欢迎大家阅读。</p>\n<h3><a id=\\"2_GoRedis_9\\"></a>2. Go-Redis测试环境搭建</h3>\\n<p><a href=\\"https://redis.uptrace.dev/\\" target=\\"_blank\\">Go-Redis</a> 是目前排名最高的 Go 语言版本的 Redis client,支持连接哨兵和集群模式的 Redis,并且提供了高级的Api封装,区别于另一个比较常用的 Go 语言 Redis client 库:Redigo,在服务集成过程中提供更多的功能支持,并且保障 Redis 数据类型安全支持。可以参考 <a href=\\"https://redis.uptrace.dev/guide/go-redis-vs-redigo.html\\" target=\\"_blank\\">go-redis 和 redigo 对比</a> 去了解更多的差异。</p>\\n<p><strong>2.1 ElastiCache for Redis 集群搭建</strong><br />\\n在亚马逊云科技上搭建 ElastiCache for Redis 集群,可以参考本篇的系列 Blog,<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> 的2.1章节,这里就不再赘述。(Redis Cluster,打开Auth + TLS模式)</p>\\n<p><strong>2.2 构建Golang SDK 测试代码工程的目录结构</strong></p>\\n<pre><code class=\\"lang-\\">[ec2-user src]\$ tree test-redis-sdk/\\ntest-redis-sdk/\\n|-- cmd\\n| `-- test-redis\\n| `-- redis_test.go\\n|-- go.mod\\n`-- go.sum\\n</code></pre>\\n<p>2.2 使用go-redis 最新的版本分支 v8版本 ,下图总结了初始化 cluster client 常用参数,PoolSize 和 MinIdleConns控制请求的连接池。go-redis 支持TLS 连接,本示例主要演示打开 TLS + Password 的 ElastiCache for Redis 集群如何接入,如下图所示,参数Password已设置,参数TLSConfig控制TLS开关已打开。如果您连接的 ElastiCache for Redis 集群没有开启 TLS 接入,只需要把 Password 参数置为空字符串,去除 TLSConfig 配置即可(TLSConfig 默认关闭)。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/67e049ace0004150a94337ccfc231042_image.png\\" alt=\\"image.png\\" /></p>\n<p><strong>2.3 使用 go-redis SDK 初始化 cluster client,包括读请求的测试逻辑(源代码)</strong></p>\\n<pre><code class=\\"lang-\\">[ec2-user test-redis]\$ cat redis_test.go\\npackage main\\n\\nimport (\\n &quot;context&quot;\\n &quot;crypto/tls&quot;\\n &quot;fmt&quot;\\n goredis &quot;github.com/go-redis/redis/v8&quot;\\n &quot;strconv&quot;\\n &quot;sync&quot;\\n &quot;testing&quot;\\n &quot;time&quot;\\n)\\n\\nfunc TestGoRedisCluster(t *testing.T) {\\n var ctx = context.Background()\\n\\n rdb := goredis.NewClusterClient(&amp;goredis.ClusterOptions{\\n\\tAddrs: []string{&quot;cluster-configuration-endpoint:6379&quot;}, \\n\\tPassword: &quot;password&quot;, //密码\\n //连接池容量及闲置连接数量\\n PoolSize: 10, // 连接池最大socket连接数,默认为4倍CPU数, 4 * runtime.NumCPU\\n MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量;。\\n\\n //超时\\n DialTimeout: 5 * time.Second, //连接建立超时时间,默认5秒。\\n ReadTimeout: 3 * time.Second, //读超时,默认3秒, -1表示取消读超时\\n WriteTimeout: 3 * time.Second, //写超时,默认等于读超时\\n PoolTimeout: 4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒。\\n\\n //闲置连接检查包括IdleTimeout,MaxConnAge\\n IdleCheckFrequency: 60 * time.Second, //闲置连接检查的周期,默认为1分钟,-1表示不做周期性检查,只在客户端获取连接时对闲置连接进行处理。\\n IdleTimeout: 5 * time.Minute, //闲置超时,默认5分钟,-1表示取消闲置超时检查\\n MaxConnAge: 0 * time.Second, //连接存活时长,从创建开始计时,超过指定时长则关闭连接,默认为0,即不关闭存活时长较长的连接\\n\\n //命令执行失败时的重试策略\\n MaxRetries: 10, // 命令执行失败时,最多重试多少次,默认为0即不重试\\n MinRetryBackoff: 8 * time.Millisecond, //每次计算重试间隔时间的下限,默认8毫秒,-1表示取消间隔\\n MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限,默认512毫秒,-1表示取消间隔\\n\\n TLSConfig: &amp;tls.Config{\\n InsecureSkipVerify: true,\\n },\\n\\n // ReadOnly = true,只择 Slave Node\\n // ReadOnly = true 且 RouteByLatency = true 将从 slot 对应的 Master Node 和 Slave Node, 择策略为: 选择PING延迟最低的点\\n // ReadOnly = true 且 RouteRandomly = true 将从 slot 对应的 Master Node 和 Slave Node 选择,选择策略为: 随机选择\\n\\n ReadOnly: true,\\n RouteRandomly: true,\\n RouteByLatency: true,\\n })\\n defer rdb.Close()\\n\\n rdb.Set(ctx, &quot;test-0&quot;, &quot;value-0&quot;, 0)\\n rdb.Set(ctx, &quot;test-1&quot;, &quot;value-1&quot;, 0)\\n rdb.Set(ctx, &quot;test-2&quot;, &quot;value-2&quot;, 0)\\n\\n AllMaxRun := 6\\n\\n wg := sync.WaitGroup{}\\n wg.Add(AllMaxRun)\\n\\n for i := 0; i &lt; AllMaxRun; i ++ {\\n go func(wg *sync.WaitGroup, idx int) {\\n defer wg.Done()\\n\\n for i := 0; i &lt; 50000; i++ {\\n key := &quot;test-&quot; + strconv.Itoa(i % 3)\\n val, err := rdb.Get(ctx, key).Result()\\n if err == goredis.Nil {\\n fmt.Println(&quot;job-&quot; + strconv.Itoa(idx) + &quot; &quot; + key + &quot; does not exist&quot;)\\n } else if err != nil {\\n fmt.Printf(&quot;err : %s&quot;, err.Error())\\n } else {\\n fmt.Printf(&quot;%s Job-%d %s = %s-%d \\\\n&quot;, time.Now().Format(&quot;2006-01-02 15:04:05&quot;), idx, key, val, i)\\n }\\n time.Sleep(500 * time.Millisecond)\\n }\\n }(&amp;wg, i)\\n }\\n\\n wg.Wait()\\n\\n stats := rdb.PoolStats()\\n fmt.Printf(&quot;Hits=%d Misses=%d Timeouts=%d TotalConns=%d IdleConns=%d StaleConns=%d\\\\n&quot;,\\n stats.Hits, stats.Misses, stats.Timeouts, stats.TotalConns, stats.IdleConns, stats.StaleConns)\\n}\\n</code></pre>\\n<h3><a id=\\"3_goredis__120\\"></a>3. go-redis 读写分离控制测试</h3>\\n<p>Redis cluster 是有 Master 和 Slave 节点,go-redis 支持对Slave 节点的访问,通过配置 ReadOnly 参数,控制 Master 和 Slave 节点的读写管理。下面我们通过不同的配置去做测试验证。</p>\n<p><strong>3.1 ReadOnly 配置规则说明</strong></p>\\n<pre><code class=\\"lang-\\">// 默认false,即只能在主节点上执行读写操作,如果置为true则允许在从节点上执行只含读操作的命令\\nReadOnly: false, \\n\\n// 默认false。 置为true则ReadOnly自动置为true,表示在处理只读命令时,可以在一个slot对应的主节点和所有从节点中选取Ping()的响应时长最短的一个节点来读数据\\nRouteByLatency: false,\\n\\n// 默认false。置为true则ReadOnly自动置为true,表示在处理只读命令时,可以在一个slot对应的主节点和所有从节点中随机挑选一个节点来读数据\\nRouteRandomly: false,\\n</code></pre>\\n<h4><a id=\\"32_ReadOnly_134\\"></a>3.2 关闭ReadOnly配置测试**</h4>\\n<p>修改测试代码,关闭 ReadOnly 配置(三个 ReadOnly 参数配置都置为 false ),观察监控仍然是维持10个 conn,但是按照配置说明,服务不会从读节点读取数据</p>\n<pre><code class=\\"lang-\\">[ec2-user ~]\$ netstat -a | grep 6379 | grep ESTABLISHED | awk '{print \$5}' | sort | uniq -c | sort -rn\\n 10 ip-172-31-18-215.a:6379\\n 10 ip-172-31-46-118.a:6379\\n 10 ip-172-31-34-217.a:6379\\n 10 ip-172-31-31-193.a:6379\\n 10 ip-172-31-15-157.a:6379\\n 10 ip-172-31-10-163.a:6379\\n</code></pre>\\n<p>观察对应的连接数,连接数仍然保持在10个</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/b52d6345e2ee473fa4ad945792fb8864_image.png\\" alt=\\"image.png\\" /></p>\n<p>调整测试代码,增大查询压力,观察GetTypeCmds监控指标,可以看到只有master节点上是所有的读请求,判断所有的读压力都是分布在所有的 master 节点上。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/dfdee142168a4a6b85ddf5bb9d9a0d72_image.png\\" alt=\\"image.png\\" /><br />\\n<strong>3.3 打开ReadOnly配置测试</strong><br />\\n修改测试代码,打开 ReadOnly 配置(或者 RouteByLatency 和 RouteRandomly 任意一个),观察监控仍然是维持10个 conn,但是按照配置说明,服务是会从读节点读取数据,可以判断 go-redis 默认和所有的 Master/Slave 节点都有长连接。</p>\n<pre><code class=\\"lang-\\">[ec2-user ~]\$ netstat -a | grep 6379 | grep ESTABLISHED | awk '{print \$5}' | sort | uniq -c | sort -rn\\n 10 ip-172-31-18-215.a:6379\\n 10 ip-172-31-46-118.a:6379\\n 10 ip-172-31-34-217.a:6379\\n 10 ip-172-31-31-193.a:6379\\n 10 ip-172-31-15-157.a:6379\\n 10 ip-172-31-10-163.a:6379\\n</code></pre>\\n<p>参考 CloudWatch Metrics观察对应的连接数,连接数仍然保持,没有变化,和客户端连接数统计一致。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/d074254475ea427e88ba2104062cda62_image.png\\" alt=\\"image.png\\" /></p>\n<p>如果ReadOnly打开,在适当压力情况下,观察GetTypeCmds监控,可以看到Master 和 Slave 节点都均匀分布读请求,可以判断读的压力是均匀分配到Master + Slave 节点上</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/1c279ba9b0b94fa185319a1107132aba_image.png\\" alt=\\"image.png\\" /></p>\n<h3><a id=\\"4__170\\"></a>4. 多值查询测试</h3>\\n<p><strong>4.1 go-redis 可以支持在 Redis 非集群和集群模式下 Pipeline 命令正确执行,以下给出 Pipeline 的代码示例。</strong></p>\\n<pre><code class=\\"lang-\\">func TestRedisClusterPipline(t *testing.T) {\\n var ctx = context.Background()\\n\\n rdb := goredis.NewClusterClient(&amp;goredis.ClusterOptions{\\n\\tAddrs: []string{&quot;cluster-configuration-endpoint:6379&quot;}, \\n\\tPassword: &quot;password&quot;, //密码\\n\\n //连接池容量及闲置连接数量\\n PoolSize: 10, // 连接池最大socket连接数,默认为4倍CPU数, 4 * runtime.NumCPU\\n MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量;。\\n\\n //超时\\n DialTimeout: 5 * time.Second, //连接建立超时时间,默认5秒。\\n ReadTimeout: 3 * time.Second, //读超时,默认3秒, -1表示取消读超时\\n WriteTimeout: 3 * time.Second, //写超时,默认等于读超时\\n PoolTimeout: 4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒。\\n\\n //闲置连接检查包括IdleTimeout,MaxConnAge\\n IdleCheckFrequency: 60 * time.Second, //闲置连接检查的周期,默认为1分钟,-1表示不做周期性检查,只在客户端获取连接时对闲置连接进行处理。\\n IdleTimeout: 5 * time.Minute, //闲置超时,默认5分钟,-1表示取消闲置超时检查\\n MaxConnAge: 0 * time.Second, //连接存活时长,从创建开始计时,超过指定时长则关闭连接,默认为0,即不关闭存活时长较长的连接\\n\\n //命令执行失败时的重试策略\\n MaxRetries: 10, // 命令执行失败时,最多重试多少次,默认为0即不重试\\n MinRetryBackoff: 8 * time.Millisecond, //每次计算重试间隔时间的下限,默认8毫秒,-1表示取消间隔\\n MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限,默认512毫秒,-1表示取消间隔\\n\\nTLSConfig: &amp;tls.Config{\\n InsecureSkipVerify: true,\\n },\\n\\n ReadOnly: true,\\n })\\n\\n rdb.Set(ctx, &quot;test-0&quot;, &quot;value-0&quot;, 0)\\n rdb.Set(ctx, &quot;test-1&quot;, &quot;value-1&quot;, 0)\\n rdb.Set(ctx, &quot;test-2&quot;, &quot;value-2&quot;, 0)\\n\\n pipe := rdb.Pipeline()\\n pipe.Get(ctx, &quot;test-0&quot;).Result()\\n pipe.Get(ctx, &quot;test-1&quot;).Result()\\n pipe.Get(ctx, &quot;test-2&quot;).Result()\\n cmders, err := pipe.Exec(ctx)\\n if err != nil {\\n fmt.Println(&quot;err&quot;, err)\\n }\\n\\n for idx, cmder := range cmders {\\n cmd := cmder.(*goredis.StringCmd)\\n strVal, err := cmd.Result()\\n if err != nil {\\n fmt.Println(&quot;err&quot;, err)\\n }\\n fmt.Println(&quot;strVal_&quot; + strconv.Itoa(idx) + &quot;:&quot;, strVal)\\n }\\n}\\n</code></pre>\\n<p><strong>4.2 go-redis 不支持 Redis 集群模式下,对不在一个 Shard 的多个Key执行MGet / MSet 操作,如果有类似的使用场景,建议使用 Redis-Go-Cluster开源项目,源码链接:Redis-Go-Cluster,以下为相应的代码示例。</strong></p>\\n<pre><code class=\\"lang-\\">func TestRedisClusterMGetMSet(t *testing.T) {\\n cluster, err := rediscluster.NewCluster(\\n &amp;rediscluster.Options{\\n StartNodes: []string{&quot;cluster-configuration-endpoint:6379&quot;},\\n ConnTimeout: 100 * time.Millisecond,\\n ReadTimeout: 100 * time.Millisecond,\\n WriteTimeout: 100 * time.Millisecond,\\n KeepAlive: 16,\\n AliveTime: 60 * time.Second,\\n })\\n\\n if err != nil {\\n fmt.Println(err.Error())\\n return\\n }\\n\\n _, err = cluster.Do(&quot;MSET&quot;, &quot;test-0&quot;, &quot;value-0&quot;, &quot;test-1&quot;, &quot;value-1&quot;, &quot;test-2&quot;, &quot;value-2&quot;)\\n if err != nil {\\n fmt.Println(&quot;MSET&quot; + err.Error())\\n return\\n }\\n\\n replys, err := rediscluster.Values(cluster.Do(&quot;MGET&quot;, &quot;test-0&quot;, &quot;test-1&quot;, &quot;test-2&quot;))\\n if err != nil {\\n fmt.Println(&quot;MGET&quot; + err.Error())\\n return\\n }\\n\\n for i := 0; i &lt; 3; i++ {\\n fmt.Println(rediscluster.String(replys[i], nil))\\n }\\n}\\n</code></pre>\\n<h3><a id=\\"5_Failover_265\\"></a>5. Failover测试</h3>\\n<p><strong>5.1 执行 go test 做测试,持续的做读请求</strong></p>\\n<pre><code class=\\"lang-\\">[ec2-user test-redis]\$ go test -v redis_test.go -run TestGoRedisCluster -timeout 100m\\n=== RUN TestGoRedisCluster\\n2022-04-18 12:27:37 Job-4 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-1 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-0 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-5 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-2 test-0 = value-0-0\\n2022-04-18 12:27:37 Job-3 test-0 = value-0-0\\n</code></pre>\\n<p>在 Idle 和 PoolSize 相等的配置下,可以观察到 Redis 客户端服务和 Master 和 Slave 都是建立 10 个连接</p>\n<pre><code class=\\"lang-\\">[ec2-user ~]\$ netstat -a | grep 6379 | grep ESTABLISHED | awk '{print \$5}' | sort | uniq -c | sort -rn\\n 10 ip-172-31-18-215.a:6379\\n 10 ip-172-31-46-118.a:6379\\n 10 ip-172-31-34-217.a:6379\\n 10 ip-172-31-31-193.a:6379\\n 10 ip-172-31-15-157.a:6379\\n 10 ip-172-31-10-163.a:6379\\n</code></pre>\\n<p>在 go test 开始之前,cluster 的 avg 连接数</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/4f7e6eb2913a4c3e98d2a6009b7b0b37_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 go test 执行开始,3个 master 和 3 个 slave 都新增了 10 个 conn</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/3575c4cd3ce24da59661fa7a6dc56a25_image.png\\" alt=\\"image.png\\" /><br />\\n<strong>5.2 测试对 redis cluster的第一个 shard 做主动的 failover</strong></p>\\n<div class=\\"hljs-center\\">\\n<p><img src=\\"https://dev-media.amazoncloud.cn/bcbc67bf27444d77aa908b635ce684d2_image.png\\" alt=\\"image.png\\" /></p>\n</div>\\n<p><strong>5.3 在命令行输出观察到压测代码发生中断</strong><br />\\n<img src=\\"https://dev-media.amazoncloud.cn/b4abf9b513f1472e94303380452a7358_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"54__ElastiCache_Dashboard_Events__Failover__302\\"></a>5.4 在 ElastiCache Dashboard Events 观察 Failover 过程</h4>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/5c8a8c3d65f2416f85a896932899aa89_image.png\\" alt=\\"image.png\\" /></p>\n<p>可以观察到 8:54:13 PM ~ 8:54:36 PM UTC+8 经过 23s完成 Failover,测试代码的时间戳是 12:54:16 ~ 12:54:26 UTC,实际服务中断只有 10s 时间</p>\n<p>在 12:53 UTC 时刻,连接正常</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/aa45078d6f3e48348fb1fa2136f5109e_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 12:54 UTC 时刻,故障节点断开连接</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/df9d558da08a40d1adc73a0107c40d6e_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 13:00 UTC 时刻,故障节点开始恢复连接,但是所有服务请求未受到影响</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/037a3229722e410c888cef2d6629172d_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 13:02 UTC 时刻,所有连接完全恢复</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/95253a6fdd2441e2a8f966cd84947104_image.png\\" alt=\\"image.png\\" /></p>\n<p><strong>5.5 在 ReadOnly = False 时,做Failover 时</strong></p>\\n<pre><code class=\\"lang-\\">2022-04-18 15:04:27 Job-5 test-1 = value-1-601\\n2022-04-18 15:04:27 Job-0 test-1 = value-1-601\\n2022-04-18 15:04:27 Job-1 test-1 = value-1-601\\n\\nerr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:38 Job-3 test-0 = value-0-603\\n2022-04-18 15:04:38 Job-5 test-0 = value-0-603\\n2022-04-18 15:04:39 Job-0 test-0 = value-0-603\\nerr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:39 Job-2 test-0 = value-0-603\\n2022-04-18 15:04:39 Job-4 test-0 = value-0-603\\n2022-04-18 15:04:39 Job-3 test-1 = value-1-604\\n2022-04-18 15:04:39 Job-5 test-1 = value-1-604\\n2022-04-18 15:04:40 Job-0 test-1 = value-1-604\\n2022-04-18 15:04:40 Job-1 test-0 = value-0-603\\n2022-04-18 15:04:40 Job-2 test-1 = value-1-604\\n2022-04-18 15:04:40 Job-4 test-1 = value-1-604\\n2022-04-18 15:04:41 Job-1 test-1 = value-1-604\\nerr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:50 Job-3 test-0 = value-0-606\\nerr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:50 Job-2 test-0 = value-0-606\\nerr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeouterr : dial tcp 172.31.31.193:6379: i/o timeout2022-04-18 15:04:51 Job-3 test-1 = value-1-607\\n2022-04-18 15:04:51 Job-5 test-0 = value-0-606\\n2022-04-18 15:04:51 Job-2 test-1 = value-1-607\\n2022-04-18 15:04:52 Job-1 test-0 = value-0-606\\n2022-04-18 15:04:52 Job-4 test-0 = value-0-606\\n2022-04-18 15:04:52 Job-0 test-0 = value-0-606\\n</code></pre>\\n<p>Failover 时,中断时间</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/0dce96076fe74399add6963317392e43_image.png\\" alt=\\"image.png\\" /></p>\n<p>可以观察到 11:04:28 PM ~ 11:05:03 PM UTC+8 经过 35s完成 Failover,测试代码的时间戳是 15:04:27 ~ 15:04:51 UTC,实际服务中断为 24s 时间</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/91f70b31b4404c098d99384af3068903_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 15:03 UTC 连接正常</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/68fe8b89dbdf4d9caf89753863fc1b71_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 15:04 UTC Failover 开始断开一个节点</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/3334dd889f2f4c22801c7a3afe7bff6c_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 15:11 UTC 开始恢复一个节点</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/284f38e8383e4c8fa9d6274bf0334a94_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 15:13 UTC 完全恢复</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/1fb52caf8cfa4775892a8a8c7b727c0b_image.png\\" alt=\\"image.png\\" /></p>\n<h3><a id=\\"6__375\\"></a>6. 小结</h3>\\n<p>本博客为大家展示了如何在 Golang 程序中通过 go-redis 连接和操作 ElastiCache 集群,从这个简单的 Demo 中我们可以看到 go-redis 能很好地支持 ElastiCache 集群开启 TLS 及 Auth 的功能,并自动完成读写分离,负载均衡,Failover 等工作。在第5小结的Failover的测试中观察到打开 ReadOnly 可以加速故障恢复,建议实际使用基于ReadOnly 更好的提升服务读写 Redis Cluster 的性能。通过 go-redis,我们可以便捷,高效地使用 ElastiCache 集群。</p>\n<p>除了本博客以外,我们还推出了一系列博客,展示了如何在不同语言中使用不同的客户端对 ElastiCache 集群进行连接和操作,欢迎大家阅读。</p>\n<h3><a id=\\"_380\\"></a>相关博客</h3>\\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/use-redis-py-to-access-amazon-elasticache-for-redis-cluster/\\" target=\\"_blank\\">条条大路通罗马 —— 使用redis-py访问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<h3><a id=\\"_387\\"></a>本篇作者</h3>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/f03a2a4b7a8e4a05ae3087b763af18e3_image.png\\" alt=\\"image.png\\" /></p>\n<p><strong>唐健</strong><br />\\nAmazon 解决方案架构师,负责基于 Amazon 的云计算方案的架构设计,同时致力于 Amazon 云服务在移动应用与互联网行业的应用和推广。拥有多年移动互联网研发及技术团队管理经验,丰富的互联网应用架构项目经历。</p>\n"}
目录
亚马逊云科技解决方案 基于行业客户应用场景及技术领域的解决方案
联系亚马逊云科技专家
亚马逊云科技解决方案
基于行业客户应用场景及技术领域的解决方案
联系专家
0
目录
关闭