[转]携程实时用户数据采集与分析系统
随着移动互联网的兴起,特别是近年来,智能手机、pad 等移动设备凭借便捷、高效的特点风靡全球,同时各类 APP 的快速发展进一步降低了移动互联网的接入门槛,越来越多的网民开始从传统 PC 转移至移动终端上。但传统的基于 PC 网站和访问日志的用户数据采集系统已经无法满足实时分析用户行为、实时统计流量属性和基于位置服务(LBS)等方面的需求。
我们针对传统用户数据采集系统在实时性、吞吐量、终端覆盖率等方面的不足,分析了在移动互联网流量剧增的背景下,用户数据采集系统的需求,研究在多种访问终端和多种网络类型的场景下,用户数据实时、高效采集的方法,并在此基础上设计和实现实时、有序和健壮的用户数据采集系统。此系统基于 Java NIO 网络通信框架(Netty)和分布式消息队列(Kafka)存储框架实现,其具有实时性、高吞吐、通用性好等优点。
一个典型的数据采集分析统计平台,对数据的处理,主要由如下五个步骤组成:
其中,数据采集步骤是最核心的问题,数据采集是否丰富、准确和实时,都直接影响整个数据分析平台的应用的效果。本论文关注的步骤主要在数据采集、数据传输和数据建模存储这三部分。
为满足数据采集服务实时、高效性、高吞吐量和安全性等方面的要求,同时能借鉴互联网大数据行业一些优秀开源的解决方案,所以整个系统都将基于 Java 技术栈进行设计和实现。整个数据采集分析平台系统架构如下图所示:
其中整个平台系统主要包括以上五部分:客户端数据采集 SDK 以 Http(s)/Tcp/Udp 协议根据不同的网络环境按一定策略将数据发送到 Mechanic(UBT-Collector) 服务器。服务器对采集的数据进行一系列处理之后将数据异步写入 Hermes(Kafka) 分布式消息队列系统。为了关联业务服务端用户业务操作埋点、日志,业务服务器需要获取由客户端 SDK 统一生成的用户标识(C-GUID),然后业务服务器将用户业务操作埋点、日志信息以异步方式写入 Hermes(Kafka) 队列。
最后数据消费分析平台,都从 Hermes(Kafka) 中消费采集数据,进行数据实时或者离线分析。其中 Mechanic(UBT-Collector) 系统还包括对采集数据和自身系统的监控,这些监控信息先写入 Hbase 集群,然后通过 Dashboard 界面进行实时监控。
要满足前面提到的高吞吐、高并发和多协议支持等方面的要求。我们调研了几种开源异步 IO 网络服务组件(如 Netty、MINI、xSocket),用它们和 Nginx Web 服务器进行了性能对比,决定采用 Netty 作为采集服务网络组件。下面对它进行一些概要介绍:Netty 是一个高性能、异步事件驱动的 NIO 框架,它提供了对 TCP、UDP 和文件传输的支持,Netty 的所有 IO 操作都是异步非阻塞的,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
Netty 的优点有:
- 功能丰富,内置了多种数据编解码功能、支持多种网络协议。
- 高性能,通过与其它主流 NIO 网络框架对比,它的综合性能最佳。
- 可扩展性好,可通过它提供的 ChannelHandler 组件对网络通信方面进行灵活扩展。
- 易用性,API 使用简单。
- 经过了许多商业应用的考验,在互联网、网络游戏、大数据、电信软件等众多行业得到成功商用。
Netty 采用了典型的三层网络架构进行设计,逻辑架构图如下:
第一层:Reactor 通信调度层。该层的主要职责就是监听网络的连接和读写操作,负责将网络层的数据读取到内存缓冲区中,然后触发各种网络事件,例如连接创建、连接激活、读事件、写事件等,将这些事件触发到 Pipeline 中,再由 Pipeline 充当的职责链来进行后续的处理
第二层:职责链 Pipeline 层。负责事件在职责链中有序的向前(后)传播,同时负责动态的编排职责链。Pipeline 可以选择监听和处理自己关心的事件。
第三层:业务逻辑处理层,一般可分为两类:a. 纯粹的业务逻辑处理,例如日志、订单处理。b. 应用层协议管理,例如 HTTP(S) 协议、FTP 协议等。
我们都知道影响网络服务通信性能的主要因素有:网络 I/O 模型、线程(进程)调度模型和数据序列化方式。
在网络 I/O 模型方面,Netty 采用基于非阻塞 I/O 的实现,底层依赖的是 JDK NIO 框架的 Selector。
在线程调度模型方面,Netty 采用 Reactor 线程模型。常用的 Reactor 线程模型有三种,分别是:
- Reactor 单线程模型:Reactor 单线程模型,指的是所有的 I/O 操作都在同一个 NIO 线程上面完成。对于一些小容量应用场景,可以使用单线程模型。
- Reactor 多线程模型:Rector 多线程模型与单线程模型最大的区别就是有一组 NIO 线程处理 I/O 操作。主要用于高并发、大业务量场景。
- 主从 Reactor 多线程模型:主从 Reactor 线程模型的特点是服务端用于接收客户端连接的不再是一个单独的 NIO 线程,而是一个独立的 NIO 线程池。利用主从 NIO 线程模型,可以解决一个服务端监听线程无法有效处理所有客户端连接的性能不足问题。Netty 线程模型并非固定不变的,它可以支持三种 Reactor 线程模型。
在数据序列化方面,影响序列化性能的主要因素有:
- 序列化后的码流大小(网络带宽占用)。
- 序列化和反序列化操作的性能(CPU 资源占用)。
- 并发调用时的性能表现:稳定性、线性增长等。
Netty 默认提供了对 Google Protobuf 二进制序列化框架的支持,但通过扩展 Netty 的编解码接口,可以实现其它的高性能序列化框架,例如 Avro、Thrift 的压缩二进制编解码框架。
通过对 Netty 网络框架的分析研究以及对比测试(见后面的可行性分析测试报告)可判断,基于 Netty 的数据采集方案能解决高数据吞吐量和数据实时收集的难点。
对一些明感的采集数据,需要在数据传输过程中进行加密处理。目前存在的问题是,客户端采集代码比较容易被匿名用户获取并反编译(例如 Android、JavaScript),导致数据加密的算法和密钥被用户窃取,较难保证数据的安全性。根据加密结果是否可以被解密,算法可以分为可逆加密和不可逆加密(单向加密)。具体的分类结构如下:
密钥:对于可逆加密,密钥是加密解算法中的一个参数,对称加密对应的加解密密钥是相同的;非对称加密对应的密钥分为公钥和私钥,公钥用于加密,私钥用于解密。私钥是不公开不传送的,仅仅由通信双方持有保留;而公钥是可以公开传送的。非对称密钥还提供一种功能,即数字签名。通过私钥进行签名,公钥进行认证,达到身份认证的目的。
根据数据采集客户端的特点,对于采集数据使用对称加密算法是很明智的选择,关键是要保证对称密钥的安全性。目前考虑的方案主要有:
- 将加解密密钥放入 APP 中某些编译好的 so 文件中,如果是 JavaScript 采集的话,构造一个用 C 编写的算法用于生成密钥,然后借助 Emscripten 把 C 代码转化为 JavaScript 代码,这种方案有较好的混淆作用,让窃听者不太容易获取到对称密钥。
- 将密钥保存到服务器端,每次发送数据前,通过 HTTPS 的方式获取加密密钥,然后对采集数据进行加密和发送。
- 客户端和服务器端保存一份公钥,客户端生成一个对称密钥 K(具有随机性和时效性),使用公钥加密客户端通信认证内容(UID+K),并发送到服务器端,服务端收到通信认证请求,使用私钥进行解密,获取到 UID 和对称密钥 K,后面每次采集的数据都用客户端内存中的 K 进行加密,服务器端根据 UID 找到对应的对称密钥 K,进行数据解密。
这三种客户端数据加密方式基本能解决客户端采集数据传输的安全性难题。
采集数据压缩。为了节省流量和带宽,高效发送客户端采集的数据,需要使用快速且高压缩比的压缩算法,目前考虑使用标准的 GZIP 和定制的 LZ77 算法。
Hermes 是基于开源的消息中间件 Kafka 且由携程自主设计研发。整体架构如图:
Hermes 消息队列存储有三种类型:
- MySQL 适用于消息量中等及以下,对消息治理有较高要求的场景。
- Kafka 适用于消息量大的场景。
- Broker 分布式文件存储(扩展 Kafka、定制存储功能)。
由于数据采集服务的消息量非常大,所以采集数据需要存储到 Kafka 中。Kafka 是一种分布式的,基于发布 / 订阅的消息系统。它能满足采集服务高吞吐量、高并发和实时数据分析的要求。它有如下优秀的特性:
- 以时间复杂度为 O(1) 的方式提供消息持久化能力,即使对 TB 级以上数据也能保证常数时间复杂度的访问性能。
- 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条以上消息的传输。
- 支持 Kafka Server 间的消息分区,及分布式消费,同时保证每个 Partition 内的消息顺序传输。
- 同时支持离线数据处理和实时数据处理。
- Scale out,即支持在线水平扩展。
一个典型的 Kafka 集群中包含若干 Producer(可以是 Web 前端产生的采集数据,或者是服务器日志,系统 CPU、Memory 等),若干 broker(Kafka 支持水平扩展,一般 broker 数量越多,集群吞吐率越高),若干 Consumer Group,以及一 Zookeeper 集群。Kafka 通过 Zookeeper 管理集群配置,选举 leader,以及在 Consumer Group 发生变化时进行 rebalance。Producer 使用 push 模式将消息发布到 broker,Consumer 使用 pull 模式从 broker 订阅并消费消息。Kafka 拓扑结构图如下:
我们知道,客户端用户数据的有序性采集和存储对后面的数据消费和分析非常的重要,但是在一个分布式环境下,要保证消息的有序性是非常困难的,而 Kafka 消息队列虽然不能保证消息的全局有序性,但能保证每一个 Partition 内的消息是有序的。在用户数据采集和分析的系统中,我们主要关注的是同一个用户的数据是否能保证有序,如果我们在数据采集服务端能将同一个用户的数据存储到 Kafka 的同一个 Partition 中,那么就能保证同一个用户的数据是有序的,因此基本上能解决采集数据的有序性。
当出现网络严重中断或者 Hermes(Kafka) 消息队列故障情况下,用户数据需要进行灾备存储,目前考虑的方案是基于 Avro 格式的本地文件存储。其中 Avro 是一个数据序列化反序列化框架,它可以将数据结构或对象转化成便于存储或传输的格式,Avro 设计之初就用来支持数据密集型应用,适合于远程或本地大规模数据的存储和交换。
Avro 定义了一个简单的对象容器文件格式。一个文件对应一个模式,所有存储在文件中的对象都是根据模式写入的。对象按照块进行存储,在块之间采用了同步记号,块可以采用压缩的方式存储。一个文件由两部分组成:文件头和一个或者多个文件数据块。其存储结构如下图所示:
灾备存储处理过程是:当网络异常或者 Hermes(Kafka) 消息队列出现故障时,将采集的用户数据解析并转化成 Avro 格式后,直接序列化存储到本地磁盘文件中,数据按 Kafka-Topic 分成多个文件存储,且每小时自动生成一个新的文件。当网络或者 Hermes(Kafka) 故障恢复后,后端线程自动读取磁盘 Avro 文件,将数据写入 Hermes(Kafka) 消息队列的对应 Topic 和分区中。每个文件写入成功后,自动删除灾备存储文件。这样能增加用户数据采集服务的健壮性和增强服务容错性。
在相同配置的测试服务器上(包括数据采集服务器、Hermes(Kafka) 集群)做如下对比实验测试:(使用 ApacheBenchmark 进行 Web 性能压力测试工具)
在不对采集数据进行业务处理的情况下(即只接请求并做响应,不做业务处理,也不存储采集数据),在 5000 并发,Keepalive 模式下均能达到每秒处理 4 万多请求,其中 Nginx 的 CPU、内存消耗会小一些。测试对比数据如下:( ab 参数: -k –n 10000000 –c 5000)
Netty 服务加上采集数据解析相关业务处理,以及处理后的数据写入 Hermes(Kafka) 消息队列。可以进行简单的间接估算。如果采集服务要求达到:每秒处理 3 万左右请求,99% 的请求完成时间小于 800ms 的目标,则采集数据解析和存储流程的处理时间必须在 600ms 以内。而这两步又分为数据解析和数据存储,可以分别进行压力测试加以验证。 根据我们的压力测试,采集数据解析和存储也能完全满足性能要求。
经以上对比实验测试表明,使用 Netty 服务组件收集、解析数据并直接写入 Hermes(Kafka) 分布式消息队列的方案初步具备可行性。
基于实时采集到的用户数据和系统监控数据,我们开发了一套相关的数据分析产品。产品的内容主要分以下几部分:(1)、API 和页面性能报表;(2)、页面访问和流量;(3)、用户行为分析;(4)、系统异常崩溃分析;(5)、数据实时查询工具;(6)、采集数据排障工具;(7)、其它。
其中详细分类如下图所示:
现选取其中几个比较常见的产品做下简单介绍:
作用:实时跟踪用户浏览记录,帮助产品优化页面访问流程、帮助用户排障定位问题。
使用案例:根据用户在客户端上的唯一标识 ID,如:手机号、Email、注册用户名、ClientId、VisitorId 等查询此用户在某一时间段顺序浏览过的页面和每个页面的访问时间及页面停留时长等信息。如果用户在浏览页面过程中发生了异常崩溃退出情况,可以结合应用崩溃信息关联查询到相关信息。
作用:实时查看各个页面的访问量和转化情况,帮助分析页面用户体验以及页面布局问题。
使用案例:用户首先配置页面浏览路径,如 p1023 -> p1201 -> p1137 -> p1300,然后根据用户配置页面浏览路径查询某个时间段各个页面的转化率情况。如有 1.4 万用户进入 p1023 页面, 下一步有 1400 用户进入下一页面 p1201。这样可推算出页面 p1201 的转化率为 10% 左右。这是最简单的一种页面转化率,还有间接的页面转化率,即只匹配第一个和最后一个页面的访问量。同时可以按各种维度进行条件筛选,比如:网络、运营商、国家、地区、城市、设备、操作系统等等。
作用:了解每个页面的相对用户量、各个页面间的相对流量和退出率、了解各维度下页面的相对流量。
使用案例:用户选择查询维度和时间段进行查询,就能获取到应用从第一个页面到第 N 个页面的访问路径中,每个页面的访问量和独立用户会话数、每个页面的用户流向、每个页面的用户流失量等信息。
作用:发现用户经常点击的模块或者区域,判断用户喜好、分析页面中哪些区域或者模块有较高的有效点击数、应用于 A/B 测试,比较不同页面的点击分布情况、帮助改进页面交互和用户体验。
使用案例:点击热力图查看工具包括 Web 和 APP 端,统计的指标包括:原始点击数(当前选中元素的原始点击总数)、页面浏览点击数(当前选中元素的有效点击数,同一次页面浏览,多次点击累计算 1 次点击)、独立访客点击数(当前选中元素的有效点击数, 同一用户,多次点击累计算 1 次点击)。
作用:快速测试是否能正常采集数据、数据量是否正常、采集的数据是否满足需求等。
使用案例:用户使用携程 APP 扫描工具页面的二维码,获取用户标识信息,之后正常使用携程 APP 过程中,能实时地将采集到的数据分类展示在工具页面中,对数据进行对比测试验证。
作用:监控系统各业务服务调用性能(如 SOA 服务、RPC 调用等)、页面加载性能、APP 启动时间、LBS 定位服务、Native-Crash 占比、JavaScript 错误占比等。按小时统计各服务调用耗时、成功率、调用次数等报表信息。
基于前端多平台(包括 iOS、Android、Web、Hybrid、RN、小程序)数据采集 SDK 的丰富的自动化埋点数据,我们可以对数据、用户、系统三方面进行多维度立体的分析。服务于系统产品和用户体验、用户留存、转换率及吸引新用户。
[source]携程实时用户数据采集与分析系统