基于PB级海量数据实现数据服务平台,需要从各个不同的角度去权衡,主要包括实践背景、技术选型、架构设计,我们基于这三个方面进行了架构实践,下面分别从这三个方面进行详细分析讨论:
实践背景
该数据服务平台架构设计之初,实践的背景可以从三个维度来进行说明:当前现状、业务需求、架构需求,分别如下所示:
当前现状
收集了当前已有数据、分工、团队的一些基本情况,如下所示:
业务需求
另外,实现的该数据服务平台,需要满足当前的基本数据业务需求,主要包括使用平台的人员特点,需要支撑的各种基本数据需求,经过梳理,如下所示:
架构需求
在未来业务模式变化的情况下,能够非常容易地扩展,并尽量复用大部分核心组件。同时,还要面向开发人员复用数据平台的数据业务服务,以增加平台利用率,间接产出数据价值。考虑如下一些当前需要以及未来可能演变的架构需求:
技术选型
技术选型,主要从如下几个方面进行考虑:
数据存储
数据量级达到PB级,所以,作为整个数据服务平台的最初输入数据,我们称为数据服务平台的原始数据,后续简称原始数据,这些原始数据是直接存储在HDFS文件系统中,根据时间的维度,分为小时数据、日数据、月数据。这样,可以根据数据计算需要,按照小时、日、月进行加工处理,能够在可允许的计算资源配额和计算时间范围内完成处理。 另外,根据每天大约30~40TB的增量数据,原始数据采用parquet格式压缩存储,我们进行二次加工的输出仍然是以parquet格式存储。
对于PB级的数据,想要在数据服务平台中快速为用户提供数据服务,根据业务特点,存储在适合快速加载、快速计算的分布式数据存储系统中。 快速加载,必然要对数据进行特殊格式处理,并在一定程度上压缩数据,这样才能减少数据加载时间。可以很容易想到,使用支持列式存储的分布式数据库。比如Vertica分布式数据库就是一款支持列式存储的MPP数据库。Vertica是HP开发的商用分布式数据库,同时也发布了开源的免费社区版本,不过社版本有一定限制:只支持1TB原始数据、3节点集群规模。如果变通一些,可以通过Vertica社区版本进行改造以支持解除3个节点集群规模和1TB存储的限制,不过要在分片逻辑控制、分片数据一致性方面做更多工作,尤其是面向上层应用提供单一的统一存取视图是非常必要的。因为列式存储支持计算时只加载用于计算的列,故而能够达到快速加载的目的。 快速计算,首先要求计算能够并行化,那么数据就应该分片存储,使数据计算本地化。Vertica自然能够实现数据的并行计算,我们在前期使用过程中验证了,对于从40亿+的大表中批量匹配出任意信息(匹配ID,以及ID对应的关联表中的其它明细信息),效率非常好,基本分钟级便可以输出匹配结果。 我们也对开源不久的MPP数据库Greenplum进行了调研,它原生支持分布式架构,支持列式和行式两种存储,自然具有Vertica对应的列式存储的优势,又不需要手动对分片进行管理控制,但性能要比Vertica差一些。然而,Greenplum数据库能够支持数组类型,支持多种编程语言的UDF,结合我们之前做过很多有关Bitmap的实践,采用开源的RoaringBitmap,能够很好的基于Greenplum实现快速的Bitmap计算。
消息存储,主要是用来解耦后台多个较重的系统之间的通信。因为本身这类系统比较重,如果采用RPC调用的方式进行通信,某个系统进行升级,会导致依赖于该系统提供服务的其它系统管理更多的特殊情况处理。而采用消息机制,使得各个系统之间不需要关注交互系统处理状态,而对消息交换只需要关注消息的生成和消费。 这样,我们可以随时对系统进行改造、升级、Bug修复重启等操作,而不会使整个平台陷入不可控的状态。消息中间件,我们选择使用RabbitMQ。
数据处理
数据处理,主要包括原始数据ETL处理、应用数据计算两大类:
基于HDFS存储的数据,最方便最高效的技术方案,自然是使用Spark计算集群来对数据进行ETL处理。我们基于原生的Scala编程语言来开发各种ETL程序,实现数据清洗、抽取、转换操作。
数据服务平台中,面向用户的应用数据计算,基于Greenplum数据库支持的SQL语言来实现数据处理,并基于Java编程语言来实现整个应用服务的开发。
ETL作业调度
数据处理需要进行大量的ETL计算,管理各种计算任务之间的依赖关系及其调度,我们采用了非常轻量的Azkaban调度系统。
业务元数据管理
业务元数据,主要用于支撑数据服务平台Web UI上面的各种业务条件选项,比如,常用的有如下一些:
这些元数据,有些来自于基础数据部门提供的标准库,比如品牌、价格范围等,可以从对应的数据表中同步或直接读取;而有些具有时间含义的元数据,需要每天通过ETL处理生成,比如应用信息;POI数据需要从外部抓取,并进行处理,一般每个月更新一次。 这些元数据,为支撑应用计算使用,被存储在MySQL数据库中;而对于填充页面上对应的条件选择的数据,则使用Redis存储,每天/月会根据MySQL中的数据进行加工处理,生成易于快速查询的键值对类数据,存储到Redis中。
数据服务
数据服务,主要支撑后台的数据应用,全平台采用标准的REST接口风格来定义,主要使用Spring Boot来快速开发对应的接口。
还有一点我们需要遵循的是,任何具有复杂的数据处理逻辑的服务,都通过一层REST接口进行封装,将全部的离线批量服务后置。这样得到一个聚合服务的REST接口层,该层主要负责定义和管理接口的各个请求、响应参数,REST接口不变,而对应的数据处理逻辑可以根据实际情况进行调整,以后对存储或计算方案进行升级改动,都不影响使用上层REST接口调用方。
比如,我们采用Greenplum数据库,在Greenplum前面增加了一层Greenplum服务网关,对于任何需要访问Greenplum数据库的应用,必须通过与Greenplum服务网关进行交互,而不是直接去访问Greenplum数据库。理想状态下,Greenplum服务网关可以实现为无状态的服务网关,通过Nginx做反向代理实现HA,这样后续因为业务变更,可以非常平滑地进行变更和升级,而不影响依赖于Greenplum服务网关的业务接口调用。
除了数据服务平台内部进行服务调用,最外层通过Web界面的风格,只需要拖动或选择可视化组件,实现对非技术背景的业务用户进行数据提取和分析,未来我们还要将全部的服务暴露到外部(数据服务平台所属部门之外的其它部门,以及公司外部),最大化数据服务的价值。 微服务部分,我们选择了Spring Cloud来快速构建微服务。
UI展示
UI层主要根据我们开发人员的技术背景,使用Vue来构建面向业务用户的数据服务Web系统。
架构设计
整个数据服务平台的架构设计,如下图所示:
如上图所示,对应的各个核心子平台及其服务,下面将分别详细说明:
数据服务Web系统是面向用户使用的,主要通过可视化业务组件的方式,将数据服务暴露出来,方便业务用户使用。同时,该系统提供用户权限管理的功能,可以设置用户权限,主要包括业务用户和管理用户。 数据服务Web系统的设计,如下图所示:
该系统的设计比较容易,核心的思想就是前端和后端分离。前端定义的各种可视化组件,都是根据不同业务线的需求,经过梳理分类,将需求频度较高的抽象出来,做成业务功能组件。后端服务包括两类:一类是业务元数据服务接口,包括各种需要在页面展示的数据项,如设备机型、地域、应用、POI等;另一类是作业管理服务接口,主要负责管理作业相关内容,如作业查询、保存等。
业务作业调度平台是整个数据服务平台最核心的子平台之一,设计该平台主要考虑除了当前支撑面向业务用户需求之外,还要能够很好的扩展以支持其他业务部门开发人员对服务的使用。该平台的架构,如下图所示:
该平台主要负责作业的解析编排、排队、调度。 作业编排采用调用外部编排服务的方式,主要考虑的是编排需要根据业务的一些属性进行实现,所以将易变的业务部分从作业调度平台分离出去。如果后续有对编排逻辑进行调整和修改,都无需操作业务作业度调度平台。 排队,支持多队列排队配置,比如根据当前及其未来的发展趋势,需要具有面向业务用户的业务队列、面向开发人员的服务队列,而这两种队列所负责的作业调度的SLA是完全不同的,业务队列中的作业每天可能成百上千个,而服务队列在初期对于每个业务线只需要每天调用一次或多次(正常会严格限制服务调用数量),初期从作业量上来看这两个作业容量的比例大概是8:2,通过队列来隔离调度,能够更好地满足具有不同需求的用户。 调度,是对作业、以及属于该作业的一组任务进行调度,为了简单可控起见,每个作业经过编排后会得到一组有序的任务列表,然后对每个任务进行调度。这里面,稍有点复杂的是,作业是一级调度,任务是二级调度,但是要保证属于同一个作业的任务能够按照先后顺序被调度运行。所以,作业是排队的基本单位,在每一个排队单元中,要包含作业ID、任务个数、作业状态,同时为能够控制任务正确调度,也需要包含当前调度运行中任务ID、运行中任务状态,可见任务是调度运行的基本单位。被调度运行的任务会发送到RabbitMQ中,然后等待任务协调计算平台消费并运行任务,这时作业调度平台只需要等待任务运行完成的结果消息到达,然后对作业和任务的状态进行更新,根据实际状态确定下一次调度的任务。 另外,还有几个点需要注意:第一,被调度运行的任务需要进行超时处理;第二,控制同时能够被调度的作业(实际上运行的是作业对应的某个任务)的数量;第三,作业优先级控制。
任务协调计算平台也整个数据服务平台最核心的子平台之一,它是无状态的,除了能够支撑我们的数据服务平台,如果有其它想要接入的任务,都可以通过该平台协调来运行。该平台的架构,如下图所示:
该平台的设计是主从架构,Master和Slave之间通过RPC调用进行通信,通信层使用了Netty网络通信框架。Worker可以根据实际计算任务的压力,进行水平扩展。 Master负责控制从RabbitMQ中拉取任务消息,然后根据Worker节点的资源状况进行任务的协调和调度,并将Worker上作业完成的信息发送到RabbitMQ,供上游业务作业调度平台消费从而控制更新作业的运行状态。同时,Master管理注册的Worker状态、Worker资源状态、Worker上运行的任务的状态。 Worker是实际运行任务的工作节点,它负责将任务调度到后端的计算集群,或者调用数据处理服务来实现任务的运行。由于任务都是批量处理型计算任务,所以Worker要管理任务的提交,以及对已提交任务运行状态的异步查询(轮询)。
Greenplum REST服务网关,直接与Greenplum数据库进行交互,这样起到保护Greenplum数据库的作用。因为实际Greenplum数据库集群的计算容量有限,不能无限支持很高并发,所以通过控制并发来加快每个计算任务。该REST服务网关的设计,如下图所示:
上图中,通过排队机制来保护Greenplum,并进行任务的调度运行,所以该服务是有状态的。而且,该服务具有一定的业务特征,根据不同的数据需求,需要对接口以及SQL进行调整,最好的方式是将业务接口与任务计算分离:业务接口层可以将调用任务保存到Redis队列中,实现接口层的冗余部署和平滑升级,然后作为消费的任务处理服务直接消费Redis队列中的任务,提交到Greenplum数据库计算。
数据微服务平台,主要考虑复用已存在的数据服务,以及支撑数据服务的核心组件,如业务作业调度平台、任务协调计算平台等,为面向开发人员使用的服务调用,通过服务接口的方式暴露出来。数据微服务平台的架构,如下图所示:
该平台主要基于Spring Cloud构建,使用Eureka作为服务注册中心。由于整个数据服务平台是以离线计算为主,没有高并发、服务降级的、调用链跟踪等需求,所以并没有完全使用Netflix OSS中大部分组件,如Zuul、Hystrix等。如果后续需要,可以非常容地集成进来。 鉴权网关,是所有调用微服务平台的外部调用方的入口。为了保证整个微服务平台的正常运行,通过用户、时间(调用期限)、调用频率等限制调用方。比如某些业务线的应用需要使用微服务平台的服务,由于对方业务可能下线,而服务程序没有下线,仍然持续调用我们平台服务,这会对微服务平台资源造成浪费。另外,也避免了服务调用方测试、调试,对整个微服务平台造成不可控的状况。 上图左面,服务注册中心及其以上部分,是整个微服务平台的核心部分,我们在构建该平台时,也考虑了接入非微服务的组件。比如热力图服务,数据是需要批量处理生成,而访问时是同步调用的,所以在数据服务平台的Web部分提交的作业,如果是热力图类型,会调用微服务平台的热力图服务异步生成数据,而用户可以在Web系统中查看热力图(如果未生成则提示正在生成中);对其它上层数据应用也可以直接调用微服务平台的热力图服务生成数据,并下载对应数热力图据。
其它服务/系统比较简单,所以这里只是简单说明一下: Java REST服务网关:要对某些从Greenplum数据库中计算得到的数据,需要进行再加工处理以满足实际业务,如热力图数据生成和压缩等,将这些服务封装成REST风格接口调用。 Spark REST服务网关:对于需要对HDFS上指定数据集处理,生成需要的结果数据,使用Spark开发程序,同时将Spark计算作业封装成REST风格接口调用。 数据ETL调度系统:使用开源的Azkaban调度系统,实现所有ETL作业的统一调度。 数据采集服务:根据数据业务需要,从网上或其它渠道采集数据,比如通过高德API采集POI数据等。
架构总结
通过上面的架构设计实践,我们总结一下实践的经验,如下所示:
对于无状态的服务,我们可以通过冗余部署多个服务实例,再通过反向代理的方式实现服务的高可用,甚至在演进为微服务架构时也比较容易做到。对于有状态的服务,因为单个服务需要维护状态新,所以实现高可用的思路是,启动多个实例,但是同一时刻只有一个是Active服务可以操作状态,而其它实例作为Standby服务,需要通过一种机制来监听并发现Active服务的可用性,然后在其失败时能切换到Standby服务,比如常用的Zookeeper等。