本文根据DBAplus社群第142期线上分享整理而成
本次分享即对表格存储负载均衡技术做一些总结,以便跟大家一起探讨分布式系统中负载均衡的问题和思路。
下面会先对表格存储做简单的介绍,以便更好的讨论我们碰到的问题。然后会介绍多租户的概念,并说明单机多租户和分布式系统多租户的不同。最后重点介绍分布式系统中多租户负载均衡的核心问题
一、表格存储概览
先来看看为什么要做表格存储。就像10年前谷歌BigTable论文里面描述的一样,新时代数据有一些明显的特征:
数据量大、读写量大、增长速度很难预计。关于增长速度,比如答题,一天内访问量就可能上涨几十倍,不差钱,就看你能不能搞定。
数据之间关系很弱。比如对邮箱应用来说,不同用户的邮件记录之间完全没有关系,无论是收发还是搜索,你都只能在自己的邮箱数据内进行。
业务变动频繁,schema也需要跟着频繁变动。
受传统数据库约束,这三个需求都没有得到很好地解决。第一是扩展性,比如你跟DBA说业务明天要扩大10倍,估计DBA得头痛一下。他要给你准备好资源,分库分表,甚至需要业务逻辑也随之改动,很麻烦。
第二个是可用性。传统单机数据库一般是主备,有强同步、弱同步等选择,看起来给应用很多的选择权,其实选哪个你都觉得不爽,因为你想的是既要、又要、还要……
第三点是灵活性。这也很好理解,比如数据库里面有几十亿条数据,业务方跟DBA说我要加一列,看看DBA的脸色你就知道了,DBA是不喜欢这种需求的,一般来说业务方会预留一些空白字段来避开这种需求。
正因为需求真实的存在,已有的数据库没有很好的解决,所以很多新的数据库就出来了,NoSQL是其中一个思路,是传统SQL数据库的一个很好的补充,所以我认为应该解释为Not Only SQL。未来将迎来数据库百花齐放的几年,数据库将和行业更紧密的结合,拭目以待。
这里以列表的方式整理了表格存储的特征,大家了解一下就好,里面唯一需要强调的是规模,因为它跟分享主题负载均衡有直接的关系。
表格存储支持单表千万读写,数十P数据量,只要运维将机器放入集群,扩容在分钟级别就可以完成。扩容完成后就面临着负载均衡的问题,因为老机器负载高、新机器负载低,负载均衡算法会去削峰填谷,让不同集群节点保持接近的利用率。
这是表格存储的架构。
可以看到下面有一些基础组件是被所有角色共享的,比如Nuwa提供分布式锁服务,日志模块主要做日志的收集保存分析,而安全体系、机房管理等都是所有产品共享的。
我们要关注的主要是盘古和表格存储引擎。盘古是阿里云自研的分布式文件系统,可以类比HDFS,当然功能性能上做了很多增强。
表格存储引擎层包括master、负载均衡和若干个worker,是典型的分布式系统架构。其中master管理表meta信息,比如表名、分区个数、压缩算法、缓存策略等,并为每个分区分配合适的worker去加载。worker就是干活的,负责加载分区并执行请求。负载均衡依赖master和worker的统计信息来干活,细节后面会讲。
这是表格存储的数据模型,只关注一点就可以了,就是分区机制,后面的内容会依赖这个。
几乎所有的分布式系统都有分区的概念,名字虽然各不相同(partition,shard,segment,group),意思大体一样,就是分而治之,把规模拆分成单机可以处理的水平,然后让单机承载相应的功能。
数据库类基本的分区机制有两种,一种是hash,一种是range。表格存储是按照range进行分区的,就是表按照PK排好序,然后切分成一个个的分区。
举个例子,表PK是字符串,从AA到FF,我们可以将其分成二个分区,[AA-CB),[CB-FF),这里的一个分区就可以理解为传统数据库的一个实例。
表格存储负载均衡依赖一些关键能力。首先表格存储使用分布式文件系统作为共享存储,这使得任何一个worker都可以随时访问任意数据,这也就带来了第二点提到的优势,即想将某个分区从一个worker迁移到另外的worker要比传统分库分表容易很多,只要调度上做一个简单的调整就可以,不需要搬动数据,可以非常快的完成。
这些能力对表格存储的负载均衡非常重要,当然做好也不容易,但是不是今天的重点,略过不提。
二、多租户负载均衡
上面我们介绍了表格存储基本概念,有了这些铺垫,下面介绍负载均衡时候会容易一些。
如图,让我们看看多租户负载均衡的一些信息。负载均衡是表格存储系统的一部分,可以按照每类资源进行细粒度的调度,允许不同类资源以合理的方式进行组合。
这里我们将租户定义为共享资源的逻辑单位,每个这样的逻辑单位都会消耗CPU、内存、网络带宽、磁盘IOPS。这个逻辑单位,也就是租户,跟表格存储里面的分区对应,后文中根据上下文不同租户和分区都会使用,概念上等同。
接下来谈谈单机系统和分布式系统负载均衡的不同。单机系统因为不可扩展,所以其负载均衡更多的是对请求优先级进行排序,如果资源用满,就拒绝一些请求。
分布式系统提供了一个额外的选项,就是通过将服务实例进行迁移来对请求进行迁移,这样就等于利用额外的资源服务用户,能够提高客户体验,迁移的过程就是分区调度的过程。请求能否迁移是分布式系统和单机系统负载均衡的最大不同。
这张图讲的是多租户负载均衡的好处以及我们认为负载均衡终态应该长什么样子。
全自动负载均衡能提供的好处是很明显的,首先就是保证业务平稳运行,我们系统上经常有些业务流量猛增猛降,比如某个突发事件访问量很快上涨十倍,此时负载均衡必须能在十秒级别响应并尽快解决问题,避免系统阻碍业务的发展。
如果负载均衡做的不好,就要在每台机器上为这种可能突发的业务预留资源,而且各个机器上的资源无法被共享,那么当客户众多的时候,这种预留是一个巨大的浪费。
另外一个常见做法就是业务通知运维扩容缩容,这无疑给运维带来了极大的压力,据一份统计数据,系统50%以上的故障都是人的失误导致的,这种事情做多了运维背锅几乎没法避免。
最后,负载均衡做好了,能够在全集群范围内平衡资源的使用,可以降低成本,也可以利用更多的资源改善服务水平。
上面说到了负载均衡系统的价值,那么负载均衡的终态就是能实现这些价值,比如这里列的几条具体指标:
能够在确定时间内解决用户业务暴涨问题;
对于那些表结构不合理的用户,比如只写尾部、量又特别大的,这时候系统也很难解决,只能尽快隔离避免影响其他用户。
负载均衡的终极目标就是跟服务SLA形成闭环,此时负载均衡就成为一个自我进化的系统,该系统的目标就是不断逼近系统SLA的极限。
前面说了理想目标,这里就介绍一下我们不同阶段的做法。
上图看到的是最土的负载均衡,是从0到1,完全依赖人肉,且粒度是机器级别。对那些大业务而言,为了避免别人影响他们或者他们影响别人,可以由运维手动指定几台机器给他们。
而对大多数业务而言,他们没有大到这种程度,只能跟众多中小业务一起自生自灭,如果有个小业务突然放量,那么跟这个小业务共享机器的其他业务就遭殃了,此时服务SLA几乎完全依赖运气。对于有情怀的技术人员而言,我们显然不能容忍小用户被轻视,必须一视同仁。
这里列出了我们新版负载均衡系统支持的主要动作,我们摒弃了业务大小的概念,以分区作为系统的调度基本单位。大业务可以分割成多个分区,小业务可以只有一个分区,这些分区是平等的。
左上角是系统的初始状态,可以看到每个worker上都加载了同样个数的分区,这是因为系统启动初始阶段没有负载信息来辅助判断,所以此时的worker跟分区的关系可以认为是随机分配的。
随着业务的上线,可能发生如下3种情况。第一是客户业务突然上涨,此时该客户的数据表可能只有一个分区(刚开始建表默认只有一个分区),该分区即使将某台机器资源全部吃掉也无法服务这么多请求,系统识别这种情况后会迅速将该分区分裂开来,变成两个分区,并再挑选一台机器,让他们共同服务该客户请求。
第二种情况是,某机器上不止一个分区业务上涨,但是并没有哪个分区的业务量能够吃掉一台机器的全部资源,只是繁忙分区多导致机器资源紧张而已,此时需要挑选部分业务上涨的分区移动到其他较空闲机器加载。
还有第三种情况,就是系统通过对用户访问模式的判断,认为已有的策略都无法解决当前问题,就会将该分区移动到隔离机器上,避免产生更大的影响,并报警运维处理。
目前看,线上情况第一种是主流,第三种很少,有些数据库新手可能会设计出不合理的表结构,比如以时间为pk。
接下来继续讲讲做到前面说的效果需要解决的问题,主要是这几条。
第一条是量化请求处理的资源使用。资源主要是网络、内存、CPU和IO。在一个复杂的数据库系统里面精确量化每条请求对每类资源的占用几乎不可能,或者说也没有必要,我们可以针对不同的资源做一些折中,比如对CPU,可以用请求延时减掉IO延时近似模拟,网络和内存则相对容易统计。关于实时统计所有租户的资源占用问题,后面会讲到。
第二个问题是机器水位和公平流控,公平是个不太好定义的问题,在我们的负载均衡体系里,那些资源消耗增长最快的分区需要被更严格的控制。负载均衡的触发时机和衡量标准跟系统的实现方式有关,后面也会介绍。最后也会说一下系统闭环。
请求资源量化和统计
先看看请求资源的量化和统计,这是整个系统的基石。
系统基本的读写请求,我们会统计数据大小,延时和内存等,其中请求大小可以用来评估入口网络带宽占用,响应大小可以用来评估出口网络带宽占用,延时可以用来评估CPU占用,内存就很直白了。
每个请求执行过程中都会尽可能的收集这些视图信息,在请求执行结束的时候,视图交给视图统计模块的环形队列,然后就直接发送响应。视图统计模块会异步的统计各个租户对各个资源的占用,每隔一个周期,视图统计模块就会输出一个类似右下角的表格,里面包含每个分区对每种资源的使用情况。
视图统计框架被设计成只需要数十行代码就可以对一种新的视图添加统计信息,我们也强制要求任何模块在设计阶段就要考虑视图,这使得我们能较容易的在系统运行时拿到系统内部的详细信息。
水位和公平流控
上图以实际的例子来说明前面统计的信息是如何被使用的。这个是读请求,资源则是以网络出口资源为例,基本思想是令牌桶。
在任何一个worker上,我们都会有一个资源监控线程,该线程从操作系统获得某种资源使用的水位,比如这个例子里面的网络出口流量,如果太高,就会触发流控。
流控的基本流程见右上方流程列表,开始处理请求后,首先判断资源是否过于繁忙,比如网络流量超过网卡带宽的95%,此时任何数据请求都不再处理;否则就正常申请资源。申请成功则继续处理请求,申请失败则表示流量不够了,此时就要拒绝一部分请求,但是拒绝谁呢?就是根据前面视图统计模块给出的二维表,谁的流量大被拒绝的概率也大,这样就可以保护小流量租户。
从例子可以看出,400M大户被拒绝的概率是49%,注意这里还有一个R,这个意思就是资源繁忙的程度,比如网络流量超过80%认为繁忙,当前如果流量占用达到90%,则(90%-80%)/90%=11%,从网络出口角度看,11%的请求应该被拒绝掉,由此可以算出租户1的请求中49%*11%=5.4%的请求应该被拒绝掉。
上面说的大用户不是说请求多就是大用户,而是某类资源占用多,比如CPU用的多的租户可能网络资源用的少,那么这个用户只能叫做CPU大户,不能叫做网络大户。
根据上面的计算,某些请求要被拒绝了,这也是负载均衡系统开始工作的时候。因为任何一台机器发生流控都意味着请求错误,影响服务可用性,必须尽快启动负载均衡以缩短流控发生的时间。负载均衡的任务就是寻找合适的机器服务这些被拒绝掉的请求。
触发时机
上图以网络出口带宽占用为具体例子看负载均衡的操作。右上的表格,是两个租户都用了较多的出口带宽导致出口带宽资源紧张,所以此时可以随便选一个分区移动到其他机器。
而右下的这个表,是一个租户占用了特别多的资源,此时无论移动到哪台机器上都无法解决问题,所以就需要将其分裂,分成2个分区后使得每个子分区占用的出口带宽尽可能的平均,然后就可以分别调度到两台机器了。
分裂点的选择
上面的PPT留下了一个疑问,就是在分裂时候如何保证分出来的两个分区尽可能的均匀呢?
从图上这个例子来看,如果只是简单的从分区range中间切一刀,比如[0, 100)变为[0, 50), [50, 100),还是解决不了问题,因为流量都在左边。
我们的解决办法是在每个租户内统计资源消耗的时候考虑权重,这样能保证切割后两边的流量比较均匀,才真正达到了负载均衡的目标。这个统计的内容会稍多一些,但是因为发生在背景线程中,不会增加请求延时,且最多占用1个CPU核,所以对系统影响还是可控的。
SLA闭环联动
前面几张图以读请求对出口网络流量的占用为示例对负载均衡基本工作流程进行了介绍,这里再对负载均衡的两个关键问题,即触发时机和评估标准做个总结。
整体来看,负载均衡的触发时机一是某台机器发生了流控,二是租户SLA低于指定的阈值,三是资源利用不均衡,这三个问题的解决难度是逐步增加的。尤其是第三个,因为资源有多种,让各个资源利用率都取得均衡是很困难的事情。
下面评估标准里面也比较直白,就是有没有解决运维问题和业务问题,服务可用性有没有提高,资源利用率有没有提高,这些都是可以量化的。
如果把负载均衡的触发和评估体系连接起来,那也就意味着我们建立了负载均衡闭环,这个闭环将可以利用其中的规则自动的优化系统,从而使得服务可用性不断的逼近极限。
三、总结
最后对本文做个小结。我们讨论了负载均衡的重要性以及实现挑战,讨论了单机负载均衡和分布式负载均衡的不同,也以表格存储为例分享了分布式系统中负载均衡的实践经验。我们也从自己的角度提出了分布式负载均衡的终态,这点希望跟大家多多交流。
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721