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