简介: MySQL 作为以后比拟受欢迎的关系型数据库(RDS),在云原生浪潮中依然面临诸多挑战。如何用 Cloud Native 的设计准则,通过沙箱隔离、计算和数据的齐全拆散,实现低成本、可扩大、高可用的 Cloud RDS 计划?阿里云数据库团队的姜杉彪(孟宇)同学将介绍一种云原生分布式 MySQL 高可用数据库计划,分享其中的关键技术,并对云原生场景下传统数据库的发展趋势做简要剖析。
云时代的到来,无论传统行业还是互联网行业,业务越来越多样,迭代速度越来越快,使得整体数据量大幅晋升。
近两年,随着 Docker + Kubernetes 等技术的衰亡,大家都将业务往容器化迁徙,团队的技术也在往云原生方向演进。晚期的 Kubernetes 着重解决 Stateless 和 Share Nothing 的利用部署场景,然而在现在愈发简单的利用场景中常常会遇到有状态保留的需要。
从 Kubernetes 1.9 开始,针对有状态服务的资源类型 Statefulset 进入 GA,而且 Kubernetes 1.14 版本 Local Volume、CSI 等存储性能也进入 GA 阶段,Kubernetes 对有状态服务的反对失去全面增强,这使得很多数据存储型根底中间件往 Kubernetes 迁徙成为可能。
MySQL 作为以后比拟受欢迎的开源关系型数据库(RDS),集牢靠、易用、功能丰富、适用范围广等特点于一身,使其成为关系型数据库的次要抉择。尽管备受关注,但 MySQL 在云原生浪潮中却也面临着诸多挑战。如何用 Cloud Native 的设计准则,通过沙箱隔离、计算和数据的齐全拆散,实现低成本、可扩大、高可用的 Cloud RDS 计划?
本文介绍一种云原生分布式 MySQL 高可用数据库计划(下称“SlightShift MySQL 高可用计划”),并对云原生场景下传统数据库的发展趋势做简要剖析。
一 需要 & 挑战
在思考云原生场景下的 MySQL 高可用架构时,次要有如下几个方面的挑战:
- 故障转移:主库产生宕机时,集群可能主动选主并疾速转移故障,且转移前后数据统一。
- 麻利弹性伸缩:基于正本的弹性横向扩大,扩缩容过程不中断业务拜访。
- 数据安全性:数据定时冷备 / 实时热备,以便故障复原和数据迁徙。
- 数据强一致性:用作备份 / 只读正本的 Slave 节点数据应该和主节点数据放弃实时或半实时统一。
二 指标 & 要害思考点
SlightShift MySQL 高可用计划要达到的指标:
- SLA 保障:一年内可承受最高 52.56 分钟服务不可用(99.99%)。
- 故障转移:主库出现异常时主从切换耗时 < 2min。
- 弹性扩大:从库实践可有限扩大,扩大从库耗时 < 2min。
- 冷备复原:MySQL 集群呈现不可恢复性问题时,从冷备复原耗时 < 10min。
除以上指标外,在技术架构设计时还需重点思考以下关键点:
- 高可用
- 利用接入老本
- 资源占用量
- 可扩展性
- 可维护性
三 架构设计
该 MySQL 高可用计划应用一主多从的复制构造,主从数据复制采纳半同步复制,保障了数据一致性和读写效率,实践上从库可有限扩大。
在数据引擎下层增加了仲裁器,应用 Raft 分布式一致性算法实现自动化选主和故障切换。
路由层应用 ProxySQL 作为 SQL 申请代理,可能实现读写拆散、负载平衡和动静配置探测。
监控告警方面,采纳 Prometheus-Operator 计划实现整个 MySQL 高可用零碎的资源监控告警。
运维管控方面,引入 Kubernetes Operator 的管控模型来实现 DB-Operator,可能做到申明式配置、集群状态治理以及 On Demand(按需创立)。另外,能够在 MySQL 管制台上进行 MySQL 的根底运维,例如:数据库治理、表治理、SQL 查问,索引变更、配置变更、数据备份 & 复原等。
四 关键技术
状态长久化
容器技术诞生后,大家很快发现用它来封装“无状态利用”(Stateless Application)十分好用。但如果想要用容器运行“有状态利用”,其艰难水平就会直线回升。
对于 MySQL 等存储型分布式应用,它的多个实例之间往往有依赖关系,比方:主从关系、主备关系。各个实例往往都会在本地磁盘上保留一份数据,当实例被杀掉,即使重建进去,实例与数据之间的对应关系也曾经失落,从而导致利用失败。
这种实例之间有不对等关系,以及实例对外部数据有依赖关系的利用,被称为“有状态利用”(Stateful Application)。
Kubernetes 集群中应用节点本地存储资源的形式有 emptyDir、hostPath、Local PV 等几种形式。其中,emptyDir 无奈长久化数据,hostPath 形式须要手动治理卷的生命周期,运维压力大。
因而在 MySQL 场景中,出于性能和运维老本思考须要应用本地存储,Local PV 是目前为止惟一的抉择。
Local PV 利用机器上的磁盘来寄存业务须要长久化的数据,和远端存储相似,Pod 的数据和生命周期是相互独立的,即便业务 Pod 被删除,数据也不会失落。
同时,和远端存储相比,本地存储能够防止网络 IO 开销,领有更高的读写性能,所以分布式文件系统和分布式数据库这类对 IO 要求很高的利用非常适合本地存储。
不同于其余类型的存储,Local PV 本地存储强依赖于节点。换言之,在调度 Pod 的时候还要思考到这些 Local PV 对容量和拓扑域的要求。
MySQL 在应用 Local PV 时,次要用到两个个性:提早绑定机制和 volume topology-aware scheduling。提早绑定机制能够让 PVC 的绑定推延到有 MySQL Pod 应用它并且实现调度后,而 volume topology-aware scheduling 则能够让 Kubernetes 的调度器晓得卷的拓扑束缚,也就是这个存储卷只能在特定的区域或节点上应用(拜访),让调度器在调度 Pod 的时候必须思考这一限度条件。
另外,MySQL 应用的 Local PV 还需通过 nodeAffinity 将 Pod 调度到正确的 Node 上。
上面展现了 MySQL 应用 Local PV 的简略配置示例:
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
creationTimestamp: null
name: local-storage
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Delete
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
pv.kubernetes.io/bound-by-controller: "yes"
creationTimestamp: null
labels:
app: slightshift-mysql
mysql-node: "true"
name: data-slightshift-mysql-0
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-volume-storage
volumeName: mysqlha-local-pv-0
---
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/resource-policy: keep
volume.alpha.kubernetes.io/node-affinity: '{"requiredDuringSchedulingIgnoredDuringExecution":
{ "nodeSelectorTerms": [ { "matchExpressions": [ { "key": "kubernetes.io/hostname",
"operator": "In", "values": ["yz2-worker004"] } ]} ]} }'
labels:
pv-label: slightshift-mysql-data-pv
type: local
name: slightshift-mysql-data-pv-0
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 500Gi
local:
path: /var/lib/ali/mysql
persistentVolumeReclaimPolicy: Retain
storageClassName: pxc-mysql-data
以上是 Local PV 的根底用法,而 MySQL 还需思考节点的弹性伸缩,这就要求底层存储也可能随着 MySQL 实例伸缩来动静配置。这里提出两种解决方案:
- 在 Kubernetes 集群中引入 LVM Manager,以 DaemonSet 模式运行,负责管理每个节点上的磁盘,汇报节点磁盘容量和残余容量、动态创建 PV 等;再引入 local storage scheduler 调度模块,负责为应用本地存储的 Pod 抉择适合(有足够容量)的节点。
- 应用开源计划 OpenEBS:iSCSI 提供底层存储性能,OpenEBS 治理 iSCSI(目前只反对 PV 的 ReadWriteOnce 拜访模式)。
自动化选主
SlightShift MySQL 高可用架构基于 Raft 强统一协定实现分布式 MySQL 自动化选主。
Raft 应用心跳来触发选主,当 MySQL Server 启动时状态是 follower。当 server 从 leader 或者 candidate 接管到非法的 RPC 时,它会放弃在 follower 状态,leader 会发送周期性的心跳来表明本人是 leader。
当一个 follower 在 election timeout 工夫内没有接管到通信,那么它会开始选主。
选主的步骤如下:
- 减少 current term。
- 转成 candidate 状态。
- 选本人为主,而后把选主 RPC 并行地发送给其余的 server。
- candidate 状态会持续放弃,直到下述三种状况呈现。
candidate 会在下述三种状况下退出:
- server 自身成为 leader。
- 其余的 server 选为 leader。
- 一段时间后,没有 server 成为 leader。
故障转移
在失常运行的主从复制环境中,故障转移 (Failover) 模块会监听集群状态,当 Master 产生故障时会主动触发故障转移。
故障转移的第一步是自动化选主,自动化选主的逻辑在下面已介绍过;其次是数据一致性保障,需最大化保障 Dead Master 的 数据被同步到 New Master。
在将 Master 切换到 New Master 之前,局部 slave 可能还未接管到最新的 relay log events,故障转移模块也会从最新的 slave 自动识别差别的 relay log events,并 apply 差别的 event 到其余 slaves,以此保障所有 slave 的数据都是统一的。
对于代理层,ProxySQL 会实时探测 MySQL 实例的可读写配置,当 MySQL 实例的可读写配置发生变化时,ProxySQL 会主动调整 MySQL 实例的读写分组配置,最终保障在 Failover 之后读写拆散能正确运行。
SlightShift MySQL 主动故障转移的步骤如下:
- HA-Manager 侦测到 Master Server 连贯异样,启动 Failover。
- 尝试敞开 Dead Master 以防止脑裂(此步骤可选)。
- 获取最新数据 slave 的 end_log_pos,并从 Dead Master 同步 bin-log,最终保障所有 slave 的 end_log_pos 统一。
- 应用 raft 分布式算法选举 New Master。
- 将 Master 切换到 New Master。
- 回调 ProxySQL 代码服务,剔除 Dead Master 配置。
- ProxySQL 网关检测到各个 MySQL 实例的可读写配置变动,调整读写拆散配置。
- 告诉切换后果(邮件、钉钉群机器人)。
SlightShift MySQL 能做到秒级故障转移,5-10 秒监测到主机故障,5-10 秒 apply 差别 relay logs,而后注册到新的 master,通常 10-30 秒即可 total downtime。另外,可在配置文件里配置各个 slave 入选为 New Master 的优先级,这在多机房部署 MySQL 场景下很实用。
故障主动复原
宕机的 Master 节点复原时:
- 如果复原的节点从新创立 Mysql Pod 并退出集群,Sentinel 会配置新 Pod 为 Slave,通过获取以后 Master Mysql 的 log file 和 Position 来实现新退出 Slave 的数据同步。
- 如果复原的节点中 Mysql Pod 仍然存在且可用,Sentinel 此时会发现 2 个 Label 为 master 的 Pod。Sentinel 会仍然应用宕机期间抉择的新 Master,且把复原的 Master 强制设置为 Slave,并开启只读模式。
宕机的 Slave 节点复原时:
- 如果复原节点从新创立 Mysql Pod 并退出集群,Sentinel 会配置新 Pod 为 Slave,通过获取以后 Master Mysql 的 log file 和 Position 来实现新退出 Slave 的数据同步。
- 如果复原的节点中 Mysql Pod 仍然存在且可用,调度器不会执行操作,proxysql-service 会监测到新退出的 Slave,并将其退出到 endpoints 列表。
状态治理
状态治理并非陈腐话题,它为中心化零碎散发统一的状态,确保分布式系统总是朝预期的状态收敛,是中心化零碎的基石之一。
MySQL 服务由一个 Master 节点和多个从 Master 上异步复制数据的 Slave 节点组成,即一主多从复制模型。其中,Master 节点可用来解决用户的读写申请,Slave 节点只能用来解决用户的读申请。
为了部署这样的有状态服务,除了 StatefulSet 之外,还须要应用许多其它类型的 k8s 资源对象,包含 ConfigMap、Headless Service、ClusterIP Service 等。正是它们间的相互配合,能力让 MySQL 这样的有状态服务有条件运行在 k8s 之上。
在 MySQL 集群中,状态转移最常产生在 Master 产生故障,集群进行故障转移期间。当 Master 产生故障宕机时会主动触发选主逻辑,选主完结后会进行故障切换,直至 New Master 失常提供读写服务,故障转移过程是须要工夫的,在故障转移过程中 MySQL 服务会处于不可写状态。
在故障转移期间,Dead Master 实例会被 k8s 集群主动拉起,Dead Master 被拉起后会认为本人是非法的 Master,这样会造成集群中同时存在两个 Master,写申请很可能会被随机调配到两个 Master 节点,从而造成脑裂问题。
为了解决这种场景下的脑裂问题,咱们引入了 InitContainer 机制,再配合 Sentinel 就能很好的解决该问题。
InitContainer,顾名思义,在容器启动的时候会先启动一个或多个容器去实现一些前置性工作,如果有多个,Init Container 将依照指定的程序顺次执行,只有所有的 InitContainer 执行完后主容器才会启动。
咱们在每一个 MySQL 的实例中都退出了 InitContainer,在 Dead Master 被 k8s 主动拉起之后,InitContainer 会自动检测集群中是否处于 Failover 阶段,如果处于 Failover 阶段会进入 Sleep 轮询状态,直至 Failover 完结。
当 Failover 完结后,Sentinel 检测到 Dead Master 被拉起,会主动将 Dead Master 设置为 New Master 的 Slave 节点,以此来实现一次残缺的 Failover 过程,并防止集群呈现脑裂问题。
申明式运维
咱们在应用 k8s 时,个别会通过 k8s 的 Resource 满足利用治理的需要:
- 通过 Deployment、StatefulSet 等 workload 部署服务。
- 通过 Service、Ingress 治理对服务的拜访。
- 通过 ConfigMap、Secrets 治理服务的配置。
- etc.
上述 Resource 表征 User 的冀望,kube-controller-mananger 中的 Controller 会监听 Resource Events 并执行相应的动作,来实现 User 的冀望。
这种操作形式给利用治理带来了很大的便当,User 能够通过申明式的形式治理利用,不必再关怀如何应用传统的 HTTP API、RPC 调用等。
Controller 会通过各种机制来确保实现 User 的冀望,如通过一直检测 Object 的状态来驱使 Object 以后状态合乎用户冀望,这种运维模式咱们称之为申明式运维。
但如果仅仅应用 k8s 提供的根底类型,对于 MySQL 这类简单利用来说运维老本仍然很高。如果能将申明式运维的模式进行扩大延长到 MySQL 利用,会极大水平升高 MySQL 利用的部署和运维老本。
为了解决这个问题,slightshift-mysql-operator 应运而生。
- slightshift-mysql-operator 实质上是 Resource + Controller:
-
- Resource
自定义资源(CRD, Custom Resource Definitions),为 User 提供一种申明式的形式形容对服务的冀望。
- Controller
-
- 实现 Resource 中 User 的冀望。
slightshift-mysql-operator 通过组合 k8s 中已有的概念,极大升高了部署和运维 MySQL 的老本:
- User 通过相似应用 Deployment 的形式形容对 MySQL 的需要
-
- 在 k8s 上部署 MySQL 利用的姿态与 k8s 官网资源的操作形式雷同。
- 由 slightshift-mysql-operator 的 Controller 监听、处理事件申请
-
- 监听 Resource Events。
-
- 针对不同类型 (ADD/UPDATE/DELETE) 的 Events 执行相应的动作。
-
- 一直检测 Object 的状态来执行动作,驱使服务的状态合乎 User 冀望。
slightshift-mysql-operator 的设计理念:
- 申明式配置:晋升可读性和运维效率,升高运维老本。
- 最终一致性:动静调整集群状态实现最终一致性。
slightshift-mysql-operator 极大水平上升高了在 Kubernetes 集群中应用和治理 MySQL 利用的老本,User 能够通过申明式的 CR 创立利用,Vendor 可将治理利用的专业知识封装,对 User 通明。
slightshift-mysql-operator 在架构层面上应用分层设计,简化了架构复杂度,同时尽可能升高了 MySQL 多个集群实例间互相烦扰,实现各个实例的自治。
部署构造如下图所示:
备份与复原
为保证数据安全性,还需对数据进行定期备份,保障用户数据在极其状况下(集群解体)时数据可复原。
通过 Cronjob 对 Mysql 数据进行备份:
- 在 Kubernetes 集群中创立 Cronjob,Cronjob 会定期进行数据备份。
- 备份数据会被打上工夫标签,并上传到对象存储服务器(Ceph、Minio)。
通过创立 Job 对 Mysql 进行数据恢复:
- 在集群中创立复原 Job,Job 依据 SNAP_SHOT 到存储服务器(OSS、Minio)上获取备份数据。
- Job 把数据恢复到 Mysql Master 实例,同时 Slave 会实现数据同步。
五 技术演进
唯有进化,能力站到食物链顶端。
依据 Gartner 报告预测,数据库云平台市场份额将会在下一个五年中翻倍,而 70% 的用户将开始应用 dbPaaS 数据库云平台。因而,为了满足各类应用程序对数据库云平台的需要,同时为了缩小公有云部署中对大量不同类型数据存储产品的运维复杂性,数据库的架构演进将是将来十年数据库转型的次要方向之一。
云原生数据库是将来数据库倒退的一个重要方向,云原生数据库架构随着云化要求也须要进行相应的迭代,将来在云原生数据库架构的演进还会随着需要的变动而继续倒退。
六 将来瞻望
中间件作为构筑下层业务零碎的基石和核心技术,具备高可靠性、高扩展性、强专业性等特点。
对于将来,因为中间件的这些特点,将来云原生中间件会向着规范化、构建化、松耦合和平台化的方向倒退。平台化可能更疾速响应业务变动,为业务的横向发展提供集中的技术解决方案。
从终态来看,咱们想做一个企业级云原生中间件 PaaS 平台,涵盖根底中间件的部署、配置、降级、伸缩、故障解决等自动化能力。
首先谈谈我对中间件平台的了解,中间件平台应该是一系列中间件解决方案汇合的能力开放式技术中台,它造成了残缺、久经考验、凋谢和组件化的解决方案,旨在为复杂多变的下层业务畛域提供稳固牢靠的计算、存储服务。
任何一个平台型产品的倒退历程,都不是一开始就做平台的,而是先有业务需要,为了解决理论业务需要而积攒了某些畛域的常识,进而成为了这些畛域的专家,最终再向平台转变。换句话说,平台是一个继续积攒的过程,也是一个瓜熟蒂落的过程。
在企业信息平台构建过程中,无效、正当、规范化的利用中间件平台来疾速构建下层业务零碎,能够为企业及时响应需要变动提供无力保障,造成企业的集约化治理,进而晋升企业外围力量取得可继续竞争的劣势。