火山引擎 Redis 云原生实践
mhr18 2024-11-28 08:44 17 浏览 0 评论
Redis 简介
Redis 是大家日常工作中使用较多的典型 KV 存储,常年位居 DB-Engines Key-Value 存储第一。Redis 是基于内存的存储,提供了丰富的数据结构,支持字符串类型、哈希/列表/集合类型以及 stream 结构。Redis 内置了很多特性,其中比较重要的有:
- 复制:Redis 支持异步的全量和增量同步,可以把数据从 Master 复制到 Slave, 实现 Redis 数据的高可用。
- 持久化:支持数据的持久化,可以通过 RDB 和 AOF 机制实现数据落盘。
- 支持哨兵工具:哨兵工具的主要工作模式是监控 Master 节点的健康状况。当发现 Master 节点不可用时,会主动执行 Failover, 把 Slave 节点提升成 Master,保证 Redis 服务的高可用。
- 提供集群模式:单体 Redis 实例受限于物理机内存,当需要很大的 Redis 集群容量时,可以使用 Redis 集群模式。Redis 集群模式的原理是把保存在其中的数据做了分片,每一部分数据由不同的 Redis 实例承担。
Redis 的典型应用场景有以下 3 种:
- 缓存:因为 Redis 是基于内存的存储,它的读写请求会在内存执行,请求响应的延迟很低,所以很多场景下会把 Redis 当做缓存使用。
- 数据库:Redis 支持持久化,可以把它当做 KV 数据库使用。
- 消息队列:Redis 支持 stream 数据,在 stream 数据结构基础上封装了 pub-sub 命令,实现了数据的发布和订阅,即提供了消息队列的基本功能。
Redis 协议是二进制安全的文本协议。它很简单,可以通过 telnet 连接到一个 Redis server 实例上执行 get 和 set 操作。
K8s 简介
K8s 是一个容器编排系统,可以自动化容器应用的部署、扩展和管理。K8s 提供了一些基础特性:
- 自动装箱:可指定 K8s 里 Pod 所需资源的最小值和最大值,即 limit 和 request 的值。K8s 可以根据 request 的值做 Pod 调度,在一个节点上拉起 Pod。
- 服务发现与负载均衡:K8s 提供基于 DNS 的服务发现机制,同时也提供基于 service 的负载均衡。
- 自动化上线和回滚:这里会涉及到 K8s 的工作负载资源。K8s 提供几种不同的工作负载资源对应不同的业务场景。这些不同的工作负载资源可以实现服务的配置变更,例如更新 image、升级 binary、进行副本的扩缩容等。
- 支持 Deployment/DaemonSet
- 支持 StatefulSet
- 支持 CronJob/Job
- 水平扩缩容:K8s 天然支持水平扩缩容,可以基于 Pod 的 CPU 利用率、内存利用率以及第三方自定义 metrics 对 Pod 进行水平动态扩缩容。
- 存储编排:K8s 支持基于 PV 和 PVC 的存储供应模式,可以通过 PV 和 PVC 在 Pod 内部使用存储。
- 自我修复:举一个例子就是副本保持。比如用 Deployment 来托管一个服务,如果 Deployment 下的一个 Pod 所在的宿主机出现了不可用的情况, K8s 会在可用的节点上重新拉起一个新的 Pod 来提供服务。
现实工作中遇到的服务根据是否需要数据持久化可分为有状态服务和无状态服务。不需要数据持久化的服务被认为是无状态的,包含以下几种类型:
- API 类服务:可在任意节点上执行。如果要在 K8s 上部署这类服务,可使用 K8s Deployment。
- Agent 或 Deamon 类服务:需要部署在每一台机器上,而且每台机器上最多部署一个进程。在 K8s 上可选择 DaemonSet 来完成对应的部署。
- 还有一类无状态服务对固定的唯一标识有需求。要满足这些需求,可使用 K8s 的 StatefulSet 来满足。虽然 StatefulSet 是用来部署有状态服务的,但它可提供固定的唯一标识,也可用来托管无状态服务。
有状态服务需要稳定的持久化存储。除此之外,可能还会有一些其它的特性要求:
- 稳定的唯一标识
- 有序、优雅地部署和缩放
- 有序的自动滚动更新
在 K8s 上,我们一般会用 StatefulSet resource 来托管有状态服务。
Redis 云原生实践
下面将介绍火山引擎 Redis 云原生实践。首先我们会明确 Redis 云原生的目标,主要有以下几个:
- 资源的抽象和交付由 K8s 来完成,无需再关注具体机型。在物理机时代我们需要根据不同机型上的 CPU 和内存配置来决定每个机型的机器上可以部署的 Redis 实例的数量。通过 Redis 云原生,我们只需要跟 K8s 声明需要的 CPU 和内存的大小,剩下的调度、资源供给、机器筛选由 K8s 来完成。
- 节点的调度由 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 架构以及 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 集群涉及到的 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 都创建出来。
Redis 云原生功能介绍
Redis 云原生化以后,Operator 组件是基于 Operator Pattern 实现的一个 custom controller,主要用于编排 Redis Cluster resource,管理 Redis 集群的一些变更。Configserver 也部署在 K8s 上,所有跟 Redis 相关的组件都是云原生化的。
新建集群
- 对于常见的新建集群的请求,会先发给 ApiServer。ApiServer 接收到请求之后,会通过 client go 的 watch 机制让 Operator 感知到。
- 随后 Operator 会请求 ApiServer 创建对应 Server 的 StatefulSet。
- K8s 把所有 Server 的 StatefulSet 创建成功之后,等所有的 Pod 都处于 ready 状态,这时所有分片内的 Server Pod 之间是没有主从关系的。
- Operator 感知到所有的 StatefulSet 都已经处于 ready 的状态之后,会获取所有 Server Pod 信息,并注册到 Configserver。
- Configserver 接下来会连接到所有分片内的 Slave 节点,执行实际的 SLAVEOF 命令,保证建立真正的主从关系。
- Operator 会定期查询 Configserver 里建立主从关系的进度。等所有分片的主从关系建立成功之后, Operator 会请求 ApiServer 把对应的 Proxy 创建出来。
- Proxy 创建出来之后,会去 Configserver 拉取最新的拓扑,保证对外提供服务的时候可以把请求打给后端正常的分片。
分片扩容
在实际使用的过程中如果遇到容量不足的情况,需要进行水平扩容。分片扩容的请求也是类似的:
- 请求发给 ApiServer。
- ApiServer 把请求推送给 Operator。
- Operator 感知到之后,会先给 ApiServer 发请求,把新的分片对应的 StatefulSet 创建出来。
- K8s 会把新分片的 StatefulSet 创建好,在处于 ready 状态之后,一个 StatefulSet 下的每个 Pod 也都是独立的状态,没有建立真正的主从关系。
- Operator 获知新创建的 Server StatefulSet 分片已经处于 ready 状态之后,会把新的 Server 分片的实例地址注册到 Configserver。Configserver 现在会有两个阶段:
- 第一步:指导新分片内真实主从关系的建立。即连到所有的新分片的 Slave 上,执行 SLAVEOF 的命令;
- 第二步:指导数据从老分片迁移到新分片。这样新的分片才能发挥作用,这一步很重要。
- Operator 会一直检查数据迁移或者 rebalance 的进度。等进度结束之后,Operator 会更新 Redis Cluster 里 status 的字段,反映出来当前的分片扩容的操作已经结束了。
分片缩容
分片缩容的流程和分片扩容类似:请求先发送给 ApiServer,Operator 会感知到请求,然后把缩容分片的请求发送给 Configserver。
Configserver 此时做的事情是:
- 先指导数据迁移。考虑到后边的一些分片下线,需要把分片上的数据先迁移到其他可用分片上,保证数据不丢。
- Operator 会一直查询 Configserver 指导的数据 rebalance 的进度。等缩容操作在 Configserver 完成之后,Operator 会请求 ApiServer 执行真正的 Server StatefulSet 删除,这时才是安全的删除操作。
组件升级
一个 Redis 集群会涉及到两个组件:Proxy 和 Server。
无状态的 Proxy 用 Deployment 托管,如果要进行组件升级,直接升级对应的 image 即可。
Server 是一个有状态的组件,它的升级流程相对来说复杂一点。为了简化流程,我们以 Server 只有 1 分片的 Redis 集群为例,介绍升级过程。
- Server 组件的升级请求发送给 ApiServer,ApiServer 接收到这个请求之后会把它推送给 Operator。
- Operator 首先会按照分片内 Pod 编号从大到小的顺序选择要升级的 Pod。
- 选定 Pod 之后,会把它从 Configserver 的读拓扑里摘掉。(如果要摘除的这个 Pod 在集群拓扑里是 Master,我们会先调用 Configserver 的 API,执行 Failover,把它变成 Slave,然后再把它从读的拓扑里边给摘除掉。)
- 之后,Operator 等待 30 秒。这个机制的出发点是:
- 首先,Proxy 去 Configserver 拉取配置是异步过程,可能需要经过至少一轮的数据同步才能正常拿到数据。等待 30 秒主要是为了保证所有的 Proxy 都已经拿到了最新的读拓扑,新的读请求不会再发送到要升级的 Server 节点上。
- 另外,我们要保证等待 30 秒的时间,让已经被要升级的 Server Pod 接收的这些请求都成功地被处理,并且返回之后,才能把要升级的 Server Pod kill 掉。
- 30 秒之后请求 ApiServer 执行实际的 Pod 删除操作。删除之后 K8s 会重新调度一个新的 Pod 起来,这时新创建的 Server Pod 也是一个独立的 Server 的状态,没有跟任何节点建立主从关系。Operator 感知到新的 Server Pod 已经处于 ready 的状态,会把它注册到 Configserver。
- Configserver 连到新的 Server Pod 上,根据它所处的分片跟所在分片的 Master 节点建立主从关系,同时进行数据同步。
- Operator 会定期检查新的 Server 分片在 Configserver 是否已经完成数据同步。如果是,Operator 才会认为一个 Server Pod 完成了升级。该分片内其他所有 Server pod 的升级流程都是类似的,直到该分片内所有 Server Pod 都升级完,才认为这个分片升级完成。最后 Operator 会更新自己 Redis Cluster 的 CRD 里对应的 Status 状态信息,反映当前组件升级的流程和变更操作已经结束了。
总结展望
本次分享的以 Redis 为例,介绍了有状态服务部署到 K8s 的抽象流程,并介绍了火山引擎在 Redis 云原生方向的一些探索和实践。目前火山引擎对于 Redis 云原生已经完成了基本功能的构建,未来会在动态扩缩容、异常恢复、细粒度的资源分配和管理方面,结合 K8s 的特性,进行更多深层次的思考和实践,期望通过云原生化的方式,进一步提升运维自动化能力和资源的利用率。
Q&A
Q1:没用 Cluster 的模式吗?
A:没有,最早也使用过 Cluster 模式,后来业务体量变大,发现 Cluster 有集群规模上限,不能满足业务的需求。
Q2:Redis 的 Proxy 会计算 Key 在哪个分片吗?
A:会的,Proxy 会参考类似 Redis Cluster 的 Key Hash 算法对 Key 进行 hash,之后分布到不同的 Server 分片上。
Q3:如何界定 Slave 可以提升为 Master?切换步骤是怎样的?
A:Configserver 会定期给 Master 发送 health check 请求进行探活。只有连续多次对一个 Master 的探活都失败时,才会认为 Master 不可用。这时 Configserver 会从分片内所有 Slave 中选择可用的提升成新的 Master(不是所有的 Slave 都可用,可能某一个 Slave 也挂了,或者主从数据同步的延迟比较高)。
Q4:Proxy 是每个 Redis 集群独有还是所有集群共享的?
A:Proxy 不是每个 Redis 集群独占的。首先,所有集群共享一个 Proxy 集群,有隔离性的问题。另外,Proxy 支持动态扩缩容,可以做到弹性资源扩缩容,所以不会导致资源浪费。
Q5:系统的稳定性如何,主从切换耗时怎样?
A:稳定性挺好。主从切换的耗时是一个策略问题,需要做一些 tradeoff。如果判断策略太激进,可能会因为临时的网络抖动等因素频繁触发主从切换。实际使用中我们的主从切换耗时在 10s 左右。
Q6:Redis 在什么规模等级下的 K8s 部署会需要修改较多默认配置或者直接更改源码? 在动态扩容的基础上建立 Redis 集群是否会加大困难?有什么方式可以让 Redis 集群无限扩容吗?最多到多少?
A:Redis 目前部署的 K8s 集群规模可选,根据需要的 Redis 集群容量来选择 K8s 规模就可以。适配云原生会需要调整一些组件之间的服务发现方式,但是不需要太多源码的修改。我们目前只支持 Proxy 的动态扩缩容,Server 是有状态的服务,还不太好接入 HPA(因为可能会涉及到一些数据的迁移),虽然 HPA 也支持对 Statefulset 服务的自动化扩缩容。我们的 Redis 架构理论上集群的规模可以很大,现在 CRD 的限制是一个 Redis 集群最多 1024 个分片。
关于火山引擎
火山引擎是字节跳动旗下的数字服务与智能科技品牌,基于公司服务数亿用户的大数据、人工智能和基础服务等技术能力,为企业提供系统化的全链路解决方案,助力企业务实地创新,给企业带来持续、快速增长。
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)