今天的分享主要围绕七牛在最近一年时间里面开发的大数据平台进行展开,目前咱们的平台已经承载了公司核心业务的运营;关于咱们的产品,主要会从一个场景展开进行介绍,当中包含了咱们在设计过程当中遇到的挑战以及解决方案。也欢迎你们基于这些问题和咱们展开交流与讨论。api
对于运维人员来讲,在进行每平常规的线上运维时,日志当中的一天内访问量的波动、线上错误分布、其余业务指标这些数据对于运维人员来讲并不是是一个透明的过程,那么如何将这些东西作到可视化,或是将这些数据收集起来作统一的处理分析,实际上是一个比较复杂和较难实现的过程。这就是所谓的运维日志分析,也是咱们以前所说起的场景。关于咱们产品解决场景的细节,在下面将会进一步进行分析。咱们以Nginx-log为例对咱们的Pandora产品进行叙述。七牛云存储
数据接入Pandora—logkit配置运行网络
任何数据分析的第一步都是数据接入。Pandora开发的数据接入工具logkit,能够帮助用户将数据打入Pandora平台内;在最开始须要下载logkit,配置并运行(图1)架构
logkit工具支持多种数据源,好比对Nginx-log、kafka数据进行采集,并打入咱们的数据处理平台当中。下面对图 1 进行详解,首先,咱们须要查看日志格式,包括日志格式的名称。在图 1 中,咱们明确了日志存储的路径以及格式。最后进入配置文件,将须要进行配置的信息进行配置,并指明数据须要打入存放的路径,若是须要打到某一个消息队列中时,须要对密钥进行配置并运行它,那么此时这个数据才会采集收录到咱们的平台当中。并发
日志检索框架
图 2 所示是一个比较直观的可视化界面,它支持拖拽,页面左侧能够看到“数据源”与“日志检索”这两项内容,配置好的 logkit 运行以后,全部数据都会打入“数据源”中。页面右侧则显示了数据源中每一个字段的名称、格式等信息。运维
图 3 所示是“日志检索”的内容显示页面,经过“日志检索”咱们能够清晰的查看一些业务逻辑,在搜索框中填入你的查询条件,就能够进行全文检索,当须要查看过去某个时刻响应超过3s的全部请求,那么经过“日志检索”页面也能够清楚的查询并显示出来。图 3 仅仅是展现了一个全文搜索的状态,在功能页面还能够查看相关数据分布的柱状图。工具
日志聚合布局
如图 4 所示,打入到数据源里面的数据,能够经过一段SQL以每分钟为粒度进行计算聚合。能够作聚合的内容不少,如来自某个IP的请求数量,也能够是别的一些相关操做,聚合结束以后,数据便会再次回流到咱们的数据源当中。简单来讲,咱们经过一次计算将数据从新回流到数据源用于下一环节的分析处理,计算、回流的过程是能够不断去进行级联的,能够实现不少相对比较复杂的数据处理。性能
数据回流至平台
上面说起的数据回流至数据源是一种处理方式,用户搭建本身的一套HTTP服务,数据经过HTTP的接口回流至其本身系统内是另一种数据处理方式。经过这种方式回流的数据,用户能够将分析结果在本身平台进行沉淀,操做页面如图 5 所示。
实时数据展现与监控
图 6 所示直观展示了咱们的监控页面,监控服务须要开通以后再进行Grafana页面配置,页面的基本配置在咱们官方文档中都有提供,用户能够直接下载导入。
图 7 展现的是对 Nginx 的日志进行分析以后的数据展现图。左上角橙色的框(visits为0)显示可总访问量,右上角绿色的柱状图则是在过去一段时间内发生的请求数以及响应时间,右下角的饼状图显示了相关用户访问的占比量。这些图的样式及位置均可以进行配置。
架构设计
图 8 所示展现了Pandora的业务架构。数据经过Portal/Logkit/SDK/API能够导入咱们的平台,进入消息队列当中,消息队列当中的数据能够通过计算反复在计算任务和消息队列之间进行流动,固然,这些数据也能够直接导出。导出后的数据通过下游系统(日志检索/时序数据等)处理最终能够生成数据报表,以上就是数据的整个流向。
Pipeline设计目标及技术选型
每一个系统在最初设计时都会拟定设计目标以及相应的须要解决的问题。下面先讲一下咱们的设计目标,首先这个系统必须支持数据快速接入、高吞吐量、低延迟;其次做为一个云服务,它必须支持海量用户并发访问以及必须支持海量消息队列;要提供实时计算与离线计算的框架知足计算需求;最终它必须是可视化的操做知足用户操做需求。在设计目标提出以后,咱们要对选型进行规划,咱们须要选择具有高吞吐量的存储系统,固然目前七牛的存储系统无疑是最知足需求的;其次咱们须要强大灵活的大数据处理引擎;最后开发人员必须保证最终设计的产品是能够快速迭代开发的。基于这些要求,咱们很轻易选择了相应的技术支撑,使用Kafka来知足咱们的对海量消息队列设计的需求;使用Spark做为计算引擎;语言选型上则选用咱们底蕴积淀深厚的Golang,最终,在肯定这几种技术选型以后,咱们便开始搭建系统。
图 9 所示,是咱们Pipeline的总体架构设计,它负责pandora中数据的接入和处理。数据经过Logkit等方式导入到数据接入层,也就是apiserver。经过apiserver的数据会进入到消息队列里面,以后经过计算引擎的读取和回写操做,最终导入到下游系统中(LogDB/TSDB/HTTP/七牛云存储)咱们今天着重关注绿色箭头指引的数据流方向,会说起里面相关的重点进行详解。在整个数据流流动过程当中,有几个因素可能会决定这个系统的效率,好比稳定性、性能等。因此我将从用户到消息队列,通过计算任务再返回到消息队列,最终导出数据这整个过程来说解。
数据接入层
图 10 所示显示的是数据接入层。数据经过apiserver导入,调度器用来管理一些用户消息队列的源数据,其中包括数据以何种形式写入到消息队列当中去。logkit这个工具之因此放在这里,不是由于数据会经过apisever流向logkit最终再流向消息队列,而是由于它能够采集各类形式的数据,在这里咱们用它采集系统审计日志与监控信息。它很容易进行管理和配置。
容器化
在最开始设计这个系统时,扩容是一个比较困扰咱们的问题。由于接入的基本是内部用户,接入速度比较快,因此一周以内须要扩容至少一到两次,这在运维上是一个比较重的负担。以后咱们采用了容器的方案,由于整个数据接入层是一个无状态的组件,因此咱们将它容器化,使用咱们的容器云产品解决。如图 11 所示,每个pod中,咱们都将apisever与logkit布局在一块儿,经过监控数据,咱们将每一个容器包括这个集群总体的信息所有都汇总在了这个调度器当中。调度器里面承载着整个集群负载及资源总量这些信息,能够及时根据这些信息动态的实现扩容缩容。
数据写入优化
图 12 所示是对数据写入进行优化的过程。第一代数据写入流程,采用了串行的方式进行,数据导入以后是一行一行进行解析,所有解析以后再将数据写入到消息队列当中,可是这种方式的处理效率是很是低效的。因此咱们利用go语言的特性,采用了line channel,数据源源不断进入channel,而后会在channel下游起多个parser,并行的对数据进行解析。也就是说咱们利用channel将处理变成了并发的过程,最终提升了CPU的利用率,下降了用户响应的延迟率,大大优化了性能。
计算
如图 13 所示,咱们的计算基于Spark 实现,提供了一个比较简单的SQL,对用户屏蔽了底层细节。
导出优化
数据流入整个系统中,在系统中无论是作计算仍是存储,这些通过处理的数据若是须要发挥做用,都要流入到下游系统中,因此“导出数据”这个过程起到的是一个链接上下游,承上启下的做用。图 14 是这个系统的总架构图,由于当时并未对导出服务作细粒度的任务切分,而且单台server也处理不了过大的用户任务,因此在高峰期时,会致使延迟增大,基于此,咱们通过一个月的开发最终推出了一个全新的版本。
如图 15 所示,是通过改进后的总体架构图。图的顶层是咱们的master,用它来控制全部任务的调度管理。全部任务都是经由调度器转发给master,由master来评估每一台机器上的负载,以后再根据机器自己的一些状态(CPU使用率、网络带宽、执行任务的状况)去作相应的调度,除此以外咱们还将任务作了更细粒度的切分。
调度方法的设计首要考虑到的就是面向资源,其次须要充分利用异构机器,而且能知足自动调整。面向资源你们都可以理解,充分利用异构的机器,是由于咱们机器规格众多,所能解决的任务强度不一致,咱们须要充分利用该机器的资源,而不能让其在处理任务时,有"机器资源"不足或浪费的状况发生;至于自动调整,就是能够保证在面对用户量突增或者突减这种突发状况发生时,咱们具有自动调整任务分布的能力,其最终的目的也是为了充分利用资源。
任务分配
图 16 是任务分配的过程图。假设最初任务(T1-T7)都相对均匀的分布在三台机器上,此时又有另外两个任务(T8-T9)进入,那么咱们就须要寻找一些相对比较空闲的机器(S1 或 S2)优先将这两个任务分配给他们。这只是针对一个相对比较均衡的状况作的一个调整。
自动调整
固然也会有不均衡的状况产生(图 17-18)那么此时就须要咱们去作一些自动调整,好比有一个用户删除了其不少任务,那么此时的S1与S2相对S3会比较空闲,那么此时咱们就须要经过server向master上报心跳,这个内容包括对资源的占用以及任务的分布状况,根据结果对比较空闲的机器作一个调整,保持一个相对平衡的状态。
水平扩展
图 19 是进行水平扩展时会产生的一个问题。全部机器目前都处于一个比较繁忙的状态,此时若是过来一个新的任务(T13),可是前12个任务已经所有分布在这三台机器上面处理,腾不出空闲的机器处理新增任务,那么此时就会须要对机器进行扩容。
如图 20 所示,在前三台机器都处于“忙碌”状态时,咱们须要新增server4,一旦启动S4,它会向master汇报心跳,而后master就会感知到这个任务的存在以及S4的存在,从新对整个资源分布使用状况作一次评估,将T13分配到比较空闲的S4上,甚至能够将在S一、S二、S3等待处理的任务分配到S4上。
资源隔离
实际上,不仅仅是对任务进行自动调整均衡分担机器处理压力是很是重要的,对于一些比较特殊的任务,如何保证这个用户流量在突增时不会影响到其余相对较小的用户,或是当数据导出到云存储进行压缩时(压缩的过程很是耗费CPU资源)如何保证它不会影响其余任务,这些都是咱们须要处理的问题。针对这些问题咱们提出了资源隔离的概念(图 21)将机器和任务进行隔离,提供调度组(调度组中是相近的一组机器或者是一类任务)功能,经过对他们物理上的隔离,达到相互之间互不影响,并对资源进行充分利用。
master高可用
综上咱们能够看出咱们的系统是一对多的状态(一个master对多个server)那么在这种状况下,如何解决在出现单点故障时仍然保证服务的高可用。如图22到图23所示,是咱们设计的一个核心所在,咱们能够在图中看到最底端是一个zookeeper集群,咱们经过对一个临时文件的建立来模拟一个锁,多台机器能够同时去抢占这把锁,抢占成功的master会成为一个主master,没有抢占成功的则会做为一个备份,在平时会空闲,但一旦S1丢锁,master2就会抢占锁,接过整个调度任务以及一些集群管理任务,这就是master高可用思路。
Server高可用
server高可用,咱们也是采用相似的思路。咱们将master视做一个高可用节点,每个server都须要向master汇报心跳,心跳的内容包含了机器自己的存活以及相应任务的执行状况。如图 24 所示,master一旦感知到S3宕机,那么此时就会将S3上执行的两个任务(T5-T6)都调走,而且它会认为S1与S2是相对比较合适的选择,而且会将这两个任务调去相应的server上,这样就完成了server的高可用目标。
系统级水平扩展
最开始有说起咱们的整个消息队列是使用kafka实现的,kafka其实也是有上限的,在最开始咱们也是采用了kafka单个集群(图 25)后来发现,一旦业务量上来,消息队列数据一旦多到必定程度,系统会发生雪崩。因此咱们对单个集群作了一个扩展(图 26)将单个kafka集群直接拆成多个集群,让每个kafka集群都保持一个相对比较小的规模,这样性能方面就会获得很大的提高,图 26 所示就是通过扩展后的状况,由三个kafka提供的信息会汇总到咱们的调度器上,调度器经过压力或者是消息队列的数量,对用户新建立的任务以及新的数据源进行分配,分配至合适的kafka集群中。
上下游协议优化
在实践中仍是会出现上下游之间性能较低的状况。在最开始,咱们采用Json来作上下游的数据传输,但是在日志检索时暴露出的问题就是,这样作对网络消耗很大,因而咱们决定采用Protobuf进行上下游数据传输。图 27 展现了使用Json与Protobuf时,从序列化、反序列化角度进行对比的数据结果展现,从图中能够看出,使用Protobuf消耗的时间都更短,尤为在反序列化时,它的CPU消耗下降了将近一个数量级。所以,采用这种方式,无论从集群计算资源利用仍是从网络带宽提高上都将效率提高了数倍。
流水线处理
至于对流水线的处理,最开始的设计实际上是一个串行的操做,导出服务从消息队列当中拉取数据,通过处理以后作一个推送,持续这样的工做过程,其中处理操做是很快的,可是拉取和推送相对就很慢,这样的一个过程,执行效率实际上是很低的,而且因为每种操做的处理时间不同,有的快有的慢,就致使在监控图上监察到网络的趋势图是时高时低的,也就致使了利用率的下降。鉴于此,咱们优化了流水线操做,采用并行化操做(图 28)结果显示,这样作的结果是推送和拉取效率都会比上面一种方式高。
Golang GC
咱们的整个语言选型是采用Golang的,而它是一个带GC的语言,实际上出现的状况仍是不少的,系统当中会有1/10的时间是不干活而在进行垃圾回收的。所以在代码层面咱们作了一些改进,一个是sync.Pool的使用可以下降垃圾回收的频率;其次是重用对象,将一个对象尽量重复使用,这样一来,每一次GC的量就会减少。以后咱们对Golang进行了版本升级,升级至1.8版本后咱们再查看了一下GC的耗时,发现提高了将近两个数量级。这就是代码层面的优化。
有限资源假设
最后叙述一下咱们关于资源假设方面进行的一个优化,也就是要创建起一个有限资源假设的概念。前段时间因为数据接入量比较大,咱们须要本身进行运维,突发接入的客户,会使系统轻易就被跑满,此时咱们会想办法加机器或者是说在调度上去作一些调整和优化,可是这样终究不是一个长久的办法。因而咱们会在刚开始去作一个资源有限假设,就是要在最开始评估出来资源有限状况下咱们该如何去作。好比须要提早预估出10M带宽所能对应多少用户的任务,这个必须是有一个数据存在的,而且在这个基础上咱们须要作一个资源的预估和集群资源的规划。根据这个预估的数据的状况,去划定一个水位标准,超过水位标准以后,再考虑是否是须要进行扩容。客户这边也须要沟通清楚咱们现有的处理能力,这样,才能保证整个集群/服务是处于一个比较健康的状态。
上面咱们说起了咱们的架构实现以及总体的一个优化,目前的成果就是:咱们支撑了万亿级的数据点,而且天天能够处理几百TB的数据,以及支持海量用户。咱们的系统目前保持很低的延迟,较高的处理效率;因为咱们实现了自动化运维,因此人力成本也大大减小,咱们的指望就是能够在写代码时不被运维事情干扰;至于可用性,目前已经达到3个9(99.9%)的成效。