Cloud + TiDB 技术解读

阅读:446 2019-03-19 14:44:32 来源:新网

作为一款定位在cloud-native的数据库,现如今tidb在云整合上已取得了阶段性的进展。日前cloudtidb产品在ucloud平台正式开启公测,tidb弹性伸缩的特性在cloud提供的基础设施支持下发挥的淋漓尽致。在感受云数据库魅力的同时,让我们来一探究竟,看一下tidb与cloud背后的技术秘密。

首先还是要先从tidb的架构说起,tidb和传统的单机关系型数据库有什么不同?相信长期以来一直关注tidb的同学都比较了解了,但这里还是科普一下。tidb作为一个开源的分布式数据库产品,具有多副本强一致性的同时能够根据业务需求非常方便的进行弹性伸缩,并且扩缩容期间对上层业务无感知。tidb的主体架构包含三个模块,对应github上面pingcap组织下的三个开源项目,tidb/tikv/pd:

上面的这三个模块,每个角色都是一个多节点组成的集群,所以最终tidb的架构看起来是这样的。

由此可见,分布式系统本身的复杂性导致手工部署和运维的成本是比较高的,并且容易出错。传统的自动化部署运维工具如puppet/chef/saltstack/ansible等,由于缺乏状态管理,在节点出现问题时不能及时自动完成故障转移,需要运维人员人工干预。有些则需要写大量的dsl甚至与shell脚本一起混合使用,可移植性较差,维护成本比较高。

在云时代,容器成为应用分发部署的基本单位,而谷歌基于内部使用数十年的容器编排系统borg经验推出的开源容器编排系统kubernetes成为当前容器编排技术的主流。作为cloudnativedatabase,tidb选择拥抱容器技术,并与kubernetes进行深度整合,使其可以非常方便地基于kubernetes完成数据库的自动化管理。

kubernetes项目可以说是为cloud而生,利用云平台的iaas层提供的api可以很方便的和云进行整合。这样我们要做的事情就很明确了,只要让tidb与kubernetes结合的更好,进而就实现了和各个云平台的整合,使得tidb在云上的快速部署和高效运维成为现实。

kubernetes最早是作为一个纯粹的容器编排系统而诞生的,用户部署好kubernetes集群之后,直接使用其内置的各种功能部署应用服务。由于这个paas平台使用起来非常便利,吸引了很多用户,不同用户也提出了各种不同的需求,有些特性需求kubernetes直接在其核心代码里面实现了,但是有些特性并不适合合并到主干分支,为满足这类需求,kubernetes开放出一些api供用户自己扩展,实现自己的需求。当前kubernetes已经发展到v1.8,其内部的api变得越来越开放,使其更像是一个跑在云上的操作系统。用户可以把它当作一套云的sdk或framework来使用,而且可以很方便地开发组件来扩展满足自己的业务需求。对有状态服务的支持就是一个很有代表性的例子。

kubernetes项目最早期只支持无状态服务(statelessservice)来管理的,无状态服务通过replicationcontroller定义多个副本,由kubernetes调度器来决定在不同节点上启动多个pod,实现负载均衡和故障转移。对于无状态服务,多个副本对应的pod是等价的,所以在节点出现故障时,在新节点上启动一个pod与失效的pod是等价的,不会涉及状态迁移问题,因而管理非常简单。但是对于有状态服务(statefulservice),由于需要将数据持久化到磁盘,使得不同pod之间不能再认为成等价,也就不能再像无状态服务那样随意进行调度迁移。kubernetesv1.3版本提出petset的概念用来管理有状态服务并于v1.5将其更名为statefulset。statefulset明确定义一组pod中每个的身份,启动和升级都按特定顺序来操作。另外使用持久化卷存储(persistentvolume)来作为存储数据的载体,当节点失效pod需要迁移时,对应的pv也会重新挂载,而pv的底层依托于分布式文件系统,所以pod仍然能访问到之前的数据。同时pod在发生迁移时,其网络身份例如ip地址是会发生变化的,很多分布式系统不能接受这种情况。所以statefulset在迁移pod时可以通过绑定域名的方式来保证pod在集群中网络身份不发生变化。

然而现实中一些分布式系统更为复杂,statefulset也显得捉襟见肘。举例来说,某些分布式系统的节点在加入集群或下线时还需要做些额外的注册和清理操作,或者滚动升级要考量版本兼容性等。基于这个原因coreos公司提出了operator概念,并实现了etcd-operator和prometheus-operator来管理etcd和prometheus这样的复杂分布式系统。用户可以开发自己的operator,在kubernetes之上实现自定义的controller,将有状态服务的领域特定的运维知识编码进去,从而实现对特定分布式系统的管理。同时operator本身也是跑在kubernetes中的一组pod(deployment),对kubernetes系统并无侵入性。

针对tidb这种复杂的分布式服务,我们开发了tidb-operator等一系列组件,来管理tidb集群实例在kubernetes平台上的创建、销毁、扩缩容、滚动升级和故障转移等运维操作。同时在上层封装一个tidb-cloud-manager组件,提供restful接口,实现与云平台的控制台打通。这样也就实现了一个dbaas(数据库即服务)架构的基本形态。

由于tidb对磁盘i/o有比较高的要求,通过pv挂载网络盘性能上会有明显的性能损耗。另外tikv本身维护了数据多副本,这点和分布式文件系统的多副本是有重复的。所以我们要给pod上挂载本地磁盘,并且在kubernetes上面把localpv管理起来,作为一种特定的资源来维护。kubernetes长期以来官方一直没有提供localpv支持,本地存储只支持hostpath和emptydir两种方式。其中hostpath的生命周期是脱离kubernetes管理的,使用hostpath的pod销毁后,里面的数据是不会被自动清理,下次再挂载pod就会造成脏数据。而emptydir更像一个临时磁盘,在pod重建时会被清理重置,不能成为持久化pv来使用。为此我们开发了一个tidb-volume-manager组件,用于管理kubernetes集群中每台物理主机上的本地磁盘,并且将其暴露成一种特殊的pv资源。结合operator在部署tidb节点时会参考localpv资源的情况来选择特定的节点来部署,分配一个空的localpv和pod绑定。而当pod销毁时候会根据具体情况来决定是否结束localpv的生命周期,释放掉的localpv再经历一个gc周期后,被tidb-volume-manager回收,清理其盘上数据等待再次被分配使用。

将这些组件整合起来,就形成了上图描述了cloudtidb的总体架构,在kubenetes管理的集群之上通过tidb-operator等组件来针对性的调配和使用集群资源,从而实现tidb集群实例的生命周期管理。通过这种方式,来实现tidb分布式数据库和云平台的整合。接下来,我们再针对cloudtidb的关键特性和实现细节分别进行解读。

数据库产品上云的一个先决条件是能实现自动化的运维管理,否则在云上靠手工运维几乎是不现实的。我们首先用kubernetes将云平台的主机资源管理起来,组成一个大的资源池。然后再通过tidb-opeartor及tidb-cloud-manager等组件来自动化完成tidb实例的一键部署、扩容缩容、在线滚动升级、自动故障转移等运维操作。

首先拿集群创建来说。前面提到过,tidb包含三大核心组件:tidb/tikv/pd,每个服务又都是一个多节点的分布式结构。服务和服务之间的启动顺序也存在依赖关系。此外,pd节点的创建和加入集群方式和etcd类似,是需要先创建一个单节点的initial集群,后面加入的节点需要用特殊的join方式,启动命令上都有差别。有一些操作完成后还需要调用api进行通知。kubernetes自身提供的statefulset是很难应付这种复杂的部署,所以需要tidb-operator中实现特定的controller来完成这样一系列的操作。并且结合kubernetese强大的调度功能,合理的规划和分配整个集群资源,尽量让新部署的tidb实例节点在集群中均匀分布,最终通过lb暴露给对应的租户使用。

在线升级也是类似。由于tikv/pd的pod挂载的是本地存储,并不能像云平台提供的块存储或网络文件系统那样可以随意挂载。如果tikv/pd迁移到其它节点,相当于数据目录也被清空,所以必须保证tikv/pd的pod在升级完成后仍然能够调度在原地,这也是要由tidb-operator的controller来保证。tidb的数据副本之间由raft算法来保证一致性,因此当集群中某一个节点暂时断开可以不影响整个服务的。所以在集群升级的过程中,必须严格按照服务的依赖关系,再依次对pod进行升级。

当节点出现故障时,同样是由于挂载本地数据盘的原因,也不能像statefulset那样直接把pod迁移走。当tidboperator检测到节点失效,首先要等一定的时间确认节点不会再恢复了,开始迁移恢复的操作。首先调度选择一个新节点启动一个pod,然后通知tidb将失效的节点放弃掉,并将新启的pod加入集群。后面会由tidb的pd模块来完成数据副本数的恢复,以及数据往新节点上进行搬移,从而重新维持集群内数据平衡。

以上只是列举了tidb几种典型的运维操作流程,实际生产上运维还有很多case需要考虑,这些都以程序的方式实现在tidb-operator里面。借助kubernetes和tidb-operator来代替人工,高效的完成tidb数据库在云平台上的复杂运维管理。

弹性水平伸缩是tidb数据库最主要的特性之一。在大数据时代,人们对数据存储的需求在快速膨胀。有时候用户很难预估自己的业务规模的增长速度,如果采用传统的存储方案,可能很快发现存储容量达到了瓶颈,然后不得不停机来做迁移和完成扩容。如果使用cloudtidb的方案,这个过程就非常简单,只需要在cloud控制台上修改一下tidb的节点数量,很快就能完成扩容操作,期间还不会影响业务的正常服务。

那么在cloud后台,同样借助kubernetes和tidb-operator的能力来完成tidb增减节点操作。kubernetes本身的运作是基于一种reconcile的机制。简单来说当用户提交一个新的请求,比如期望集群里面跑5个tikv节点,而目前正在跑的只有3个,那么reconcile机制就会发现这个差异,首先由kubernetes的调度器根据集群整体资源情况,并结合tidb节点分配的亲和性原则和资源隔离原则来分配节点。另外很重要一点就是选择有空闲localpv的机器来创建pod并进行挂载。最终通过tidb-operator将2个节点加入tidb集群。

对于缩容的过程也是类似。假如数据库存储的总数据量变少,需要减少节点以节省成本。首先用户通过云控制台向后端提交请求,在一个reconciling周期内发现差异,tidb-operator的controller开始通知tidb集群执行节点下线的操作。安全下线可能是个比较长的过程,因为期间需要由pd模块将下线节点的数据搬移到其他节点,期间集群都可以正常服务。当下线完成,这些tikv变成tombstone状态。而tidb-operator也会通知kubernetes销毁这些pod,并且由tidb-volume-manager来回收localpv。

资源隔离也是云上用户关心的一个问题。尤其是数据库这类应用,不同租户的数据库实例,甚至一个租户的多套数据库实例,都跑在一套大的kubernetes管理的集群上,相互间会不会有资源的争抢问题,某个实例执行高负载的计算任务时,cpu、内存、i/o等会不会对同台机器上部署的其他实例产生影响。其实容器本身就是资源隔离的一个解决方案,容器的底层是linux内核提供的cgroups技术,用于限制容器内的cpu、内存以及io等资源的使用,并通过namespace技术实现隔离。而kubernetes作为容器编排系统,能够根据集群中各个节点的资源状况,选择最优的策略来调度容器。同时tidb-operator会根据tidb自身的特性和约束,来综合决策tidb节点的调度分配。举例来说,当一个kubernetes集群横跨多个可用区,用户申请创建一个tidb集群,那么首先根据高可用性原则,将存储节点尽量分配到不同的可用区,并给tikv打上label。那么同一个可用区内也尽量不把多个tikv部署到相同的物理节点上,以保证集群资源最大化利用。此外,每个localpv也是一块独立的磁盘,每个tikv的pod分别挂载不同的盘,所以i/o上也是完全隔离的。kubernetes还可以配置pod之间的亲和性(affinity)和反亲和性(anti-affinity),例如tikv和tidb之间我们可以通过亲和性使其调度到网络延时较小的节点之上,提高网络传输效率,tikv之间借助反亲和性,使其分散部署到不同的主机、机架和可用区上,降低因硬件或机房故障造成的丢数据的风险。

上面解释了容器层面的隔离,可以看作是物理层面的隔离。那么数据层面的隔离,tidb的调度体系也是有所考虑的。比如一个大的tidb集群,节点分布在很多台主机,跨越多个机架、可用区。那么用户可以定义namespace,这是一个逻辑概念,不同业务的数据库和表放置在不同的namespace。再通过配置namespace和tikv节点以及区域的对应关系,由pd模块来进行调度,从而实现不同业务的数据在物理上的隔离。

tidb作为一个分布式数据库本身就具有高可用性,每个核心组件都可以独立的扩缩容,任意一个模块在部署多份副本时如果有一个挂掉,整体仍然可以正常对外提供服务,这是由raft协议保证的。但是如果对数据库节点的调度不加任何限制,包含一份数据的多个副本的节点可能会被调度到同一台主机。这时如果主机发生故障,就会同时失去多个副本,一个raft分组内在失去多数派节点就会使整个集群处于不可用的状态。因此tidb-operator在调度tikv节点时需要避免出现这种情况。

另外tidb支持基于label的数据调度的,给不同的tikv实例加上描述物理信息的label,例如地域(region)、可用区(az)、机架(rack)、主机(host),这样pd在对数据进行调度时就会参考这些信息更加智能的制定调度策略,尽最大可能保证数据的可用性。例如pd会基于label信息尽量把相同数据的副本分散调度到不同的主机、机架、可用区、地域上,这样在物理节点挂掉或机架掉电或机房出故障时,其它地方仍然有该数据足够的副本数。借助tidb-operator中controller-manager组件我们可以自动给tikv实例加上物理拓扑位置标签,充分发挥pd对数据的智能调度能力,实现数据层面的高可用性。

同时我们还可以实现实例级别的高可用性,通过kubernetes强大的调度规则和我们扩展的调度器,我们按优先级会尽量选择让tikv部署到不同的主机、机架和可用区上,把因主机、机架、机房出问题造成的影响降到最低,使数据具有最大的高可用性。另外运行在kubernetes之上我们能实时监测到tidb各组件的运行情况,当出现问题时,我们也能第一时间让tidb-operator对集群进行自动修复(self-healing)。具体表现为tidb/tikv/pd实例出现故障时,执行安全的下线操作。同时增加新的实例,来保证集群的规模和之前一致。

相关文章
{{ v.title }}
{{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
你可能感兴趣
推荐阅读 更多>
推荐商标

{{ v.name }}

{{ v.cls }}类

立即购买 联系客服