亚马逊AWS官方博客

基于 Redis-Cluster-Proxy 集群的 Redis 非原生集群至 AWS Redis 原生集群异构迁移方案

关键字

Amazon ElastiCache,数据迁移,Redis 异构迁移,非集群模式,Redis 集群访问代理,自动扩展

背景

在一些客户的迁移项目中,我们会发现客户可能在自建或者托管 Redis 集群并非采用 Redis 原生集群模式(尤其是 Redis 5.0 以下版本),比如采用了 Codis 或者 Twemproxy 的代理集群模式方式。而客户对于迁移至 Amazon ElastiCache for Redis 后希望采用原生集群方案,并需要保证迁移后的原生集群尽量规避修改客户应用侧代码进行访问。而目前现有的亚马逊云科技原生 Redis 迁移工具并没有可以针对非亚马逊云科技原生 Redis 实例迁移至 Amazon ElastiCache for Redis(AWS ElastiCache 迁移先决条件),所以针对这种异构的迁移方案,我们采用了解决方案:Amazon ElastiCache for Redis 集群代理方案。

Amazon ElastiCache for Redis 集群代理方案设计

架构设计

  • Redis 数据迁移
    • 在源端(IDC/公有云)针对每一个 Redis 实例部署一个专属的 redis-port 代理用于迁移源端 Redis 数据。
    • 源端和亚马逊云科技网络之间通过 Amazon Direct Connect 专用网络连接确保网络的稳定和低延迟。
    • 使用网络负载均衡器进行负载均衡对于源端的写入请求进行分配至一个 Auto Scaling Group 中,其中每一个 EC2 部署 Redis 集群代理实例用于写入请求命令转换。
    • 每一个 Redis 集群代理均是指向不同的 Amazon ElastiCache for Redis 节点平衡 Redis 集群中节点的写入流量,同时也是无状态存在并只负责进行协议层的命令转发。
  • 应用程序访问
    • 保持应用侧客户端单实例 Redis 访问方式不变,无需客户针对原生 Redis 集群改变客户端访问方式。
    • 将客户端 Redis 连接目标地址设置为负责均衡的网络负载均衡器开放的接口,利用网络负载均衡器 4 层协议转发保证 Redis 请求命令至 Redis 集群代理。而由于 Redis 单节点和集群模式使用不同的请求命令,所以使用 Redis 集群代理完成命令转换。
    • Redis 集群代理转换单节点 Redis 请求命令后转发至相应的 Amazon ElastiCache for Redis 集群相应节点保证结果正确并按原访问链路返回。

注意,该图示只是构建了 3 个 Redis 集群代理实例用于展示架构,真实迁移中的实例数量根据 Redis-Port Proxy 数量和数据量综合考虑。

Redis 集群代理部署详解

Redis 集群代理单节点部署测试

  • 参考官方安装教程进行安装。注意,在安装过程中根据不同的操作系统可能缺少某些必要的依赖,请根据错误提示安装相应依赖。
  • 安装好以后,进入 redis-cluster-proxy/src, 使用./redis-cluster-proxy 进行启动。注意,不同的 Redis 节点使用”,”进行分割,同时必须带上端口号。
    ./redis-cluster-proxy test-0002-001.pe3b6y.0001.apse1.cache.amazonaws.com:6379,test-0003-001.pe3b6y.0001.apse1.cache.amazonaws.com:6379 &
  • 使用 redis-cli 连接 redis-cluster-proxy 实例 7777 端口,并发送 set 命令进行测试。并通过 redis-cli 连接 Redis 集群测试通过 Redis 集群代理转发的命令是否执行成功。
    • 首先连接 redis-cluster-proxy 7777 端口,并添加 key “123”和”1234″。
    • 连接 AWS ElastiCache 集群,检查添加的 key 是否成功。
  • 在测试成功以后,我们对该 EC2 实例创建 AMI 用于集群其他的实例部署。具体步骤如下:

    • 首先选择需要创建 AMI 的 EC2 实例,通过菜单选择创建出相应的镜像。
    • 在 AMIs 中选择我们创建出来的镜像,并通过该镜像启动相应数量的 EC2 实例。在该实例中 Redis 集群代理已经安装完毕可以直接启动。

至此,我们已经安装并且验证 Redis 集群代理可以成功帮助我们转发面向单节点 Redis 写入命令的转发至集群版 Redis。

Redis 集群代理高可用部署测试

  • 手动 Redis 集群代理高可用设置
    基于之前对于 Redis 集群代理高可用的架构,我们通过设置网络负载均衡器对于 Redis 协议层的命令进行转发。
    • Target Group 创建:
      • 进入 AWS 控制台中 EC2 页面,并在侧边栏选中 Target Group。
      • 选择创建 Target Group,并进行所需要的配置。注意我们需要转发的是 TCP 且端口为 7777 的请求,并且只选择部署有 Redis 集群代理的实例。
      • 创建 Target Group 结束后等待数分钟,然后检查注册的实例是处于 healthy 状态。
    • 网络负载均衡器创建:
      • 在 Target Group 中选择我们刚创建的名为“redis-cluster-proxy”的 Target Group。
      • 创建成功后我们可以看见网络负载均衡器 DNS 地址,通过该地址我们可以进行测试。
        1. 首先通过 redis-cli 连接网络负载均衡器的 7777 端口,并添加”789″和”7890″两个 key。
        2. 然后通过 redis-cli 连接到 Redis 集群,通过 key ”789“和”7890“成功获取两个 key 的值。
  • CloudFormation 自动化集群部署方案
    由于手动部署耗时且容易出现疏忽和错误,于是我们提出了基于 CloudFormation 的自动化部署方案,通过 IaC(Infra as Code)的思想使用 AWS CloudFormation 服务的能力将我们之前设计的基于 Redis 集群代理构建的异构 Redis 集群适配架构固化成模板。使用改模板输入针对于相适应场景的参数构建出相应的服务架构。

    • CloudFormation 方案前期准备
      EC2 AMI:需要建立一个 EC2 的 AMI,在该 AMI 中要将 Redis 集群代理建立为 Linux 服务保证即便重启还要确保服务存活(具体制作步骤详见附录)。
    • CloudFormation Stack 模板
      将 CloudFormation 模板代码保存成 yaml 文件,并通过 AWS CloudFormation 控制台中上传生成 Stack 构建相应的资源架构。

      AWSTemplateFormatVersion: 2010-09-09
      Resources:
        RedisProxyAutoScalingGroupConfiguration:
          Type: 'AWS::AutoScaling::LaunchConfiguration'
          Properties:
            KeyName: !Ref RedisProxyKey
            ImageId: !Ref RedisClusterProxyAMI
            SecurityGroups:
              - !Ref RedisProxySecurityGroup
            InstanceType: !Ref RedisClusterProxyInstanceType
            UserData:
              Fn::Base64: 
                !Sub  |
                  #!/bin/bash -xe
                  sed -i 's/REDIS-ENDPOINT/${RedisClusterConfigurationEndPoint}/g' /opt/systemd-sh/redis-cluster-proxy.sh
                  sed -i 's/THREAD-COUNT/${RedisClusterProxyThreadCount}/g' /opt/systemd-sh/redis-cluster-proxy.sh
                  systemctl enable redis-cluster-proxy
                  systemctl start redis-cluster-proxy
      
        RedisProxyAutoScalingPolicyCPUUtilization:
          Type: AWS::AutoScaling::ScalingPolicy
          Properties:
            AutoScalingGroupName: !Ref RedisProxyAutoScalingGroup
            PolicyType: TargetTrackingScaling
            TargetTrackingConfiguration:
              PredefinedMetricSpecification:
                PredefinedMetricType: ASGAverageCPUUtilization
              TargetValue: !Ref RedisClusterProxyAutoScallingCPUThreshold
      
        RedisProxyTargetGroup:
          Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
          Properties:
            HealthCheckEnabled: true
            HealthCheckIntervalSeconds: 10
            VpcId: !Ref RedisProxyVPC
            Protocol: TCP
            Port: 6379
      
        RedisProxyNLB:
          Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
          Properties:
            Name: !Ref RedisProxyClusterName
            Type: network
            Scheme: internal
            Subnets: !Split [',', !Join [',', !Ref RedisProxySubnets]]
      
        RedisProxyELBListener:
          Type: 'AWS::ElasticLoadBalancingV2::Listener'
          DependsOn:
            - RedisProxyNLB
            - RedisProxyTargetGroup
          Properties:
            DefaultActions:
              - Type: forward
                TargetGroupArn: !Ref RedisProxyTargetGroup
            Port: 6379
            Protocol: TCP
            LoadBalancerArn: !Ref RedisProxyNLB
      
        RedisProxyAutoScalingGroup:
          Type: 'AWS::AutoScaling::AutoScalingGroup'
          DependsOn:
            - RedisProxyAutoScalingGroupConfiguration
            - RedisProxyTargetGroup
          Properties:
            TargetGroupARNs:
              - !Ref RedisProxyTargetGroup
            VPCZoneIdentifier: !Split [',', !Join [',', !Ref RedisProxySubnets]]
            DesiredCapacity: !Ref RedisClusterProxyDefaultInstanceCount
            HealthCheckGracePeriod: 100
            LaunchConfigurationName: !Ref RedisProxyAutoScalingGroupConfiguration
            MinSize: !Ref RedisClusterProxyMinimalInstanceCount
            MaxSize: !Ref RedisClusterProxyMaximumInstanceCount
            Tags:
              - Key: Name
                Value: !Ref RedisProxyClusterName
                PropagateAtLaunch: true
        
      Parameters:
      
        RedisClusterProxyThreadCount:
          Type: Number
          Description: Set Redis Cluster Proxy thread count
          Default: 100
        RedisClusterProxyAutoScallingCPUThreshold:
          Type: Number
          Description: Set CPU threshold for redis proxy cluster scale up
          Default: 60
        RedisClusterProxyDefaultInstanceCount:
          Type: Number
          Description: Set initial size of EC2 instances for redis proxy cluster
          Default: 8
        RedisClusterProxyMinimalInstanceCount:
          Type: Number
          Description: Set minimize size of EC2 instances for redis proxy cluster
          Default: 4
        RedisClusterProxyMaximumInstanceCount:
          Type: Number
          Description: Set maximum size of EC2 instances for redis proxy cluster
          Default: 16
        RedisClusterProxyAMI:
          Type: String
          Description: Set Redis Cluster Proxy AMI ID
        RedisClusterProxyInstanceType:
          Type: String
          Default: c5.large
          AllowedValues:
            - c5.2xlarge
            - c5.xlarge
            - c5.large
          Description: Enter EC2 type for redis cluster proxy.
        RedisProxyClusterName:
          Type: String
          Description: Specific Redis Proxy Name
        RedisClusterConfigurationEndPoint:
          Type: String
          Description: Specific corresponding Redis Cluster Configuration Endpoint
        RedisProxyVPC:
          Type: 'AWS::EC2::VPC::Id'
          Description: Choose one valid VPC for Redis Proxy
        RedisProxySubnets:
          Type: 'List<AWS::EC2::Subnet::Id>'
          Description: Choose one or more valid subnet for Redis Proxy
        RedisProxyKey:
          Type: 'AWS::EC2::KeyPair::KeyName'
          Description: Select the key pair for those EC2 instance
        RedisProxySecurityGroup:
          Type: 'AWS::EC2::SecurityGroup::Id'
          Description: Choose Security Group for this cloudformation
      
      Outputs:
        RedisProxyNLBDNSName:
          Description: The DNSName of the Redis Proxy NLB load balancer
          Value: !GetAtt RedisProxyNLB.DNSName
    • CloudFormation 参数配置
      参数名称 参数描述 参数建议
      RedisClusterConfigurationEndPoint 需要填写相对应的 AWS Elasticache 集群的 Configuration Point
      RedisClusterProxyAMI 需要填写之前创建的包含 Redis 集群代理服务 AMI 的 ID。
      RedisClusterProxyAutoScallingCPUThreshold 自动扩展组 CPU 使用率值。 建议使用 70%
      RedisClusterProxyDefaultInstanceCount Redis 集群代理自动扩展组的初始 EC2 数量。 按照 QPS 3w/s 计算
      RedisClusterProxyMaximumInstanceCount Redis 集群代理自动扩展组的最大 EC2 数量。 按照 QPS 3w/s 计算
      RedisClusterProxyMinimalInstanceCount Redis 集群代理自动扩展组的最小 EC2 数量。 按照 QPS 3w/s 计算
      RedisClusterProxyInstanceType 选择 Redis 集群代理 自动扩展组的 EC2 实例机型 建议使用 M5 机型
      RedisClusterProxyThreadCount 每一个 Redis 集群代理进行 Redis 协议转发的线程数。 默认 8,建议不超过 20
      RedisProxyClusterName 填写 Redis 集群代理高可用名称。
      RedisProxyKey 选择 EC2 的 Key Pair。
      RedisProxySecurityGroup 选择对应的 Security Group。
      RedisProxySubnets 选择 Redis Proxy 部署的 Subnets。
      RedisProxyVPC 选择 Redis Proxy 部署的 VPC。
  • 确定网络负载均衡器健康检查
    同手动创建网络负载均衡器一样,我们需要通过网络负载均衡器控制台确保所有 Redis 集群代理实例完成注册并健康检查通过。至此,通过 CloudFormation 我们完成整体架构的自动化部署。

总结

1. 针对部分客户的源 Redis 集群采用非标的集群方式如 Twemproxy,Codis 等,同时底层 Redis 物理节点无法访问的时候。可以采用 Redis 集群代理伪装为目标端的单节点 Redis 实例接收数据,并转发至真正的目标集群。

2. Redis 迁移完成后对于客户来讲应该尽可能规避掉对于应用客户端代码的修改来适配 Redis 原生集群,而本方案可以帮助客户达到这个目标即使目前 AWS 官方并没有提供相应的代理节点。

3. 采用网络负载均衡器的目的的提高整个 Proxy 集群的吞吐量,如源端输出吞吐量较小的情况,采用单节点 Redis 集群代理依然可以完成迁移的目的。

4. 有用 Redis 集群代理禁掉部分标准 Redis 命令如 info,则针对部分市面工具如 Tencent Cloud DTS 无法使用。Redis 集群代理支持命令

5. 对于超高吞吐量(Redis 集群请求超过 100W/TPS)的情况需要对于 Redis 集群代理配置进行压力测试和调整,如 Keepalive 参数尽可能调大避免大量 TCP 链接重建消耗系统资源。

附录:

1. Redis-Port:用于通过解析源 Redis 实例快照并根据复制缓冲区命令实现 Redis 全量及增量迁移的开源工具(查看详情)。

2. Redis 集群代理:用于解析单节点 Redis 请求命令并转换成集群 Redis 可接受命令的开源工具(查看详情)。

3. Redis 集群代理系统服务 AMI 创建:通过 AWS AMI 能力创建一个具有自动启动 Redis 集群代理作为底层服务的 Ubuntu 镜像。具体自启动服务构造如下:

  • a. 进入建立好的 EC2 实例,根据如下步骤编译 Redis 集群代理
    • sudo su -
      git clone https://github.com/RedisLabs/redis-cluster-proxy.git
      cd redis-cluster-proxy
      apt-get update
      apt install gcc
      cd deps
      apt install make
      make hiredis
      cd ..
      make install
  • b.创建 Systemd Service 确保 Redis 集群代理高可用
    • i. 创建 redis-cluster-proxy.service 文件
      vi /lib/systemd/system/redis-cluster-proxy.service
    • 文件内容如下:
      [Unit]Description=create redis-cluster-proxy service
      [Service]Type=simple
      ExecStart=/opt/systemd-sh/redis-cluster-proxy.sh
      [Install]WantedBy=multi-user.target
    • ii. 创建 redis-cluster-proxy.sh 文件
      mkdir /opt/systemd-sh
      vi /opt/systemd-sh/redis-cluster-proxy.sh
      chmod 777 /opt/systemd-sh/redis-cluster-proxy.sh
    • 文件内容如下:
      #!/bin/bash
      /root/redis-cluster-proxy/src/redis-cluster-proxy --enable-cross-slot --threads THREAD-COUNT -p 6379 REDIS-ENDPOINT
    • iii. 配置 System 自启动
      systemctl daemon-reload
  • c. 在 AWS Console 选择该 EC2 实例并制作 AMI

本篇作者

杨雨龙

AWS 云迁移架构师,负责提供大型企业迁移上云架构和实施落地,并专注于数据库,中间件,以及企业级应用程序的迁移和架构。