当前位置: 首页 > 科技观察

如何在云原生环境下实现高可用MySQL?

时间:2023-03-14 15:35:23 科技观察

MySQL作为一种比较流行的关系型数据库(RDS),在云原生的浪潮中依然面临着诸多挑战。如何利用CloudNative的设计原则,通过沙箱隔离,实现计算与数据的完全分离,实现低成本、可扩展、高可用的CloudRDS解决方案?阿里云数据库团队姜山彪(孟宇)将介绍一种云原生分布式MySQL高可用数据库解决方案,分享关键技术,并简要分析传统数据库在云原生场景下的发展趋势。随着云时代的到来,无论是传统行业还是互联网行业,业务越来越多样化,迭代速度越来越快,使得整体数据量大幅增加。近两年,随着Docker+Kubernetes等技术的兴起,大家纷纷将业务向容器化迁移,团队的技术也在向云原生方向演进。早期的Kubernetes专注于解决Stateless和ShareNothing的应用部署场景。然而在应用场景越来越复杂的今天,经常会遇到有状态保存的需求。从Kubernetes1.9开始,有状态服务的资源类型Statefulset进入GA阶段,Kubernetes1.14版本LocalVolume、CSI等存储功能也进入GA阶段。Kubernetes对有状态服务的支持得到了全面加强,这使得很多数据存储基础向Kubernetes迁移中间件成为可能。MySQL作为一种比较流行的开源关系型数据库(RDS),具有可靠、易用、功能丰富、应用范围广等特点,成为关系型数据库的主要选择。虽然备受关注,但MySQL在云原生的浪潮中也面临诸多挑战。如何利用CloudNative的设计原则,通过沙盒隔离,实现计算与数据的完全分离,实现低成本、可扩展、高可用的CloudRDS方案?本文介绍了一种云原生分布式MySQL高可用数据库解决方案(以下简称“SlightShiftMySQL高可用解决方案”),并简要分析了传统数据库在云原生场景下的发展趋势。1.Requirements&Challenges在考虑云原生场景下MySQL的高可用架构时,主要有以下几个方面的挑战:Failover:当主库宕机时,集群能够自动选主,快速转移故障,传输前后数据一致。敏捷弹性伸缩:基于副本的弹性伸缩,伸缩时不中断业务访问。数据安全:数据定时冷备份/实时热备份,实现故障恢复和数据迁移。数据一致性强:作为备份/只读副本的从节点数据应与主节点数据实时或半实时保持一致。2Goals&KeyConsiderationsSlightShiftMySQL高可用解决方案要实现的目标:SLA保证:一年内最多52.56分钟的服务不可用(99.99%)是可以接受的。Failover:当主库异常时,主从切换时间小于2分钟。弹性扩容:从库理论上可以无限扩容,从库扩容不到2分钟。冷备恢复:当MySQL集群出现不可恢复的问题时,冷备恢复时间小于10分钟。除了上述目标外,在设计技术架构时还需要考虑以下关键点:高可用应用访问成本资源占用可扩展性可维护性三架构设计本MySQL高可用方案采用主多从复制结构.主从数据复制采用半同步复制,保证了数据的一致性和读写效率。理论上,从数据库可以无限扩展。在数据引擎上层增加仲裁器,采用Raft分布式共识算法实现自动选主和故障转移。路由层使用ProxySQL作为SQL请求的代理,可以实现读写分离、负载均衡和动态配置检测。在监控告警方面,采用Prometheus-Operator方案实现对整个MySQL高可用系统的资源监控告警。在运维管控方面,引入KubernetesOperator的管控模型,实现DB-Operator,可以实现声明式配置、集群状态管理和OnDemand(按需创建)。此外,MySQL的基本运维都可以在MySQL控制台上进行,如:数据库管理、表管理、SQL查询、索引更改、配置更改、数据备份恢复等四大关键技术诞生后状态持久化容器技术,大家很快发现封装“无状态应用”(StatelessApplication)非常有用。但是如果你想用容器来运行“有状态的应用程序”,难度就会直线上升。对于MySQL等存储型分布式应用,其多个实例之间往往存在依赖关系,如:主从关系、主备关系。每个实例往往会在本地磁盘上保存一段数据。当实例被杀死后,即使重建,实例与数据的对应关系也会丢失,导致应用失败。此类实例之间存在不平等关系且实例对外部数据具有依赖性的应用程序称为“有状态应用程序”。Kubernetes集群中节点本地存储资源的使用方式有多种,例如emptyDir、hostPath、LocalPV。其中emptyDir无法持久化数据,hostPath方法需要手动管理volume的生命周期,给运维带来很大压力。所以在MySQL场景下,为了性能和运维成本需要使用本地存储。本地光伏是目前唯一的选择。本地PV使用机器上的磁盘存储业务需要持久化的数据。与远程存储类似,Pod的数据和生命周期是相互独立的。即使业务Pod被删除,数据也不会丢失。同时,与远程存储相比,本地存储可以避免网络IO开销,具有更高的读写性能。因此,分布式文件系统、分布式数据库等对IO要求高的应用非常适合本地存储。与其他类型的存储不同,LocalPV本地存储对节点的依赖性很强。换句话说,在调度Pod时,还应该考虑这些LocalPV的容量和拓扑域要求。MySQL在使用LocalPV时,主要利用了两个特性:延迟绑定机制和卷拓扑感知调度。延迟绑定机制可以延迟PVC的绑定,直到一个MySQLPod使用它并完成调度,而卷拓扑感知调度可以让Kubernetes调度器知道卷的拓扑约束,即存储卷只能被使用在特定区域或节点上使用(访问),因此调度程序在调度Pod时必须考虑此限制。另外,MySQL使用的LocalPV需要通过nodeAffinity将Pod调度到正确的Node上。下面展示了MySQL使用本地PV的简单配置展示示例:---apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:creationTimestamp:nullname:local-storageprovisioner:kubernetes.io/no-provisionerreclaimPolicy:Delete---apiVersion:v1kind:PersistentVolumeClaimmetadata:annotations:pv.kubernetes.io/bound-by-controller:"yes"creationTimestamp:nulllabels:app:slightshift-mysqlmysql-node:"true"name:data-slightshift-mysql-0spec:accessModes:-ReadWriteOnceresources:requests:storage:10GistorageClassName:local-volume-storagevolumeName:mysqlha-local-pv-0---apiVersion:v1kind:PersistentVolumemetadata:annotations:helm.sh/hook:pre-install,pre-upgradehelm.sh/resource-policy:keepvolume.alpha.kubernetes.io/node-affinity:'{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/hostname","operator":"In"“值”:[“yz2-worker004”]}]}]}}'labels:pv-label:slightshift-mysql-data-pvtype:localname:slightshift-mysql-data-pv-0spec:accessModes:-ReadWriteOncecapacity:storage:500Gilocal:path:/var/lib/ali/mysqlpersistentVolumeReclaimPolicy:RetainstorageClassName:pxc-mysql-data以上是LocalPV的基本用法,而MySQL还需要考虑节点的弹性伸缩,这就要求底层存储也可以随着MySQL实例的伸缩而动态配置。这里有两种解决方案:Kubernetes集群中引入LVMManager,以DaemonSet的形式运行。负责管理各个节点上的磁盘,上报节点磁盘容量和剩余容量,动态创建PV等;然后引入localstoragescheduler调度模块,负责选择使用localstorage的Pod。合适的(有足够容量的)节点。使用开源方案OpenEBS:iSCSI提供底层存储功能,OpenEBS管理iSCSI(目前只支持PV的ReadWriteOnce访问方式)。自动选主SlightShiftMySQL高可用架构基于Raft强共识协议实现了分布式MySQL的自动选主。Raft使用心跳触发leader选举,MySQLServer启动时状态为follower。当服务器收到来自领导者或候选者的有效RPC时,它会保持在跟随者状态,领导者会发送周期性的心跳来表明它是领导者。当follower在选举超时时间内没有收到通信,就会开始选举leader。选择硕士的步骤如下:添加当前学期。更改为候选人状态。选择自己作为master,然后将masterRPC并行发送给其他服务器。候选状态将持续保持,直到出现以下三种情况。候选人会在以下三种情况下退出:服务器自己成为领导者。其他服务器被选为领导者。一段时间后,没有服务器成为领导者。故障转移在正常运行的主从复制环境中,故障转移(Failover)模块会监控集群状态,当Master发生故障时会自动触发故障转移。故障转移的第一步是自动选择主节点。上面已经介绍了自动选主的逻辑;二是数据一致性保证,需要最大限度的将DeadMaster数据同步到NewMaster。在Master切换为NewMaster之前,部分slave可能还没有收到最新的relaylog事件,failover模块会自动识别来自最新slave的不同relaylog事件,并将不同的事件应用到其他slave上,从而以保证所有slave的数据一致。对于代理层,ProxySQL会实时检测MySQL实例的读写配置。当MySQL实例的读写配置发生变化时,ProxySQL会自动调整MySQL实例的读写组配置,最终保证Failover运行后读写分离正确。SlightShiftMySQL自动故障转移步骤如下:HA-Manager检测到MasterServer连接异常,启动Failover。尝试关闭DeadMaster以避免脑裂(此步骤是可选的)。获取最新数据的slave的end_log_pos,同步死掉的master的bin-log,最后保证所有slave的end_log_pos一致。使用raft分布式算法选举NewMaster。将Master切换为NewMaster。回调ProxySQL代码服务,去除DeadMaster配置。ProxySQL网关检测每个MySQL实例的读写配置变化,调整读写分离配置。通知切换结果(邮箱、钉钉群机器人)。SlightShiftMySQL可以实现秒级故障转移,5-10秒检测主机故障,5-10秒应用差分中继日志,然后注册到新的master,通常10-30秒完全宕机。另外可以在配置文件中配置每个被选为NewMaster的slave的优先级,这在多机房部署MySQL的场景下非常实用。当宕机的Master节点故障自动恢复时:如果恢复的节点重新创建MysqlPod并加入集群,Sentinel会将新的Pod配置为Slave,通过获取日志文件和实现新加入的Slave的数据同步现任MysqlMaster职位。如果被恢复的节点中的MysqlPod还存在并且可用,此时Sentinel会找到2个Label为master的Pod。Sentinel在宕机期间仍会使用新选出的Master,并强制恢复的Master为Slave,并开启只读模式。当宕机的Slave节点恢复时:如果恢复节点重新创建MysqlPod并加入集群,Sentinel会将新的Pod配置为Slave,通过获取当前的日志文件和Position实现新加入的Slave的数据同步掌握Mysql。如果恢复节点中的MysqlPod仍然存在并且可用,调度器将不会执行该操作,proxysql-service会监听新添加的Slave并将其添加到endpoints列表中。状态管理状态管理并不是一个新话题。它为中心化系统分配一致的状态,并确保分布式系统始终向预期状态收敛。它是中心化系统的基石之一。MySQL服务由一个Master节点和多个从Master异步复制数据的Slave节点组成,即master-multiple-slave复制模型。其中Master节点可以用来处理用户的读写请求,Slave节点只能用来处理用户的读请求。为了部署这样的有状态服务,除了StatefulSet之外,还需要用到很多其他类型的k8s资源对象,包括ConfigMap、HeadlessService、ClusterIPService等,正是它们的配合,才让像MySQL这样的有状态服务可以运行在上面k8s有条件地。在MySQL集群中,状态转移最常发生在master发生故障并且集群发生故障转移时。当Master发生故障宕机时,会自动触发选主逻辑。master选择完成后,会进行failover,直到NewMaster正常提供读写服务。故障转移过程需要时间。在故障转移过程中,MySQL服务会处于不可用状态。写入状态。故障转移时,死掉的master实例会被k8s集群自动拉起。死掉的master被拉上去之后,它会认为自己是一个合法的master。这样会导致集群中同时存在两个master,写请求很可能会随机分配给两个Master节点,造成脑裂问题。为了解决这种场景下的脑裂问题,我们引入了InitContainer机制,用Sentinel可以很好的解决。InitContainer,顾名思义,会在容器启动时启动一个或多个容器,完成一些前期工作。如果有多个,InitContainer将按照指定的顺序执行。只有所有的InitContainer都执行完了,才会执行主容器。将开始。我们已将InitContainer添加到每个MySQL实例。DeadMaster被k8s自动拉起后,InitContainer会自动检测集群是否处于Failover阶段。如果处于Failover阶段,则进入Sleeppolling状态,直到Failover结束。当Failover结束后,Sentinel检测到DeadMaster被拉起,会自动将DeadMaster设置为NewMaster的Slave节点,从而完成一个完整的Failover过程,避免集群出现脑裂问题.声明式运维我们在使用k8s的时候,一般会通过k8s的资源来满足应用管理的需求:通过Deployment、StatefulSet等工作负载来部署服务。通过Service、Ingress管理对服务的访问。通过ConfigMap和Secrets管理服务配置。etc.上面提到的Resource代表了User的期望,kube-controller-mananger中的Controller会监听ResourceEvents并执行相应的动作来实现User的期望。这种操作方式给应用管理带来了极大的方便。用户可以以声明的方式管理应用程序,而不必担心如何使用传统的HTTPAPI、RPC调用等。Controller会使用各种机制来保证用户期望的实现,比如持续检测对象的状态驱动对象的当前状态以满足用户的期望。这种运维方式称为声明式运维。但是,如果只使用k8s提供的基本类型,对于MySQL等复杂应用来说,运维成本还是很高的。如果能够将声明式运维模型扩展到MySQL应用中,将大大降低MySQL应用的部署和运维成本。为了解决这个问题,smileshift-mysql-operator应运而生。smileshift-mysql-operator本质上是Resource+Controller:ResourceCustomResource(CRD,CustomResourceDefinitions),为User提供一种声明式的方式来描述对服务的期望。控制器在资源中实现用户的期望。smileshift-mysql-operator通过结合k8s中已有的概念,大大降低了部署和运行MySQL的成本:用户描述对MySQL的需求,类似使用Deployment在k8s上部署MySQL应用的姿势,k8s官方资源中运行同样的方式。slightshift-mysql-operator的Controller监控和处理事件请求,监听ResourceEvents。对不同类型的事件(ADD/UPDATE/DELETE)执行相应的操作。持续检测对象的状态以执行操作,并驱动服务的状态以满足用户的期望。smileshift-mysql-operator的设计理念:声明式配置:提高可读性和运维效率,降低运维成本。最终一致性:动态调整集群状态,实现最终一致性。smileshift-mysql-operator大大降低了在Kubernetes集群中使用和管理MySQL应用程序的成本。用户可以通过声明式CR创建应用程序。Vendor可以封装管理应用的专业知识,对用户透明。smileshift-mysql-operator在架构层面采用了分层设计,简化了架构的复杂度,同时尽可能减少了多个MySQL集群实例之间的相互干扰,实现了各个实例的自治。部署结构如下图所示:备份与恢复为了保证数据安全,需要定期对数据进行备份,以保证在极端情况下(集群崩溃)用户数据能够恢复。通过Cronjob备份Mysql数据:在Kubernetes集群中创建Cronjob,Cronjob会定时进行数据备份。备份数据会被打上时间戳并上传到对象存储服务器(Ceph、Minio)。通过创建作业恢复Mysql数据:在集群中创建一个恢复作业,该作业会根据SNAP_SHOT从存储服务器(OSS、Minio)获取备份数据。Job将数据恢复到MysqlMaster实例,Slave完成数据同步。五、技术进化只有进化,我们才能站在食物链的顶端。根据Gartner的报告,未来五年数据库云平台的市场份额将翻一番,70%的用户将开始使用dbPaaS数据库云平台。因此,为满足数据库云平台上各种应用的需求,降低私有云部署中大量不同类型数据存储产品的运维复杂度,数据库架构的演进将是一个方向。未来十年数据库转型的主要方向之一。云原生数据库是未来数据库发展的重要方向。云原生数据库架构也需要不断迭代,以满足云化的需求。未来,云原生数据库架构的演进将随着需求的变化而不断发展。6未来展望中间件作为构建上层业务系统的基石和核心技术,具有高可靠性、高扩展性、专业性强等特点。展望未来,由于中间件的这些特点,未来云原生中间件将朝着标准化、架构化、松散耦合、平台化的方向发展。平台化可以更快地响应业务变化,为业务的横向发展提供集中的技术解决方案。从最终状态来看,我们要打造一个企业级的云原生中间件PaaS平台,涵盖基础的中间件部署、配置、升级、伸缩、故障处理等自动化能力。先说说我对中间件平台的理解。中间件平台应该是一套中间件解决方案。它形成了一个完整的、经过验证的、开放的、组件化的解决方案,旨在为复杂多变的上层业务领域提供稳定可靠的计算和存储服务。任何平台化产品的开发过程,不是一开始就做平台,而是先有业务需求,解决实际业务需求,积累某些领域的知识,然后成为这些领域的专家,最后平台转移。也就是说,平台是一个不断积累的过程,是一个自然的过程。在企业信息化平台建设过程中,有效、合理、规范地利用中间件平台快速搭建上层业务系统,为企业及时响应需求变化提供有力保障,形成企业集约化经营,进一步增强企业核心实力。持续的竞争优势。【本文为专栏作者《阿里巴巴官方技术》原创稿件,转载请联系原作者】点此查看作者更多好文