【大数据】

217 阅读49分钟

主要使用消息队列( TimeTunel, TT )来实现从日 志 采集服务器到数据计算的 MaxCompute。简单来讲,就是 TT 将消息收 集功能部署到日志采集服务器上进行消息的收集,而后续的应用可以是 实时的应用实时来订阅 TT 收集到的消息,进行实时计算,也可以是离 线的应用定时来获取消息,完成离线的计算。有关消息队列,以及日志 数据的统计计算

考虑到服务器的收集能力(如峰值 QPS 等)、数据传输能力( TT 的搬 运速度)、实时解析的吞吐量、实时业务分析的处理能力

如页面加载情况、消耗内存等,可以实施采样), 只 上报部分日志到服务器。读到这里,可能会有读者发现,整个日 志处理 流程还是比较长的,对于对实时性要求极高的业务场景,如上链路显然 不能满足需求。所以一方面,我们从业务上进行改造,采用端上记录 ; 另 一方面,我们也在链路各环节做优化,如从采集服务器直接完成解码 并调用业务 API 完成业务的计算(省去中间的传输和过多 的处理) 。在 这方面我们也面临着巨大的挑战,在保证稳定的同时扩展功能, 在稳定 及业务深度之间做到很好的平衡。 在如上策略下, 2016 年的“双 11 ”,日 志采集浏览等核心用 户行为 日志均实现了 100%全量及实时服务,支持天猫所有会场的实时推荐。 在“双 11 ”中,用户的浏览、点击、滚屏等每个操作行为,都实时影 响到后续为其推荐的商品,很好地提高了用 户体验。

任务管理接口:供用户系统向 Alisa 中提交、查询和操作离线任 务,并获得异步通知。 · 系统管理接口 : 供系统管理员进行后台管理,包括为集群增加新 的机器、划分资源组、查看集群资源和负载、追踪任务状态等。 • Driver: Alisa 的调度器, Driver 中实现了任务管理接口和系统管 理接口;负责任务的调度策略、集群容灾和伸缩、任务失效备援、 负载均衡实现。 Drive 的任务调度策略是可插拔替换的,以满足 不同的使用场景。 Driver 使用 Resource manager 管理整个集群的 负载。 (我们可以把 Driver 理解为 Hadoop 的 JobTracker。) • Task pool: Driver 也将已经提交的全部任务都放入到 Task pool 中管理,包括等待资源、数据质量检测、运行中、运行成功和 失 败的所有任务。直到任务运行完成(不论成功或者失败),并且 用户确实获取到了关于这个状态的通知, Driver 才会将任务从 Task pool 中移除。 Driver 和 Node 通过 Task pool 提供的事件机 制进行可靠的通信。整个系统全部状态(除了与运行无关的部分 管理信息外)都保存在 Task pool 中,这样系统的其他部分很容 易实现高可用性和伸缩性。而 Task pool 本身采用 Zookeeper 实 现,这样它本身也是具备高可用能力的。 • Resource manager :这个组件专注于集群整体资源的管理。 Task container :类似于 Web Server,为 Task 提供运行的容器( 类 似的, Web Server 为 Action 提供运行的容器)。容器负责处理 Task 的公共逻辑,如文件下载,任务级 Session 、流程级 Session 的维护等。同时 Task container 负责收集机器的实际负载并上报 给 Resource manager。 • Session manager :这个组件实现了对 Task session 的管理。 • Node: Node 代表 Alisa 集群中的一个节点。节点负责提供任务 运行所需的物理资源。 Node 是逻辑概念, 一 台物理机器上可 以 部署一个或者多个 Node (Node 类似于 Hadoop 的 TaskTracker ) 。

。总的来说,同步方式可以分为三种 : 直连同步 、数据文件 同步和数据库日志解析同步。

直连同步 直连同步是指通过定义好的规范接口 API 和基于动态链接库的方 式直接连接业务库,如 ODBC/JDBC 等规定了统一规范的标准接口,不 同的数据库基于这套标准接口提供规范的驱动,支持完全相同的函数调 用和 SQL 实现。

这种方式配置简单,实现容易,比较适合操作型业务系统的数据同 步。但是业务库直连的方式对源系统的性能影响较大,当执行大批量数 据同步时会降低甚至拖垮业务系统的性能。如果业务库采取主备策略, 则可以从备库抽取数据,避免对业务系统产生性能影响。但是当数据量 较大时,采取此种抽取方式性能较差,不太适合从业务系统到数据仓库 系统的同步。

数据文件同步

数据文件同步通过约定好的文件编码、大小、格式等,直接从源系 统生成数据的文本文件,由专门的文件服务器,如 FTP 服务器传输到 目标系统后,加载到目标数据库系统中。当数据源包含多个异构的数据

库系统(如 MySQL 、 Oracle 、 SQL Server 、 DB2 等)时,用这种方式比 较简单、实用。另外,互联网的日志类数据,通常是以文本文件形式存 在的,也适合使用数据文件同步方式(见图 3.2 ) 。

由于通过文件服务器上传、下载可能会造成丢包或错误,为了确保 数据文件同步的完整性,通常除了上传数据文件本身以外,还会上传一 个校验文件,该校验文件记录了数据文件的数据量以及文件大小等校验 信息,以供下游目标系统验证数据同步的准确性。 另外,在从源系统生成数据文件的过程中,可以增加压缩和加密功 能,传输到目标系统以后,再对数据进行解压缩和解密 , 这样可以大大 提高文件的传输效率和安全性。

3.1.3 数据库日志解析同步 目前,大多数主流数据库都已经实现了使用日志文件进行系统恢 复 ,因为日 志文件信息足够丰富,而且数据格式也很稳定,完全可以通 过解析日志文件获取发生变更的数据,从而满足增量数据同步的需求。 目前,大多数主流数据库都已经实现了使用日志文件进行系统恢 复 ,因为日 志文件信息足够丰富,而且数据格式也很稳定,完全可以通 过解析日志文件获取发生变更的数据,从而满足增量数据同步的需求。

以 Oracle 为例,可以通过源系统的进程,读取归档日志文件用以 收集变化的数据信息,并判断日志中的变更是否属于被收集对象,将其 解析到目标数据文件中。这种读操作是在操作系统层面完成的,不需要 通过数据库,因此不会给源系统带来性能影响。 然后可通过网络协议,实现源系统和目标系统之间的数据文件传 输。相关进程可以确保数据文件的正确接收和网络数据包的正确顺序, 并提供网络传输冗余,以确保数据文件的完整性。 数据文件被传输到目标系统后,可通过数据加载模块完成数据的导 人,从而实现数据从源系统到目标系统的同步(见图 3.3 )。 数据库日志解析同步方式实现了实时与准实时同步的能力,延迟可 以控制在毫秒级别,并且对业务系统的性能影响也比较小,目前广泛应 用于从业务系统到数据仓库系统的增量数据同步应用之中。 由于数据库日志抽取一般是获取所有的数据记录的变更(增、删、 改),落地到目标表时我们需要根据主键去重按照日志时间倒排序获取 最后状态的变化情况。对于删除数据这种变更情况,针对不同的业务场 景可以采用一些不同的落地手法。 我们以具体的实例进行说明。如表 3.1 所示为源业务系统中某表变 更日志流水表。其含义是:存在 5 条变更日志,其中主键为 1 的记录有 3 条变更日志,主键为 2 的记录有 2 条变更日志

针对删除数据这种变更,主要有三种方式,下面以实例进行说明。 假设根据主键去重,按照流水倒序获取记录最后状态生成的表为 delta 表。 第一种方式,不过滤删除流水。不管是否是删 除操作, 都获取 同一 主键最后变更的那条流水。采用 此种方式生成的 delta 表如表 3 . 2 所示。

第二种方式,过滤最后一条删 除流水 。 如果 同一主键最后变更 的那 条流水是 删除操作,就获取倒数第二条流水。采用此种方式生成 的 delta 表 如表 3 . 3 所示。 第三种方式,过滤删除流水和之前的流水。如果在同一主键变更的 过程中有删除操作,则根据操作时间将该删除操作对应的流水和之前的 流水都过滤掉。采用 此种方式生成的 delta 表如表 3.4 所示。

对于采用哪种方式处理删除数据,要看前端是如何删除无效数据 的。前端业务系统删除数据的方式一般有两种 :正常业务数据删除和手 工批量删除。手工批量删除通常针对类似的场景,业务系统只做逻辑删 除,不做物理删除, OBA 定期将部分历史数据直接删除或者备份到备 份库。 一般情况下,可以采用不过滤的方式来处理,下游通过是否删除记 录的标识来判断记录是否有效。如果明确业务数据不存在业务上的删 除,但是存在批量手工删除或备份数据删除,例如淘宝商品、会员等, 则可以采用只过滤最后一条删除流水的方式,通过状态字段来标识删除 记录是否有效。 通过数据库日志解析进行同步的方式性能好、效率高,对业务系统 的影响较小。但是它也存在如下一些问题 : ~ 34 ·数据延迟。例如,业务系统做批量补录可能会使数据更新量超出 系统处理峰值,导致数据延迟。 ·投人较大。采用数据库日 志抽取的方式投入较大,需要在源数据 库与目标数据库之间部署一个系统实时抽取数据。 ·数据漂移和遗漏。数据漂移, 一般是对增量表而言的,通常是指 该表的同一个业务日期数据中包含前一天或后一天凌晨 附近的 数据或者丢失当天的变更数据。这个问题我们将在“数据漂移的 处理”一节中详细论述。

在批量数据同步中,有些表的数据量随着业务的发展越来越大,如 果按周期全量同步的方式会影响处理效率。在这种情况下,可以选择每 次只同步新变更的增量数据,然后与上一个同步周期获得的全量数据进 行合井,从而获得最新版本的全量数据。 在传统的数据整合方案中,合并技术大多采用 merge 方式 (update+insert ):当前流行的大数据平台基本都不支持 update 操作 ,现 在我们比较推荐的方式是全外连接( full outer join) +数据全量覆盖 重新加载( insert overwrite ),即如日调度,则将当天的增量数据和前一 天的全量数据做全外连接,重新加载最新的全量数据。在大数据量规模 下,全量更新的性能比 update 要高得多。此外,如果担心数据更新错 误问题,可以采用分区方式,每天保持一个最新的全量版本,保留较短 的时间周期(如 3~7 天) 。 另外,当业务系统的表有物理删除数据的操作,而数据仓库需要保 留所有历史数据时,也可以选择这种方式,在数据仓库中永久保留最新 的全量数据快照 。 下面我们以淘宝订单表的具体实例来说明。 淘宝交易订单表,每天新增、变更的增量数据多达几亿条,历史累 计至今的全量数据则有几百亿条,面对如此庞大的数据量,如果每天从 业务系统全量同步显然是不可能的 。 可行的方式是同步当天的增量数 据,并与数据仓库中的前一天全量数据合并,获得截至当天的最新全量 数据

数据漂移的处理 通常我们把从源系统同步进人数据仓库的第一层数据称为 ODS 或 者 stag ing 层数据,阿里巴巴统称为 ODS 。数据漂移是 ODS 数据的一个 顽疾,通常是指 ODS 表的同一个业务日期数据中包含前一天或后一天 凌晨附近的数据或者丢失当天的变更数据。 由于 ODS 需要承接面向历史的细节数据查询需求,这就需要物理 落地到数据仓库的 ODS 表按时间段来切分进行分区存储 ,通常的做法 是按某些时间戳字段来切分,而实际上往往由于时间戳字段的准确性问 题导致发生数据漂移。 通常,时间戳字段分为四类: · 数据库表中用来标识数据记录更新时间的时间戳字段(假设这类 字段叫 modified_time )。 · 数据库日志中用来标识数据记录更新时间的时间戳字段·(假设这 类宇段叫 log_time ) 。 · 数据库表中用来记录具体业务过程发生时间的时 间戳字段 (假设 这类字段叫 proc_time ) 。 · 标识数据记录被抽取到时间的时间戳字段(假设这类字段 叫 extract time ) 。 理论上,这几个时间应该是一致的,但是在实际生产中,这几个时 间往往会出现差异,可能的原因有以下几点 : · 由于数据抽取是需要时间的, extract_ti me 往往 会晚于前三个时

前台业务系统手工订正数据时未更新 modified_time 。 · 由于网络或者系统压力问题, log_time 或者 modified_time 会晚 于 proc_time 。 通常的做法是根据其中的某一个字段来切分 ODS 表,这就导致产 生数据漂移。下面我们来具体看下数据漂移的几种场景。 · 根据 extract_ti me 来获取数据。这种情况数据漂移 的问题最明显 。 . 根据 modified_time 限制。在实际生产中这种情况最常见,但是 往往会发生不更新 modified time 而导致的数据遗漏,或者凌晨 45 .A一T 大数据之路一一阿里巴巴大数据实践 时间产生的数据记录漂移到后一天。 · 根据 log_time 限制。由于网络或者系统压力问题, log_time 会晚 于 proc_time ,从而导致凌晨时间产生的数据记录漂移到后一天。 例如,在淘宝“双 l l ”大促期间凌晨时间产生的数据量非常大, 用户支付需要调用多个接口,从而导致 log_time 晚于实际的支付 时间。

根据 proc_time 限制。仅仅根据 proc_time 限制,我们所获取的 ODS 表只是包含一个业务过程所产生的记录 ,会遗漏很多其他过 程的变化记录,这违背了 ODS 和业务系统保持一致的设计原则 。 处理方法主要有以下两种: ( l )多获取后一天的数据 既然很难解决数据漂移的问题,那么就在 ODS 每个时间分区中向 前、向后多冗余一些数据,保障数据只会多不会少,而具体的数据切分 让下游根据自身不同的业务场景用不同的业务时间 proc_time 来限制 。 但是这种方式会有一些数据误差,例如一个订单是当天支付的,但是第 二天凌晨申请退款关闭了该订单,那么这条记录的订单状态会被更新, 下游在统计支付订单状态时会出现错误。 .... 46 (2 )通过多个时间戳字段限制时间来获取相对准确的数据 · 首先根据 log_time 分别冗余前一天最后 15 分钟的数据和后一天 凌晨开始 15 分钟的数据,并用 modified time 过滤非 当天数据, 确保数据不会因为系统问题而遗漏。 · 然后根据 log_time 获取后一天 15 分钟的数据 z 针对此数据,按 照主键根据 log_time 做升序排列去重。因为我们需要获取的是最

我们在处理“双 i 1 ”交易订单时发现,有一大批在 1 1 月川日第 3 章数据同步寸一 23:59:59 左右支付的交易订单漂移到了 12 日 。主要原因是用户下单支 付后系统需要调用支付宝的接口而有所延迟,从而导致这些订单最终生 成的时间跨天了。即 modified_time 和 log_time 都晚于 proc_time 。 如果订单只有一个支付业务过程,则可以用支付时间来限制就能获 取到正确的数据。但是往往实际订单有多个业务过程 : 下单、支付、成 功,每个业务过程都有相应的时间戳字段,并不只有支付数据会漂移。 如果直接通过多获取后一天的数据,然后限制这些时间,则可以获 取到相关数据,但是后一天的数据可能已经更新多次,我们直接获取到 的那条记录已经是更新多次后的状态,数据的准确性存在一定的问题。

因此,我们可以根据实际情况获取后一天 15 分钟的数据,并限制 多 个业务过程的时间戳字段(下单、支付、成功)都是“双 l l ”当天 的,然后对这些数据按照订单的 modified_time 做升序排列,获取每个 订单首次数据变更的那条记录。 此外,我们可以根据 log_time 分别冗余前一天最后 15 分钟的数据 和后一天凌晨开始 15 分钟的数据,并用 modified_time 过滤非 当天数据, 针对每个订单按照 log_time 进行降序排列 ,取每个订单当天最后一次数 据变更的那条记录。 最后将两份数据根据订单做全外连接,将漂移数据回补到当天数据中。

如图 4.7 所示,调度系统中的各类任务互相依赖,形成一个典型的 有向无环图。在传统的数据仓库系统中,很多是依靠 Crontab 定时任务 功能进行任务调度处理的。这种方式有很多弊端:①各任务之间的依赖 基于执行时间实现,容易造成前面的任务未结束或失败而后面的任务已 ‘ 58第 4 章离线数据开发丁一 经运行;②任务难以并发执行,增加了整体的处理时间:③无法设置任务 优先级;④任务的管理维护很不方便,无法进行执行效果分析等。

任务状态机模 型和工作流状态机模型。 任务状态机模型是针对数据任务节点在整个运行生命周期的状态 定义 ,总共有 6 种状态,状态之间的转换逻辑如图 4.9 所示。

工作流状态机模型是针对数据任务节点在调度树中生成的工作流 运行的不同状态定义,共有 5 种状态,其关系如图 4.10 所示。

调度引擎工作原理 调度引擎( Phoenix Engine )基于以上两个状态机模型原理,以事 件驱动的方式运行,为数据任务节点生成实例,并在调度树中生成具体 执行的工作流。任务节点实例在工作流状态机、任务状态机和事件处理 器之间转换,其中调度引擎只涉及任务状态机的未运行和等待运行两种 状态,其他 5 种状态存在于执行引擎中。 调度引擎工作原理示意图如图 4.1 l 所示。 • Async Dispatcher :异步处理任务调度。 • Sync Dispatcher :同步处理任务调度。 • Task 事件处理器:任务事件处理器,与任务状态机交互 。 • DAG 事件处理器:工作流事件处理器,与工作流状态机交互。 一个 DAG 事件处理器包含若干个 Task 事件处理器 。

执行引擎工作原理

执行引擎逻辑架构图

按照数据的延迟情况,数据时效性一般分为三种(离线、准实时、 实时) : ·离线:在今天( T)处理 N 天前 ( T-N, N > I )的数据,延迟时 69 "'一寸大数据之路一一阿里巴巴大数据实践 间粒度为天。 ·准实时 :在当前小时( H)处理 N 小时前 ( H-N, N>O ,如 0.5 小时、 l 小时等)的数据,延迟时间粒度为小时。 ·实时 :在当前时刻处理当前的数据,延迟时间粒度为秒; 离线和准实时都可以在批处理系统中实现(比如 Hadoop 、 MaxCompute 、 Spark 等系统),只是调度周期不一样而己,而实时数据 则需要在流式处理系统中完成。简单来说,流式数据处理技术是指业务 系统每产生一条数据,就会立刻被采集并实时发送到流式任务中进行处 理,不需要定时调度任务来处理数据。

在流式计算技术中,需要各个子系统之间相互依赖形成一条数据处 理链路,才能产出结果最终对外提供实时数据服务。在实际技术选型时 , 可选的开源技术方案非常多,但是各个方案的整体架构是类似的 , 只是 各个子系统的实现原理不太一样。另外,流式技术架构中的系统跟离线 处理是有交叉的,两套技术方案并不是完全独立的,并且在业界中有合 并的趋势。 各个子系统按功能划分的话,主要分为以下几部分。

1 . 数据采集 数据的源头,一般来自于各个业务的日志服务器(例如网站的浏览 行为日志、订单的修改日志等),这些数据被实时采集到数据中间件中, 供下游实时订阅使用。 2 . 数据处理 数据被采集到中间件中后,需要下游实时订阅数据,并拉取到流式 计算系统的任务中进行加工处理。这里需要提供流计算引擎以支持流式 71 All一寸大数据之路一一阿里巴巴大数据实践 任务的执行。 3 . 数据存储

数据被实时加工处理(比如聚合、清洗等)后,会写到某个在线服 务的存储系统中,供下游调用方使用。这里的写操作是增量操作,并且 是源源不断的。 4 . 数据服务 在存储系统上会架设一层统一的数据服务层(比如提供 HSF 接口、 HTTP 服务等 ),用于获取实时计算结果。 整体技术架构如图 5.2 所示。

从图 5.2 可以看出,在数据采集和数据服务部分实时和离线是公用 的,因为在这两层中都不需要关心数据的时效性。这样才能做到数据源 的统一,避免流式处理和离线处理的不一致。

的数据都来自于业务服务器,从所采集的数据种类来看,主要可以划分 为两种: ·数据库变更日志,比如 MySQL 的 binlog 日 志、 HBase 的 hlog 日 志、 OceanBase 的变更日志、 Oracle 的变更日志等。 · 引擎访问日志,比如用户访问网站产生的 Apache 引 擎日志、搜 索引擎的接口查询日志等。 不管是数据库变更日志还是引擎访问日志,都会在业务服务器上落 地成文件,所以只要监控文件的内容发生变化,采集工具就可以把最新 的数据采集下来 。一般情况下,出于吞吐量以及系统压力上的考虑,并 不是新增一条↓己录就采集一次,而是基于下面的原则,按批次对数据进 行采集 。 · 数据大小限制:当达到限制条件时,把目前采集到的新数据作为 一批(例如 512KB 写一批)。 · 时间阐值限制:当时间达到 一定条件时,也会把目前采集到的新 数据作为一批,避免在数据量少的情况下一直不采集(例如 30 秒写一批)。 只要上面的其中一个条件达到了,就会被作为一批新数据采集到数 据中间件中 。 这两个条件的参数需要根据业务的需求来设定,当批次采 集频繁时,可以降低延时,但必然会导致吞吐量下降。 对于采集到的数据需要一个数据交换平台分发给下游,这个平台就 是数据中间件。数据中间件系统有很多实现方式,比如开源的系统有 据中间件中 。 这两个条件的参数需要根据业务的需求来设定,当批次采 集频繁时,可以降低延时,但必然会导致吞吐量下降。 对于采集到的数据需要一个数据交换平台分发给下游,这个平台就 是数据中间件。数据中间件系统有很多实现方式,比如开源的系统有 Kafka ,而阿里巴巴集团内部用得比较多的是 TimeTunnel (原理和 Kafka 类似),还有 MetaQ 、 Notify 等消息系统。 从图 5 .3 可以看出,消息系统是数据库变更节点的上游,所以它的 延时比数据中间件低很多,但是其支持的吞吐量有限。因此,消息系统 一般会用作业务数据库变更的消息中转,比如订单下单、支付等消息。 对于其他较大的业务数据(每天几十 TB 的容量), 一般会通过数据中 间件系统来中转,虽然它的延时在秒级,但是其支持的吞吐量高。消息 系统和数据中间件的性能对比如表 5.1 所示。

5.2.2 数据处理 实时计算任务部署在流式计算系统上,通过数据中间件获取到实时 源数据后进行实时加工处理。在各大互联网公司中,有各种开源的和非 开源的流计算引擎系统在使用。在业界使用比较广泛的是 Twitter 开源 ‘ 74第 5 章实时技术寸一 的 Storm 系统、雅虎开源的 S4 系统、 Apache 的 Spark Streaming ,以及 最近几年兴起的 Flink。这些系统的整体架构大同小异,但是很多细节 上的实现方式不太一样,适用于不同的应用场景。 在阿里巴巴集团内使用比较多的是 阿里云提供的 StreamCompute 系统,作为业界首创的全链路流计算开发平台,涵盖了从数据采集到数 据生产各个环节,力保流计算开发严谨、可靠。其提供的 SQL 语义的 流式数据分析能力( StreamSQL ),让流数据分析门槛不再存在。它在 Storm 的基础上包装了 一层 SQL 语义,方便开发人员通过写 SQL 就可 以实现实时计算,不需要关心其中的计算状态细节,大大提高了开发效

下面以 Storm 为例,简单讲一下流数据处理的原理。实时应用的整 个拓扑结构是一个有向无环图(详情可参考 Apache Storm 的官网: http ://storm.apache.org/ index.html ),如图 5.4 所示。

图 5 .4 实时应用的整个拓扑结构图 • spou t :拓扑的输人,从数据中间件中读取数据,并且根据自定 义的分发规则发送给下游的 bolt ,可以有多个输人源。 • bolt :业务处理单元,可以根据处理逻辑分为多个步骤,其相互 之间的数据分发规则也是自定义的。 实时数据处理应用出于性能考虑,计算任务往往是多线程的。 一般 会根据业务主键进行分桶处理,并且大部分计算过程需要的数据都会放 在内存中,这样会大大提高应用的吞吐量。当然,为了避免内存溢出, 内存中过期的数据需要定时清理,可以按照 LRU (最近最少使用)算 法或者业务时间集合归类清理(比如业务时间属于 T- 1 的,会在今天凌 晨进行清理 )。 下面就实时任务遇到的几个典型问题进行讲解。 1.去重指标 在 BI (商业智能)统计类实时任务中 ,对于资源的消耗有一类指 标是非常高的,那就是去重指标。由于实时任务为了追求处理性能,计 算逻辑一般都是在内存中完成的,中间结果数据也会缓存在内存中,这 就带来了内存消耗过多的问题。在计算去重时,势必要把去重的明细数 据保存下来,当去重的明细数据达到上亿甚至几十亿时,内存中放不下 了,怎么办?这时需要分两种情况去看 : · 精确去重。在这种情况下,明细数据是必须要保存下来的,当遇 到内存问题时,可以通过数据倾斜来进行处理,把一个节点的内 存压力分到多个节点上。 ·模糊去重。在去重的明细数据量非常大,而业务的精度要求不高 的情况下,可以使用相关的去重算法,把内存的使用 量降到千分 之一甚至万分之一 ,以提高内存的利用率。

( I )布隆过滤器 该算法是位数组算法的应用 , 不保存真实的明细数据,只保存明细 数据对应哈希值的标记位。当然,会出现哈希值碰撞的情况,但是误差 率可以控制,计算出来的去重值比真实值小。采用这个算法存储 l 亿条 数据只需要 100 多 MB 的 空间。 适用场景 : 统计精度要求不高,统计维度值非常多的情况。比如统 计全网各个商家的 UV 数据,结果记录数达到上千万条。因为在各个维 度之间,布隆过滤器是可以共用的。 (2 )基数估计 该算法也是利用哈希的原理,按照数据的分散程度来估算现有数集 的边界,从而得出大概的去重值总和。这里估算的去重值可能比真实值 大,也可能比真实值小。采用这个算法存储 1 亿条数据只需要几 KB 的 内存。 适用场景:统计精度要求不高,统计维度非常粗的情况。比如整个 大盘的 UV 数据,每天的结果只有一条记录。基数估计在各个维度值之 间不能共用,比如统计全天小时的 UV 数据,就需要有 24 个基数估计 对象,因此不适合细粒度统计的场景。 这两个算法可以在网上搜索到具体的实现细节,这里就不细讲了。

2 . 数据倾斜 数据倾斜是 ETL 中经常遇到的问题,比如计算一天中全网访客数 或者成交额时,最终的结果只有一个,通常应该是在一个节点上完成相 关的计算任务。在数据量非常大的时候,单个节点的处理能力是有限的, 必然会遇到性能瓶颈。这时就需要对数据进行分桶处理 , 分桶处理和离 线处理的思路是一样的。 ( 1)去重指标分桶 通过对去重值进行分桶 Hash ,相同 的值一定会被放在同一个桶 中 去重,最后再把每个桶里面的值进行加和就得到总值,这里利用了每个 桶的 CPU 和内存资源。 (2 )非去重指标分桶 数据随机分发到每个桶中,最后再把每个桶的值汇总,主要利用的 是各个桶的 CP U 能力

数据存储 实时任务在运行过程中,会计算很多维度和指标,这些数据需要放 在一个存储系统中作为恢复或者关联使用。其中会涉及三种类型的数据: ·中间计算结果一一在实时应用处理过程中,会有一些状态的保存 (比如去重指标的明细数据),用于在发生故障时,使用数据库 中的数据恢复内存现场。 ·最终结果数据一一指的是通过 ETL 处理后的实时结果数据,这些 数据是实时更新的,写的频率非常高,可以被下游直接使用。 · 维表数据一一在离线计算系统中,通过同步工具导人到在线存储 系统中,供实时任务来关联实时流数据。后面章节中会讲到维表 的使用方式。 数据库分为很多种类型,比如关系型数据库、列式数据库、文档数 据库等 , 那么在选择实时任务所使用的数据库时应该注意哪些特征呢? 前面提到实时任务是多线程处理的,这就意味着数据存储系统必须 能够比较好地支持多并发读写,并且延时需要在毫秒级才能满足实时的 性能要求。在实践中,一般使用 HBase 、 Tair 、 MongoDB 等列式存储系 统。由于这些系统在写数据时是先写内存再落磁盘,因此写延时在毫秒 级:读请求也有缓存机制,重要的是多并发读时也可以达到毫秒级延时。 但是这些系统的缺点也是比较明显的,以 HBase 为例, 一张表必 须要有 rowkey ,而 rowkey 是按照 ASCII 码来排序的,这就像关系型数 据库的索引一样, rowkey 的规则限制了读取数据的方式。如果业务方 需要使用另一种读取数据的方式,就必须重新输出 rowkey。从这个角 度来看, HBase 没有关系型数据库方便。但是 HBase 的一张表能够存储 几 TB 甚至几十 TB 的数据,而关系型数据库必须要分库分表才能实现 这个量级的数据存储。因此,对于海量数据的实时计算,一般会采用非 关系型数据库,以应对大量的多并发读写。

下面介绍在数据统计中表名设计和 rowkey 设计的一些实践经验。 1 . 表名设计 设计规则:汇总层标识+数据域+主维度+时间维度 例如: dws_trd_s lr_dtr ,表示汇总层交易数据,根据卖家( sir )主维度 +O 点截至当日( dtr ) 进行统计汇总。 这样做的好处是,所有主维度相同的数据都放在一张物理表中,避 免表数量过多,难以维护。另外,可以从表名上直观地看到存储的是什 么数据内容,方便排查问题。 2 . 「owkey 设计 设计规则: MD5 +主维度+维度标识+子维度 1 +时间维度+ 子维度 2 例如:卖家 ID 的 MD5 前四位+卖家 ID+ app + 一级类目 ID+ ddd +二级类目 ID 。 以 MD5 的前四位作为 rowkey 的第一部分,可以把数据散列,让服 务器整体负载是均衡的,避免热点问题。在上面的例子中,卖家 ID 属 于主维度 ,在查数据时是必传的。每个统计维度都会生成一个维度标识 以便在 rowkey 上做区分。,

在流式数据模型中,数据模型整体上分为五层。 ‘ 80第 5 章实时技术寸一

  1. ODS 层 眼离线系统的定义一样, ODS 层属于操作数据层,是直接从业务 系统采集过来的最原始数据,包含了所有业务的变更过程,数据粒度也 是最细的。在这一层,实时和离线在源头上是统一的 , 这样的好处是用 同 一份数据加工出来的指标,口径基本是统一的,可以更方便进行实时 和离线间数据比对。例如:原始的订单变更记录数据 、 服务器引擎的访 问日志。 2 . DWD 层 DWD 层是在 OD S 层基础上,根据业务过程建模出来的实时事实明 细层,对于访问日志这种数据(没有上下文关系,并且不需要等待过程 的记录),会回流到离线系统供下游使用,最大程度地保证实时和离线 数据在 ODS 层和 DWD 层是一致的。例如 : 订单的支付明细表、退款 明细表、用户的访问日志明细表。
  2. DWS 层 订阅明细层的数据后,会在实时任务中计算各个维度的汇总指标。 如果维度是各个垂直业务线通用的,则会放在实时通用汇总层,作为通 用的数据模型使用。比如电商网站的卖家粒度,只要涉及交易过程,就 会跟这个维度相关,所以卖家维度是各个垂直业务的通用维度,其中的 汇总指标也是各个业务线共用的。例如:电商数据的几大维度的汇总表 ( 卖 家、商品、买家)。
  3. ADS 层 个性化维度汇总层,对于不是特别通用的统计维度数据会放在这一 层 中,这里计算只有自身业务才会关注的维度和指标,眼其他业务线一 般没有交集,常用于一些垂直创新业务中。例如:手机淘宝下面的某个 爱逛街、微淘等垂直业务。
  4. DIM 层 实时维表层的数据基本上都是从离线维表层导出来的,抽取到在线 系统中供实时应用调用。这一层对实时应用来说是静态的,所有的 ETL 处理工作会在离线系统中完成。维表在实时应用的使用中跟离线稍有区 别,后面章节中会详细说明。例如:商品维表、卖家维表、买家维表、 类目维表。 下面通过简单的例子来说明每一层存储的数据。 • OD S 层:订单粒度的变更过程, 一笔订单有多条记录。 • DWD 层:订单粒度的支付记录,一笔订单只有一条记录。 • DWS 层 : 卖家的实时成交金额,一个卖家只有一条记录,并且 指标在实时刷新。 • AD S 层 : 外卖地区的实时成交金额,只有外卖业务使用。 • DIM 层:订单商品类目和行业的对应关系维表。 整体的数据流向如图 5.5 所示。

其中, OD S 层到 DIM 层的 ETL 处理是在离线系统中进行的,处理 完成后会同步到实时计算所使用的存储系统。 ODS 层和 DWD 层会放在 数据中间件中,供下游订阅使用。而 DWS 层和 ADS 层会落地到在线 存储系统中,下游通过接口调用的形式使用。 在每一层中,按照重要性划分为 PO 、 Pl 、 P2 、 P3 等级, PO 属于最 高优先级保障。根据不同的优先级给实时任务分配不同的计算和存储资 源,力求重要的任务可以得到最好的保障。 5.3.2 多流关联 在流式计算中常常需要把两个实时流进行主键关联,以得到对应的 实时明细表。在离线系统中两个表关联是非常简单的,因为离线计算在 任务启动时已经可以获得两张表的全量数据,只要根据关联键进行分桶 关联就可以了。但流式计算不一样,数据的到达是一个增量的过程,并 且数据到达的时间是不确定的和无序的,因 此在数据处理过程中会涉及 中间状态的保存和恢复机制等细节问题。 比如 A 表和 B 表使用 ID 进行实时关联,由于无法知道两个表的到 达顺序,因此在两个数据流的每条新数据到来时,都需要到另外一张表 中进行查找。如 A 表的某条数据到达,到 B 表的全量数据中查找,如 果能查找到,说明可以关联上,拼接成一条记录直接输出到下游 ; 但是 如果关联不上,则需要放在内存或外部存储中等待,直到 B 表的记录 也到达。多流关联的一个关键点就是需要相互等待,只有双方都到达了, 才能关联成功。 下面通过例子(订单信息表和支付信息表关联)来说明,如图 5 . 6 所示。

维表使用 在离线系统中,一般是根据业务分区来关联事实表和维表的,因为 在关联之前维表的数据就已经就绪了。而在实时计算中,关联维表一般 会使用当前的实时数据( T)去关联 T-2 的维表数据,相当于在 T 的数 据到达之前需要把维表数据准备好,并且一般是一份静态的数据。 为什么在实时计算中这么做呢?主要基于以下几点的考虑。 1 . 数据无法及时准备好 当到达零点时,实时流数据必须去关联维表(因为不能等待,如果 等就失去了实时的特性),而这个时候 T- 1 的维表数据一般不能在零点 马上准备就绪(因为 T-1 的数据需要在 T 这一天加工生成),因此去关 联巨2 维表,相当于在 T-1 的一天时间里加工好 T-2 的维表数据。 2. 无法准确获取全量的最新数据 维表一般是全量的数据,如果需要实时获取到当天的最新维表数 据,则需要 T-1 的数据+当天变更才能获取到完整的维表数据。也就是 说,维表也作为一个实时流输入,这就需要使用多流实时关联来实现。 但是由于实时数据是无序的并且到达时间不确定,因此在维表关联上有 歧义。

( 1)剥离计算资源 调用者调用接口获取的数据,有些指标需要多天数据的聚合,比如 最近 7 天访客浏览量、最近 365 天商品最低价格等;有些指标还包含一 些复杂的计算逻辑,比如成交回头率,其定义为在统计时间周期内,有 两笔及以上成交父订单的买家数除以有成交父订单的买家数。 如此复杂的计算逻辑,如果放在每次调用接口时进行处理,其成本 是非常高 的。因此剥离复杂的计算统计逻辑,将其全部交由底层的数据 公共层进行处理,只保留核心的业务处理逻辑。详细内容请参见第 9 章。 (2 )查询资源分配 查询接口分为两种: Get 接口,只返回一条数据; List 接口,会返 回多条数据。一般来说, Get 查询基本都转换为 KV 查询,响应时间比 较短,或者说查询代价比较小。而 List 查询的响应时间相对较长,且返 回记录数比较多,这就增加了序列化以及网络传输的成本,查询代价肯 定会更高一些。 假如将 Get 、 List 请求都放在同一个线程池中进行查询,那么查询 效率会怎么样?想象一下如图 6.11 所示的场景,在高速公路上,行车 道以及超车道全部都有大卡车在慢速行驶,后面的小轿车只能慢慢等 待,并祈祷前方路段能少一些大卡车。这样整个路段的行车速度就降了 下来,车流量也会下降许多。同理,虽然 Get 请求的真正查询耗时很短, 但是会在队列等待上消耗大量的时间,这样整体的 QPS 会很不理想。

为此,我们设计了两个独立的线程池: Get 线程池和 List 线程池, 分别处理 Get 请求和 List 请求,这样就不会因为某些 List 慢查询,而影 响到 Get 快查询。系统的 QPS 比之前提升许多。回到上文的类比中, 在高速公路上大卡车只行驶在最右车道上,小轿车行驶在其他车道上 , 这样整个路段也会畅通许多,如图 6.12 所示。

List 查询的响应时间相对较长,所以 List 线程池设置的最大运行任 务数就稍微多一些。另外,由于超时的限制, List 线程池的等待队列不 宜过长。具体的参数设置,可以根据压力测试的结果评估出来。后期, 也可以根据线上调用日志的统计,比如 List 请求与 Get 请求的比例来进 行优化调整。 (3 )执行计划优化 ①查询拆分。举个例子,顾客去肯德基点餐,需要一个汉堡、 一包 薯条,再加一杯饮料。他可以先点个汉堡,拿到后再点包薯条,最后再 ‘ 104第 6 章数据服务寸一 点杯饮料 ,是不是很浪费时间?为了节约时间,他可以叫上朋友来帮忙, 每个人负责一样,同时去点餐。这样是快了很多 ,但是需要顾客付出额 外的成本。那么现实中应该是怎么样的呢?顾客直接眼服务员说需要这 些,服务员可以分工协作 ,最后统一放在餐盘中,告知顾客可以取餐了。 查询接口同样如此,接口暴露给调用者的指标都是逻辑宇段,调用 者不用关注这些逻辑字段对应的是哪张物理表的哪个物理宇段。比如调 用者调用了 A,B,C 三个指标,这些指标分别在三张物理表中,引擎层会 将调用者的请求拆分成三个独立的查询,分别去三张物理表中查询,且 这些查询是并发执行的。查询结束后 ,引擎层会将三个查询的结果汇总 至lj 一起返回给调用者,这样最大程度地降低了调用者的调用成本,并能 保证查询性能(见图 6.13 )

查询优化,就是分析用户请求中的 SQL 语句,将符合条件的 List 105 All一寸大数据之路-一阿里巴巴大数据实践 查询转换为 Get 查询,从而提高性能。具体的步骤是: ·解析 SQL 语句中的 WHERE 子句,提取出筛选字段以及筛选条 件。 ·假如筛选字段中包含了该逻辑表的所有主键,且筛选条件都为 equal ,则说明主键都已经确定为固定值,返回记录数肯定为 l 条。在这种场景中, List 查询就转换为 Get 查询。

3 . 查询能力 ( 1)合并查询 数据产品的有些场景,虽然表面上看只是展现几个数字而己 ,但是 ‘ 108第 6 章数据服务丁一 后台的处理逻辑其实并不简单。举例来说,展现某一日卖家的支付金额, 有个日期选择框可以任意选择日期。日期为今天时,展现的是实时数据 (从零点截至当前的成交金额) :日期为昨天时 ,展现的就是离线数据(最 近 l 天的成交金额)。其背后的复杂性在于: · 在数据公共层中,实时数据是在流计算平台 Galaxy 上进行计算 的,结果保存在 HBase 中;而离线数据的计算和存储都是在 MaxCompute 中进行的。这就造成了实时数据与离线数据存储在 两个数据调、中,调用者的查询方式完全不同。 · 离线数据的产出时间,取决于上游任务的执行时间,以及当前平 台的资源情况。所以其产出时间是无法估算的,有可能 3 : 00 产 出,也有可能延迟到 6: 00。在昨天的离线任务产出之前,其前 台展现的数字只能来源于实时数据。 · 出于对性能和成本的考虑,实时作业做了 一 些折中,去重时,视 情况可能使用 一些不精准的去重算法,这就导致实时数据的计算 结果与离线数据存在一些差异。 综上所述,离线数据最准确,需要优先使用离线数据。如果离线数 据还未产出,则改用实时数据。所以在简单的数字背后,需要使用者清 晰地了解上述三点。 为了降低这种场景的复杂性,我们设计了 一 种新的语法 一- REPLACE ,如图 6.16 所示。

REPLACE 的效果就是用上边 SQL 的结果,根据 replace_key 去替 换下边 SQL 的结果。比如上述 SQ L ,上边的查询是取离线数据,下边 109 ...一寸’大数据之路一一阿里巴巴大数据实践 的查询是取实时数据,那么结果就是优先取离线数据,如果没有再去取 实时数据。 调用者使用这样的语法,就可以实现离线数据替换实时数据的功 能,不再需要考虑离线数据未产出等问题。

(2 )推送服务 有些数据产品需要展现实时指标,为了追求数据的实时性,都是轮 询请求最新数据。轮询的间隔时间设置很重要,如果设置间隔时间较长, 用户体验会不太好;如果设置很短,对服务器的请求压力会非常大,从 而影响整体性能。另外,这种轮询请求的方式,其实很大部分时间是在 浪费资源,因为有可能后台的数据根本没有更新,而前端却一直在请求。 那能不能换种方式呢?监昕数据提供者,新数据产生时能够及时知道, 并且告知用户,为此“推送”应运而生。推送服务很好地解决了数据更 新的实时性问题,同时也减少了对服务器的请求压力。其主要从网络、 内存、资源等方面做了如下设计: ·对消息生产者进行监听。比如监听消息源 TT , 一天的消息量可 能有几百亿,但实际在线用户关心的可能就几亿甚至更少,所以 并不是所有的消息都需要关心,做好消息过滤是非常必要的。 ·过滤后的消息量也是可观的,推送服务无法满足高效的响应需 求,这就需要考虑将符合条件的消息放置在临时队列中,但对于 有锁的队列,存在竞争则意味着性能或多或少会有些下降,所以 采用无锁的队列 Disruptor 来存放消息是最佳的选择。在采用 Disruptor 的情况下,推送应用也考虑到可以对重要的消息配置单 独的队列单线程运转,以提高性能。 ·消息 的推送必须基于 Socket 来实现, Netty 在性能表现上比较优 秀,采用基于高性能异步事件的网络通信框架 Netty 是我们的最 终选择。不同事件采用不同的监昕处理,职责分明也是提高性能 的基础。 ·推送应用是典型的 IO 密集型系统,在采用多线程解决性能问题 的同时,也带来了上下文切换的损耗。在注册消息向 Filter 广播 时,采用协程方式可以大大减少上下文切换,为性能的提高做出 相应的贡献。 · 从业务角度出发,主题也会存在重要级别或者优先级,适当地控 制线程数以及流量,为某些重要的业务消息节约服务器资源也是 备选方案。 ·缓存的利用在推送应用中多处体现。例如对注册的在线用户信息 做本地缓存,可以极大地提高读性能。 ·对突发事件的推送也有针对性地做了很多工作 。 比如过滤服务器 异常重启时,在线用户信息需要重新向该过滤服务器投递,但每 条用户信息才几百字节,如果逐条投递,则会造成高流量带宽的 浪费,所以批量投递甚至打包投递会大大降低网络开销。

大促前的优化工作在实时计算中显得尤为重要,如果吞吐量跟不上 的话,也就失去了实时的特性。吞吐量不佳原因非常多,有些眼系统资 源有关,有些眼实现方式有关 , 以下几点是实时任务优化中经常需要考 虑的要素。 ( 1)独占资源和共享资源的策略 在一台机器中,共享资源池可以被多个实时任务抢占,如果一个任 务在运行时 80%以上的时间都需要去抢资源,这时候就需要考虑给它分 配更多的独占资源,避免抢不到 CPU 资源导致吞吐量急剧下降。 (2 )合理选择缓存机制,尽量降低读写库次数 内存读写性能是最好的,根据业务的特性选择不同的缓存机制,让 最热和最可能使用的数据留在内存中,读写库次数降低后,吞吐量自 然 就上升了。 (3 )计算单元合并,降低拓扑层级 拓扑结构层级越深 , 性能越差,因为数据在每个节点间传输时, 大 部分是需要经过序列化和反序列化的,而这个过程非常消耗 CPU 和时间 。 (4 )内存对象共享,避免字符拷贝 在海量数据处理中,大部分对象都是以字符串形式存在的,在不同 线程间合理共享对象,可以大幅降低字符拷贝带来的性能消耗,不过要 注意不合理使用带来的内存溢出问题。 (5 )在高吞吐量和低延时间取平衡 高吞吐量和低延时这两个特性是一对矛盾体,当把多个读写库操作 或者 ACK 操作合并成一个时,可以大幅降低因为网络请求带来的消耗, 不过也会导致延时高一些,在业务上衡量进行取舍。

SmartDQ 不能满足个性化的取数业务场景 ,可以使用 Lego 。 Lego 采用插件化方式开发服务, 一类需求开发一个插件,目 前 一共生产 5 个插件。为了避免插件之间相互影响,我们将插件做成微服 务,使用 Docker 做隔离。 实时数据服务 iPush 主要提供 WebSocket 和 long polling 两种方式, 其应用场景主要是商家端实时直播。在“双 l l ” 当天 ,商家会迫不及 ‘ 96第 6 章数据服务丁一 待地去刷新页面,在这种情况下 long polling 会给服务器带来成倍的压 力。而 WebSocket 方式,可以在这种场景下,有效地缓解服务器的压力, 给用户带来最实时的体验。 uTimi 昭 主要提供即时任务和定 时任务两种模式,其主要应用场景 是满足用户运行大数据量任务的需求。