火山引擎 Redis 云原生实践( 二 )

  • 节点的调度由 K8s 来完成 。在实际部署一个 Redis 集群时,为了保证高可用,需要让 Redis 集群的一些组件满足一定的放置策略 。要满足放置策略,在物理机时代需要运维系统负责完成机器的筛选以及计算的逻辑,这个逻辑相对比较复杂 。K8s 本身提供了丰富的调度能力,可以轻松实现这些放置策略,从而降低运维系统的负担 。
  • 节点的管理和状态保持由 K8s 完成 。在物理机时代,如果某台物理机挂了,需要运维系统介入了解其上部署的服务和组件,然后在另外一些可用的机器节点上重新拉起新的节点,填补因为机器宕机而缺少的节点 。如果由 K8s 来完成节点的管理和状态的保持,就可以降低运维系统的复杂度 。
  • 标准化 Redis 的部署和运维的模式 。尽量减少人工介入,提升运维自动化能力,这是最重要的一点 。
  •  
    Redis 集群架构
    下面介绍一下我们的 Redis 集群架构 。集群里有三个组件:Server、Proxy 和 Configserver,分别完成不同的功能 。
    • Server:存储数据的组件,即 Redis Server,其后端部署模型是一个多分片的模型 。分片之间的 Server Pod 没有通信,为 share-nothing 的架构 。分片内部为一主多从的模式,可以一主一从、一主两从,甚至更多 。
    • Proxy:承接 client 发来的请求,同时根据读写拓扑,把请求转发给后端的 Server 分片 。
    • Configserver:配置管理组件,本身是无状态的,所有的状态信息都存储在 etcd 。集群生命周期里 Server 所有的分片信息都保存在 Configserver 里 。Configserver 会对每一个分片的 Master 节点进行定期探活,如果发现某一个分片的 Master 节点不可用,就会执行 Failover,把分片内可用的 Slave 提成新的 Master,保证分片可继续对外提供服务 。同时,Configserver 也会定期根据 Failover 或其他一些实例信息的变更来更新自己的读写拓扑关系,保证 Proxy 可以从 Configserver 拉取新的正确的配置 。

    火山引擎 Redis 云原生实践

    文章插图
     
    结合以上介绍的 Redis 架构以及 K8s 的特性,我们抽象了一个 Redis 集群在 K8s 集群上部署的基本形态:
    • 使用 Deployment 将无状态的 Configserver 部署在 K8s 上 。因为 Configserver 可被所有 Redis 集群共用,为了简化运维复杂度,我们规定所有的 Redis 集群共用一个 Configserver 。
    • Proxy 也是无状态的组件,也用 Deployment 来部署 。
    • 因为我们有多分片,而且 Server 是有状态的,所以每一个分片用 StatefulSet 进行托管 。在新建集群时,我们默认分片内的 0 号 Pod 为 Master Pod,其余所有的 Pod 是 Slave 。这是一个初始状态,后续可能会跟随 Failover 或其他异常发生变更,但是 Configserver 里会实时记录最新的状态信息 。
     
    Redis Server 启动的时候需要一些配置文件,里面涉及到一些用户名和密码,我们是用 Secret 来存储的 。在 Server Pod 运行的时候通过 volume 机制挂载到 Server Pod 内部 。
    对于 Proxy,通过 HPA,基于 Proxy 的 CPU 利用率,支持 Proxy 服务的动态扩缩容 。
    火山引擎 Redis 云原生实践

    文章插图
     
    放置策略
    对于一个 Redis 集群涉及到的 Server 和 Proxy 组件,我们有一些放置策略的要求,比如:
    • 同一个 Server 分片下的节点不能在同一台机器上,即,一个分片内的主从节点不能在同一台机器上 。转换成 K8s 里面的模型,即我们希望一个 StatefulSet 下所有的 Pod 部署在不同的机器上 。我们会利用 Pod-AntiAffinity 下面的 required 语义,来保证 StatefulSet 下所有的 Pod 都部署在不同的机器上 。
    • 一个集群下的 Proxy Pod 需要尽可能分布在不同的机器上,可通过 Pod-AntiAffinity 下的 preferred 语义加上拓扑分布约束来满足 。preferred 语义只能保证 Pod 尽可能分布在不同的机器上,为了避免极端情况下所有 Pod 都在同一台机器上的情况,我们会使用拓扑分布约束 。
     
    存储
    存储使用的是 PVC 加 PV 再加上具有动态供给能力的 StorageClass 。使用 StorageClass 是为了抽象不同的存储后端,可支持本地磁盘和分布式存储 。可以通过 StorageClass 的配置直接申请对应的存储,不用了解具体后端的实现 。
     
    另外,我们使用的是支持动态供给的 StorageClass,可自动按需创建不同大小的 PV 。如果使用静态供给,就无法提前预知所有 Redis 实例的规格,也无法把它们对应的指定数量的 PV 都创建出来 。


    推荐阅读