3.1.2 数据实时计算及快速响应技术
谈起实时计算,一般都会和上一节介绍的离线计算进行比较。
离线计算主要包括批量获取数据、批量传输数据和周期性批量计算数据。其代表技术有Sqoop(批量导入数据)、HDFS(批量存储数据)、MapReduce与Spark(批量计算数据)、Hive(批量计算数据)、Azkaban/oozie任务调度等。
实时(流式)计算主要包括数据实时产生、数据实时传输、数据实时计算和实时展示。其代表技术有Flume(实时获取数据)、Kafka/Metaq(实时消息队列)、Storm/Jstorm/Spark streaming/Flink(实时数据计算)、Redis(实时结果缓存)等。
一句话总结实时计算:源源不断产生的数据实时收集并实时计算,尽可能快地得到计算结果。而从智能运维角度来看,实时计算主要有以下4类典型应用场景。
● 用户感知:实时预测用户感受,如哪些用户网络不畅或视频卡顿。
● 安全预警:实时感知业务安全态势,快速响应输出威胁情报,完成资产精准防护。
● 精准营销:实时感知用户兴趣变化、环境/位置变化、商家优惠策略变化,从而实现精准营销推送。
● 运维监控:实时感知每台机器、每个业务的运行状态,实现秒级监控告警
1.实时计算的挑战
面对海量实时流数据,为保证高可用与低延时,在日常开发过程中主要面临以下几个挑战。
● 数据聚合:数据以日志、数据库文件等多种形式、多种格式散落在各业务服务器上,如何做到高效聚合?
● 数据复用:同一份数据可能承载多份业务,如何实现多份业务的并发执行并保证数据的一致性?如何去承载洪峰流量?
● 数据计算:实现业务的实时计算,如何做到数据无丢失、高容错性?
● 数据存储:实时流计算会频繁读写操作数据库,选择什么样的方式,才能保证高频读写效率?
● 数据展现:计算汇聚后的结果怎样能高效展现,让数据说话,让运维者迅速读懂计算情况?
面临这么多棘手的问题,开发人员很难从头开始一步步做起,可以借助业界较为成熟的中间件,结合业务场景进行定制化开发,实现图3-6所示的实时计算总体框架。目前较具有代表性的实时计算框架有以下几个。
● Flume(数据聚合):Flume是Cloudera提供的一个高可用的、高可靠的、分布式的海量日志采集、聚合和传输的系统,解决数据聚合的问题。
● Kafka(数据分发):Apache Kafka由Scala写成,是由Apache软件基金会开发的一个开源消息系统项目。
● Storm/Spark streaming/Flink(实时计算):用来实时处理数据。特点:低延迟、高可用、分布式、可扩展、数据不丢失。提供简单容易理解的接口,便于开发。
● Redis(k-v数据库):Redis是一个key-value存储系统。
● ECharts(图表呈现):ECharts是使用JavaScript实现的开源可视化库,可以流畅运行在个人计算机和移动设备上,兼容当前绝大部分浏览器,底层依赖矢量图形库ZRender,提供直观、交互丰富、可高度个性化定制的数据可视化图表。
由于Flume属于数据采集技术,在实时与离线场景中均有涉及,在接下来的章节笔者会进行详细介绍。实时计算组件目前较多,在本节将选择较具有代表性的Storm进行介绍。
图3-6 实时计算总体框架
2.实时消息队列
可能你会好奇,实时计算与消息队列有什么关系。其实消息队列和蓄水池一个原理:在平时,河道完全能承载上游水量,但在雨季,河道无法负载入水量,就会出现决堤河水泛滥的情况;在实时计算中也是如此,当遇到尖峰流量时,下游的计算资源难以负载,需要上游拥有一个组件扮演蓄水池的功能,而消息队列恰恰满足上述要求。
以一个业务系统为例,介绍消息队列在日常开发的应用。传统做法如图3-7所示,在网络流量日志汇聚以后,立刻调用质差识别系统实时监测网络运行情况。该做法在实际应用中有以下几个问题。
● 耦合强,采集系统与处理系统之间互相调用,模块间耦合性太强。
● 响应慢,需要识别系统处理完成后,再返回给客户端,即使用户并不需要立刻知道结果。
● 并发低,一般识别系统并发上限较小,很难应对突发尖峰流量的冲击。
图3-7 传统业务系统调用示例
改用消息队列后如图3-8所示。该示例有以下改进:网络流量汇聚系统请求先接入消息队列,而不是由业务处理系统直接处理,消息队列做了一次缓冲,极大地减少了质差识别系统的压力;每个质差识别子系统对于消息的处理方式可以更为灵活,可以选择收到消息时就处理,可以选择定时处理,也可以划分时间段按不同处理速度处理;发送者(网络流量汇聚系统)和接收者(质差识别系统)间没有依赖性,发送者发送消息之后,不管有没有接收者在运行,都不会影响发送者下次发送消息。
图3-8 基于消息队列的业务系统调用示例
上述简单介绍了消息队列的作用,目前常见的消息队列有Kafka、RabbitMQ、ActiveMQ与RocketMQ等,由于本书重点介绍智能运维算法,仅选取目前在智能运维中使用较广泛的Kafka进行介绍,让读者对此有一个粗略的认识。
Kafka作为一个分布式消息队列,其消息保存时按照Topic进行归类,一个Topic可以看作是消息的集合。每个Topic下又包括一个或多个分区Partition(图3-9中的P1、P2、P3、P4),Partition可以理解为Topic的子集,这里说的是多个分区,一个分区就代表磁盘中一块连续的位置,不同分区也就是磁盘上不同的区域块。发送消息方被称为Producer,其数据写入与读取分别如图3-9和图3-10所示。多个不同的生产者(图中Producer client1与Producer client2)可以彼此独立地发布新事件,具有相同主键(图中由颜色区分)的事件会被写入同一分区Partition。每个Partition都是一个有序并且不可变的消息记录集合。当新的数据写入时,就被追加到Partition的末尾。如图3-10所示,在每个Partition中,每个分区中的记录都会被分配一个顺序的ID号作为每记录的唯一标识,这个标识被称为offset,即偏移量。每个消费者(Con-sumer A和Consumer B)保留的唯一元数据就是消费者在分区中的偏移位置。一般情况下消费者会在读取记录时提高偏移,按照相应顺序消费数据;但也可以重置为之前的偏移量,重新处理过去的数据;也可以配置直接跳过最近的记录,并从当前位置开始消费数据。
图3-9 Kafka写操作示例图
图3-10 Kafka读操作示例图
3.实时计算组件
上面介绍的消息队列Kafka,其在实时计算扮演蓄水池的角色,起到削峰限流的作用。而本节介绍的Storm主要是如何快速泄洪,让数据计算快速且有效。Storm是一个分布式流式计算应用,其基本原理也可类比水利工程。如果上游堤坝汇聚了多条支流,堤坝下游只有一条主干河流泄洪,对主干河流的硬件设施要求就会很高,如果有多条支流协同处理就简单许多。实时计算也是借鉴了这种思路,其通过(spout、bolt)组合的方式,实现快速水平扩展。
Storm的计算结构称为topology(拓扑),如图3-11所示,其包括spout(数据流生成者)、tuple(数据处理单元)、bolt(运算)、stream(持续的tuple流)。
图3-11 Storm topology组成结构图
spout是单个topology的数据入口,扮演数据采集器的角色,其连接到上游数据源,将数据源转换为tuple,并将tuple数据流发送到下游运算单元。
tuple为一个包含若干个键值对的列表,它是Storm的核心数据结构。若干个tuple组成的序列被称为stream。类比火车,其中每节车厢中人与座位一一对应构成的元素集合就是tuple,所有车厢连在一起的火车就是Storm的stream。
bolt可以理解为其中的计算单元,其接收数据流输入后,通过设定的一系列运算结果,输出为新的数据流。bolt的上游可以是spout转换的数据流,也可以是bolt产生的数据流,通过bolt的级联,就可以构成复杂的数据流计算网络。
在之前的离线计算中,本书一直以单词计算为例,在实时计算中也以此为例。在之前的场景中,单词计数批量读取文本文件,对时延要求不高,如果上游持续有新的文本加入,要求实时输出各单词的数目,离线计算就不再适用了。图3-12所示为Storm单词计数流程图,Strom基于spout会接入动态数据源并将其转变为tuple发送到下游。例如接收到一条语句“hello Tom”,将其转变为一个单条tuple的数据流,键值若设定为statement,则数据流为{“statement”:“hello Tom”};之后执行语句分割运算,bolt通过键值获取对应的语句,根据空格将句子分割为一个单词,组成向下游发送的数据流{“word”:“hello”}{“word”:“Tom”};然后执行单词计数功能,bolt接收到一个tuple后,会将对应单词数目加1,并上报相应的统计结果,例如收到上游tuple{“word”:“hello”}后,单词数加1后输出为{“word”:“hello”,“count”:“4”}。
图3-12 Storm单词计数流程图
上述实时单词计数与离线计算的最大区别在于:上游数据源实时生成、实时计算、实时更新,能够最大限度保证业务的实时性。但在上述单词计数过程中也会发现,计算中会频繁地更新数据,对数据库读写性能有很大的挑战,传统的关系型数据库很难满足需求,这里就需要下文介绍的key-value数据库Redis。
4.内存数据库
Redis是一个高性能的key-value内存数据库,其以键值对的形式存储,且数据都缓存在内存中,具有出色的读写能力,拥有以下3个特点。
● 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
● 不仅仅支持简单的key-value类型的数据,同时还提供list、set、zset、hash等数据结构的存储。
● 支持数据的备份,即master-slave模式的数据备份。
Redis包括字符串、散列、列表、集合等数据类型,主要描述如下。
● 字符串类型:最基本的存储类型,可以存储任何字符串,包括二进制数字、json对象和图片。
● 散列类型:存储字段和字段值的映射,适合存储对象。
● 列表类型:存储一个有序的字符串列表,常用操作是向两端添加元素(类比双向链表)。
● 集合类型:常用于向集合中加入或删除元素、判断元素是否存在。
● 有序集合类型:常用于计算topN。