一、写在前面
上篇文章《亿流量大考(1):日增上亿数据,把MySQL直接搞宕机了...》,聊了一下商家数据平台第一个阶段的架构演进。通过离线与实时计算链路的拆分,离线计算的增量计算优化,实时计算的滑动时间窗口计算引擎,分库分表 + 读写分离,等各种技术手段,支撑住了百亿量级的数据量的存储与计算。
我们先来回看一下当时的那个架构图,然后继续聊聊这套架构在面对高并发、高可用、高性能等各种技术挑战下,应该如何继续演进。
二、active-standby高可用架构
大家看看上面的那个架构图,有没有发现里面有一个比较致命的问题?就是如何避免系统单点故障!
在最初的部署架构下,因为数据平台系统对CPU、内存、磁盘的要求很高,所以我们是单机部署在一台较高配置的虚拟机上的,16核CPU、64G内存、SSD固态硬盘。这个机器的配置是可以保证数据平台系统在高负载之下正常运行的。
但是如果仅仅是单机部署数据平台系统的话,会导致致命的单点故障问题,也就是如果单台机器上部署的数据平台系统宕机的话,就会立马导致整套系统崩溃。
因此在初期的阶段,我们对数据平台实现了active-standby的高可用架构,也就是一共部署在两台机器上,但是同一时间只有一台机器是会运行的,但是另外一台机器是备用的。处于active状态的系统会将滑动窗口计算引擎的计算状态和结果写入zookeeper中,作为元数据存储起来。
关于元数据基于zookeeper来存储,我们是充分参考了开源的Storm流式计算引擎的架构实现,因为Storm作为一个非常优秀的分布式流式计算系统,同样需要高并发的读写大量的计算中间状态和数据,他就是基于zookeeper来进行存储的。
本身zookeeper的读写性能非常的高,而且zookeeper集群自身就可以做到非常高的可用性,同时还提供了大量的分布式系统需要的功能支持,包括分布式锁、分布式协调、master选举、主备切换等等。
因此基于zookeeper我们实现了active-standby的主备自动切换,如果active节点宕机,那么standby节点感知到,会自动切花为active,同时自动读取他们共享的一个计算引擎的中间状态,然后继续恢复之前的计算。
大家看下面的图,一起感受一下。
在完成上述的active-standby架构之后,肯定是消除掉了系统的单点故障了,保证了基本的可用性。而且在实际的线上生产环境中表现还不错,一年系统总有个几次会出现故障,但是每次都能自动切换standby机器稳定运行。
这里随便给大家举几个生产环境机器故障的例子,因为部署在公司的云环境中,用的都是虚拟机,可能遇到的坑爹故障包括但不限于下面几种情况:
- 虚拟机所在的宿主机挂了
- 虚拟机的网络出现故障
- 负载过高导致磁盘坏了
所以在线上高负载环境中,永远别寄希望于机器永远不宕机,你要随时做好准备,机器会挂!系统必须做好充分的故障预测、高可用架构以及故障演练,保证各种场景下都可以继续运行。
三、Master-Slave架构的分布式计算系统
但是此时另外一个问题又来了,大家考虑一个问题,数据平台系统其实最核心的任务就是对一个一个的时间窗口中的数据进行计算,但是随着每天的日增数据量越来越多,每个时间窗口内的数据量也会越来越大,同时会导致数据平台系统的计算负载越来越高。
在线上生产环境表现出来的情况就是,数据平台系统部署机器的CPU负载越来越高,高峰期很容易会100%,机器压力较大。新一轮的系统重构,势在必行。
首先我们将数据平台系统彻底重构和设计为一套分布式的计算系统,将任务调度与任务计算两个职责进行分离,有一个专门的Master节点负责读取切分好的数据分片(也就是所谓的时间窗口,一个窗口就是一个数据分片),然后将各个数据分片的计算任务分发给多个Slave节点。
Slave节点的任务就是专门接收一个一个的计算任务,每个计算任务就是对一个数据分片执行一个几百行到上千行的复杂SQL语句来产出对应的数据分析结果。
同时对Master节点,我们为了避免其出现单点故障,所以还是沿用了之前的Active-Standby架构,Master节点是在线上部署一主一备的,平时都是active节点运作,一旦宕机,standby节点会切换为active节点,然后自动调度运行各个计算任务。
这套架构部署上线之后,效果还是很不错的,因为Master节点其实就是读取数据分片,然后为每个数据分片构造计算任务,接着就是将计算任务分发给各个Slave节点进行计算。
Master节点几乎没有太多复杂的任务,部署一台高配置的机器就绝对没问题。
负载主要在Slave节点,而Slave节点因为部署了多台机器,每台机器就是执行部分计算任务,所以很大程度上降低了单台Slave节点的负载,而且只要有需要,随时可以对Slave集群进行扩容部署更多的机器,这样无论计算任务有多繁忙,都可以不断的扩容,保证单台Slave机器的负载不会过高。
四、弹性计算资源调度机制
在解决了单台机器计算负载压力过高的问题之后,我们又遇到了下一个问题,就是在线上生产环境中偶尔会发现某个计算任务耗时过长,导致某台Slave机器积压了大量的计算任务一直迟迟得不到处理。
这个问题的产生,其实主要是由于系统的高峰和低谷的数据差异导致的。
大家可以想想,在高峰期,瞬时涌入的数据量很大,很可能某个数据分片包含的数据量过大,达到普通数据分片的几倍甚至几十倍,这是原因之一。
还有一个原因,因为截止到目前为止的计算操作,其实还是基于几百行到上千行的复杂SQL落地到MySQL从库中去执行计算的。
因此,在高峰期可能MySQL从库所在数据库服务器的CPU负载、IO负载都会非常的高,导致SQL执行性能下降数倍,这个时候数据分片里的数据量又大,执行的又慢,很容易就会导致某个计算任务执行时间过长。
最后一个造成负载不均衡的原因,就是每个计算任务对应一个数据分片和一个SQL,但是不同的SQL执行效率不同,有的SQL可能只要200毫秒就可以结束,有的SQL要1秒,所以不同的SQL执行效率不同,造成了不同的计算任务的执行时间的不同。
因此,我们又专门在Master节点中加入了计算任务metrics上报、计算任务耗时预估、任务执行状态监控、机器资源管理、弹性资源调度等机制。
实现的一个效果大致就是:
- Master节点会实时感知到各个机器的计算任务执行情况、排队负载压力、资源使用等情况。
- 同时还会收集各个机器的计算任务的历史metrics
- 接着会根据计算任务的历史metrics、预估当前计算任务的耗时、综合考虑当前各Slave机器的负载,来将任务分发给负载较低的Slave机器。
通过这套机制,我们充分保证了线上Slave集群资源的均衡利用,不会出现单台机器负载过高,计算任务排队时间过长的情况,经过生产环境的落地实践以及一些优化之后,该机制运行良好。
五、分布式系统高容错机制
其实一旦将系统重构为分布式系统架构之后,就可能会出现各种各样的问题,此时就需要开发一整套的容错机制。
大体说起来的话,这套系统目前在线上生产环境可能产生的问题包括但不限于:
- 某个Slave节点在执行过程中突然宕机
- 某个计算任务执行时间过长
- 某个计算任务执行失败
因此,Master节点内需要实现一套针对Slave节点计算任务调度的容错机制,大体思路如下:
1.Master节点会监控各个计算任务的执行状态,同时也会监控各个Slave节点的运行状态
2.如果说某个Slave宕机了,那么此时Master就会将那个Slave没执行完的计算任务重新分配给其他的Slave节点
3.如果说某个Slave的计算任务执行失败了,同时重试几次之后还是失败,那么Master会将这个计算任务重新分配给其他的Slave节点来执行
4.如果说某个计算任务在多个Slave中无法成功计算的话,此时会将这个计算任务储存在一个延时内存队列中,间隔一段时间过后,比如说等待高峰期故去,然后再重新尝试执行这个计算任务
5.如果某个计算任务等待很长时间都没成功执行,可能是hang死了,那么Master节点会更新这个计算任务的版本号,然后分配计算任务给其他的Slave节点来执行。
6.之所以要更新版本号,是为了避免说,新分配的Slave执行完毕写入结果之后,之前的那个Slave hang死了一段时间恢复了,接着将计算结果写入存储覆盖正确的结果。用版本号机制可以避免这种情况的发生。
六、阶段性总结
系统架构到这个程度为止,其实在当时而言是运行的相当不错的,每日亿级的请求以及数据场景下,这套系统架构都能承载的很好,如果写数据库并发更高可以随时加更多的主库,如果读并发过高可以随时加更多的从库,同时单表数据量过大了就分更多的表,Slave计算节点也可以随时按需扩容。
计算性能也是可以在这个请求量级和数据量级下保持很高的水准,因为数据分片计算引擎(滑动窗口)可以保证计算性能在秒级完成。同时各个Slave计算节点的负载都可以通过弹性资源调度机制保持的非常的均衡。
另外整套分布式系统还实现了高可用以及高容错的机制,Master节点是Active-Standby架构可以自动故障转移,Slave节点任何故障都会被Master节点感知到同时自动重试计算任务。
七、下一个阶段的展望
其实如果仅仅只是每天亿级的流量请求过来,这套架构是可以撑住了,但是问题是,随之接踵而来的,就是每天请求流量开始达到数十亿次甚至百亿级的请求量,此时上面那套架构又开始支撑不住了,需要继续重构和演进系统架构。