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