大数据无处不在,大数据应用于各个行业,包括金融、汽车、餐饮、电信、能源、体能和娱乐等在内的社会各行各业都已经融入了大数据的印迹。
电商是最早利用大数据进行精准营销的行业,除了精准营销,电商可以依据客户消费习惯来提前为客户备货,并利用便利店作为货物中转点,在客户下单15分钟内将货物送上门,提高客户体验。
例如:马云的菜鸟网络宣称的24小时完成在中国境内的送货;以及刘强东宣传未来京东将在15分钟完成送货上门都是基于客户消费习惯的大数据分析和预测。
随着大数据技术的应用,越来越多的金融企业也开始投身到大数据应用实践中。
麦肯锡的一份研究显示,金融业在大数据价值潜力指数中排名第一。
典型的案例有:花旗银行利用IBM沃森电脑为财富管理客户推荐产品;美国银行利用客户点击数据集为客户提供特色服务,如有竞争的信用额度;招商银行利用客户刷卡、存取款、电子银行转帐、微信评论等行为数据进行分析,每周给客户发送针对性广告信息,里面有顾客可能感兴趣的产品和优惠信息。
可见,大数据在金融行业的应用可以总结为以下五个方面:精准营销、风险管控、决策支持、效率提升以及产品设计。
大数据让就医、看病更简单。随着大数据在医疗行业的深度融合,大数据平台积累了海量的病例、病例报告、治愈方案、药物报告等信息资源,所有常见的病例、既往病例等都记录在案,医生通过有效、连续的诊疗记录,能够给病人优质、合理的诊疗方案。这样不仅提高医生的看病效率,而且能够降低误诊率,从而让患者在最短的时间接受最好的治疗。
零售行业大数据应用有两个层面,一个层面是零售行业可以了解客户消费喜好和趋势,进行商品的精准营销,降低营销成本。另一层面是依据客户购买产品,为客户提供可能购买的其它产品,扩大销售额,也属于精准营销范畴。例如:美国零售业的传奇故事——“啤酒与尿布”。
交通作为人类行为的重要组成和重要条件之一,对于大数据的感知也是最急迫的。目前,交通的大数据应用主要在两个方面,一方面可以利用大数据传感器数据来了解车辆通行密度,合理进行道路规划包括单行线路规划。另一方面可以利用大数据来实现即时信号灯调度,提高已有线路运行能力。
《黑猫警长》大家都很熟悉,它讲述的是“黑猫警长”如何精明能干、对坏人穷追不舍、跌宕起伏的故事情节。拿到大数据时代背景下的话,虽然它也能体现“黑猫警长”的尽职尽责、聪明能干,但更多的会归结到一个问题:为何还是如此的被动、低效?疾病可以预防,难道犯罪不能预防么?
答案是肯定的。国家正在将大数据技术用于舆情监控,其收集到的数据除了解民众诉求,降低群体事件之外,还可以用于犯罪管理。
以上内容来自百度,举个身边的例子,ping哥在淘宝购买了一罐奶粉,淘宝会基于海量下单记录推送他一些之前买过奶粉的用户后续也会下单的商品,比如纸尿布,没准还会推送酒。为什么会推送酒呢,结合他的性别和购买奶粉这种行为对他分析,极大概率是已婚男士,那关于酒的故事还要从一个女人开始说起。。。
1、埋点产生的用户行为数据:用户在使用产品过程中,与客户端产品交互过程中产生的数据,比如页面浏览、点击、停留、评论、点赞、收藏等 , 互联网核心指标PV、UV的统计基础.
2、JavaEE后台产生的业务数据.
3、爬虫:就是模拟客户端发送网络请求,接收请求响应,一种按照一定的规则,自动地抓取互联网信息的程序。只要浏览器能够做的事情,原则上,爬虫都能够做到。简单来说,爬虫就是自动从网络上收集信息的一种程序,复杂点来说,就是一整套关于数据请求、处理、存储的程序
俗话说的好,爬虫学的好,监狱进的早。。。
多图带你认知采集通道
业务系统数据处理链路
前端埋点数据处理链路
1、1 flume是干什么的?
flume 是一个分布式的数据收集系统,具有高可靠、高可用、事务管理、失败重启、聚合和传输等功能。数据处理速度快,完全可以用于生产环境。
Flume 的使用不只限于日志数据。因为数据源可以定制,flume 可以被用来传输大量事件数据,这些数据不仅仅包括网络通讯数据、社交媒体产生的数据、电子邮件信息等等。
flume如何搜集日志?
我们把flume比作情报人员
(1)搜集信息
(2)获取记忆信息
(3)传递报告间谍信息
flume是怎么完成上面三件事情的,三个组件:
agent
flume 的核心是 agent。agent 是一个 java 进程,运行在日志收集端,通过 agent 接收日志,然后暂存起来,再发送到目的地。 每台机器运行一个 agent。 agent 里面可以包含多个 source,channel,sink。
source
source 是数据的收集端,负责将数据捕获后进行特殊的格式化,将数据封装到 event 里,然后将事件推入 channel 中。flume 提供了很多内置的 source,支持 avro,log4j,syslog 等等。如果内置的 source 无法满足环境的需求,flume 还支持自定义 source。
channel
channel 是连接 source 和 sink 的组件,大家可以将它看做一个数据的缓冲区(数据队列),它可以将事件暂存到内存中也可以持久化到本地磁盘上, 直到 sink 处理完该事件。两个较为常用的 channel,MemoryChannel 和 FileChannel。
sink
sink 从 channel 中取出事件,然后将数据发到别处,可以向文件系统、数据库、hadoop、kafka,也可以是其他 agent 的 source。
配置单个组件
流中的每一个组件(source、channel、sink)都有自己的名称、类型以及一系列配置属性。例如,一个 Avro source 需要配置 hostname (或者 IP 地址)以及端口号来接收数据。一个内存模式 channel 可以有最大队列长度的属性("capacity": channel 中最大容纳多少事件)。一个 HDFS slink 则需要知道文件系统的 URL(hdfs://**)、文件落地的路径、文件回滚的评率("hdfs.rollInterval": 每隔多少秒将零时文件回滚成最终文件保存到 HDFS 中)。所有这些关于各个组件的属性需要在配置文件中进行指定。
Flume还有多种拓扑模式 方便灵活搭建,下图实现了复制和多路复用的功能
一个简单的测试
这里,我们给出一个配置文件的示例,该示例为 flume 单节点部署的配置方式。
# example.conf: A single-node Flume configuration
# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# Describe/configure the source
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444
# Describe the sink
a1.sinks.k1.type = logger
# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
bin/flume-ng agent -n a1 -c conf/ -f job/exp.conf -Dflume.root.logger=info,console
看看这个配置文件,我们可以发现这个 agent 的名称是 a1。其中该 agent 的 source 监听 44444 端口。channel 采用内存模式,而 slink 直接输出数据到 控制台上(logger)。配置文件指定了各个组件的名称,并描述了它们的类型以及其他属性。当然,一个配置文件可以配置多个 agent 属性,当希望运行指定 agent 进程时,我们需要在命令行中显示的给出该 agent 的名称。
初识 Kafka
Kafka是啥?用Kafka官方的话来说就是:
Kafka is used for building real-time data pipelines and streaming apps. It is horizontally scalable, fault-tolerant, wicked fast, and runs in production in thousands of companies.
大致的意思就是,这是一个实时数据处理系统,可以横向扩展、高可靠,而且还变态快,已经被很多公司使用。
那么什么是实时数据处理系统呢?顾名思义,实时数据处理系统就是数据一旦产生,就要能快速进行处理的系统。
对于实时数据处理,我们最常见的,就是消息中间件了,也叫MQ(Message Queue,消息队列),也有叫Message Broker的。
首先,我将从消息中间件的角度,带大家看看Kafka的内部结构,看看它是如何做到横向扩展、高可靠的同时,还能变态快的。
为什么需要消息中间件
消息中间件的作用主要有两点:
· 解耦消息的生产和消费。
· 缓冲。
想象一个场景,你的一个创建订单的操作,在订单创建完成之后,需要触发一系列其他的操作,比如进行用户订单数据的统计、给用户发送短信、给用户发送邮件等等,就像这样:
createOrder(...){
...
statOrderData(...);
sendSMS();
sendEmail();}
代码这样写似乎没什么问题,可是过了一段时间,你给系统引进了一个用户行为分析服务,它也需要在订单创建完成之后,进行一个分析用户行为的操作,而且随着系统的逐渐壮大,创建订单之后要触发的操作也就越来越多,代码也渐渐膨胀成这样:
createOrder(...){
...
statOrderData(...);
sendSMS();
sendEmail();
// new operation statUserBehavior(...);
doXXX(...);
doYYY(...);
// more and more operations ...}
导致代码越来越膨胀的症结在于,消息的生产和消费耦合在一起了。createOrder方法不仅仅要负责生产“订单已创建”这条消息,还要负责处理这条消息。
这就好比espn的记者,在知道勇士拿到NBA冠军之后,拿起手机,翻开勇士球迷通讯录,给球迷一个一个打电话,告诉他们,勇士夺冠了。
事实上,espn的记者只需要在他们官网发布这条消息,然后球迷自行访问espn,去上面获取这条新闻;又或者球迷订阅了espn,那么订阅系统会主动把发布在官网的消息推送给球迷。
同样,createOrder也需要一个像espn官网那样的载体,也就是消息中间件,在订单创建完成之后,把一条主题为“orderCreated”的消息,放到消息中间件去就ok了,不必关心需要把这条消息发给谁。这就完成了消息的生产。
至于需要在订单创建完成之后触发操作的服务,则只需要订阅主题为“orderCreated”的消息,在消息中间件出现新的“orderCreated”消息时,就会收到这条消息,然后进行相应的处理。
因此,通过使用消息中间件,上面的代码也就简化成了:
createOrder(...){
...
sendOrderCreatedMessage(...);}
以后如果在订单创建之后有新的操作需要执行,这串代码也不需要修改,只需要给对消息进行订阅即可。
另外,通过这样的解耦,消费者在消费数据时更加的灵活,不必每次消息一产生就要马上去处理(虽然通常消费者侧也会有线程池等缓冲机制),可以等自己有空了的时候,再过来消息中间件这里取数据进行处理。这就是消息中间件带来的缓冲作用。
kafka 的设计目标
Kafka一代 - 消息队列
从上面的描述,我们可以看出,消息中间件之所以可以解耦消息的生产和消费,主要是它提供了一个存放消息的地方——生产者把消息放进来,消费者在从中取出消息进行处理。
那么这个存放消息的地方,应该采用什么数据结构呢?
在绝大多数情况下,我们都希望先发送进来的消息,可以先被处理(FIFO),这符合大多数的业务逻辑,少数情况下我们会给消息设置优先级。不管怎样,对于消息中间件来说,一个先进先出的队列,是非常合适的数据结构:
那么要怎样保证消息可以被顺序消费呢?
消费者过来获取消息时,每次都把index=0的数据返回过去,然后再删除index=0的那条数据?
很明显不行,因为订阅了这条消息的消费者数量,可能是0,也可能是1,还可能大于1。如果每次消费完就删除了,那么其他订阅了这条消息的消费者就获取不到这条消息了。
事实上,Kafka会对数据进行持久化存储(至于存放多长时间,这是可以配置的),消费者端会记录一个offset,表明该消费者当前消费到哪条数据,所以下次消费者想继续消费,只需从offset+1的位置继续消费就好了。
消费者甚至可以通过调整offset的值,重新消费以前的数据。
那么这就是Kafka了吗?不,这只是一条非常普通的消息队列,我们姑且叫它为Kafka一代吧。
这个Kafka一代用一条消息队列实现了消息中间件,这样的简单实现存在不少问题:
· 吞吐量低。我们把全部消息都放在一条队列了,请求一多,它肯定应付不过来。
由此就引申出了Kafka二代。
Kafka二代 - Partition
二代Kafka引入了Partition的概念,也就是采用多条队列, 每条队列里面的消息都是相同的topic:
我们可以看到,每个Partition中的消息都是有序的,生产的消息被不断追加到Partition log上,其中的每一个消息都被赋予了一个唯一的offset值。
发布到Kafka主题的每条消息包括键值和时间戳。消息到达服务器端的指定分区后,都会分配到一个自增的偏移量。原始的消息内容和分配的偏移量以及其他一些元数据信息最后都会存储到分区日志文件中。消息的键也可以不用设置,这种情况下消息会均衡地分布到不同的分区。
1) 分区的原因
(1)方便在集群中扩展,每个Partition可以通过调整以适应它所在的机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的数据了;
(2)可以提高并发,因为可以以Partition为单位读写了。
传统消息系统在服务端保持消息的顺序,如果有多个消费者消费同一个消息队列,服务端会以消费存储的顺序依次发送给消费者。但由于消息是异步发送给消费者的,消息到达消费者的顺序可能是无序的,这就意味着在并行消费时,传统消息系统无法很好地保证消息被顺序处理。虽然我们可以设置一个专用的消费者只消费一个队列,以此来解决消息顺序的问题,但是这就使得消费处理无法真正执行。
Kafka比传统消息系统有更强的顺序性保证,它使用主题的分区作为消息处理的并行单元。Kafka以分区作为最小的粒度,将每个分区分配给消费者组中不同的而且是唯一的消费者,并确保一个分区只属于一个消费者,即这个消费者就是这个分区的唯一读取线程。那么,只要分区的消息是有序的,消费者处理的消息顺序就有保证。每个主题有多个分区,不同的消费者处理不同的分区,所以Kafka不仅保证了消息的有序性,也做到了消费者的负载均衡。
2)分区的原则
(1)指定了patition,则直接使用;
(2)未指定patition但指定key,通过对key的value进行hash出一个patition
(3)patition和key都未指定,使用轮询选出一个patition。
Kafka二代足够完美了吗?当然不是,我们虽然通过Partition提升了性能,但是我们忽略了一个很重要的问题——高可用。
万一机器挂掉了怎么办?单点系统总是不可靠的。我们必须考虑备用节点和数据备份的问题。
Kafka三代 - Broker集群
很明显,为了解决HA问题,我们需要集群。
Kafka对集群的支持也是非常友好的。在Kafka中,集群里的每个实例叫做Broker,就像这样:
每个partition不再只有一个,而是有一个leader(红色)和多个replica(蓝色),生产者根据消息的topic和key值,确定了消息要发往哪个partition之后(假设是p1),会找到partition对应的leader(也就是broker2里的p1),然后将消息发给leader,leader负责消息的写入,并与其余的replica进行同步。
一旦某一个partition的leader挂掉了,那么只需提拔一个replica出来,让它成为leader就ok了,系统依旧可以正常运行。
通过Broker集群的设计,我们不仅解决了系统高可用的问题,还进一步提升了系统的吞吐量,因为replica同样可以为消费者提供数据查找的功能。
Kafka没那么简单
以上只是带大家初步认识一下Kafka,很多细节并没有深入讨论,比如:
1、Kafka的消息结构?
我们只知道Kafka内部是一个消息队列,但是队列里的元素长什么样,包含了哪些消息呢?
2、Zookeeper和Kafka的关系?
如果玩过Kafka的,就会发现,我们在使用Kafka时,需要先启动一个ZK,那么这个ZK的作用到底是什么呢?
3、数据可靠性和重复消费
生产者把消息发给Kafka,发送过程中挂掉、或者Kafka保存消息时发送异常怎么办?
同理,消费者获取消费时发生异常怎么办?
甚至,如果消费者已经消费了数据,但是修改offset时失败了,导致重复消费怎么办?
等等这些异常场景,都是Kafka需要考虑的。
4、 pull or push
消费者侧在获取消息时,是通过主动去pull消息呢?还是由Kafka给消费者push消息?
这两种方式各自有什么优劣?
5、 如何提高消费者处理性能
还是之前的订单创建的例子,订单创建后,你要给用户发送短信,现在你发现由于你只有一个消费者在发送短信,忙不过来,怎么办?这就有了Kafka里头的消费者组(Consumer Group)的设计。
……
终极问题:一条消息从生产,到被消费,完整流程是怎样的?
如果能详尽透彻地回答这个问题,那你对Kafka的理解也就非常深入了。
初识Hadoop
hadoop是什么?Hadoop是一种分析和处理大数据的软件平台,是Apache的一个用Java语言所实现的开源软件的框架,在大量计算机组成的集群当中实现了对于海量的数据进行的分布式计算。
1.1MapReduce是什么
1、概念理解
Hadoop Map/Reduce是一个使用简易的软件框架,基于它写出来的应用程序能够运行在由上千个商用机器组成的大型集群上,并以一种可靠容错的方式并行处理上T级别的数据集。
2、Map(映射)
“Map”:主结点读入输入数据,把它分成可以用相同方法解决的小数据块(这里是一个分而治之的思想),然后把这些小数据块分发到不同的工作节点上(worder nodes)上,每一个工作节点(worder node)循环做同样的事,这就行成了一个树行结构,而每一个叶子节点有来处理每一个具体的小数据块,再把这些处理结果返回给父节点。
3、Reduce(归约)
“Reduce”:主结节得到所有子节点的处理结果,然后把所有结果组合并且返回到输出。
4、个人理解
简单的来讲,map就是分,reduce就是合。怎么理解呢?我们来看个例子。
我们将100吨砖,从山东运到北京,如果我们用一辆能装1吨的大卡车来运,一天跑一个来回,那么我们需要100天,可是如果我们用10辆这样的车来做同样的事情,那么我们10天就可以完成了。虽然在现实生活中,我们增加了车费等一系列支出,可能不太划算,但是对于计算机来说,我们的成本是相当低的。所以在迎接大数据的到来时,MapReduce将大大提高的计算的速度,特别方便。
1.2Shuffle - 奇迹发生的地方
上面的流程是整个MapReduce最全工作流程,但是Shuffle过程只是从第7步开始到第16步结束,具体Shuffle过程详解,如下:
1)MapTask收集我们的map()方法输出的kv对,放到内存缓冲区中
2)从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
3)多个溢出文件会被合并成大的溢出文件
4)在溢出过程及合并的过程中,都要调用Partitioner进行分区和针对key进行排序
5)ReduceTask根据自己的分区号,去各个MapTask机器上取相应的结果分区数据
6)ReduceTask会取到同一个分区的来自不同MapTask的结果文件,ReduceTask会将这些文件再进行合并(归并排序)
7)合并成大文件后,Shuffle的过程也就结束了,后面进入ReduceTask的逻辑运算过程(从文件中取出一个一个的键值对Group,调用用户自定义的reduce()方法)
3.注意
Shuffle中的缓冲区大小会影响到MapReduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。
缓冲区的大小可以通过参数调整,参数:io.sort.mb默认100M。
2.1 什么是Hive
Hive是Facebook开源的基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供完整的sql查询功能,可以将sql语句转换为MapReduce任务进行运行。其优点是学习成本低,可以通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析。
Hive是建立在 Hadoop 上的数据仓库基础构架。它提供了一系列的工具,可以用来进行数据提取转化加载(ETL),这是一种可以存储、查询和分析存储在 Hadoop 中的大规模数据的机制。Hive 定义了简单的类 SQL 查询语言,称为 HQL,它允许熟悉 SQL 的用户查询数据。同时,这个语言也允许熟悉 MapReduce 开发者的开发自定义的 mapper 和 reducer 来处理内建的 mapper 和 reducer 无法完成的复杂的分析工作。
2.2、Hive本质
将HQL转换为MapReduce程序
体现在:
1、处理数据在HDFS上
2、通过MR实现
3、执行程序运行在Yarn上
2.3、数仓建模
ODS:通常而言,原始数据的种类是非常丰富的,我们可能从几十个业务方把数据拉回来,然后格式化放到HDFS上。但很多时候,情况并不这么简单,虽然有很多的损坏数据、脏数据等是不需要统计的,但是我们需要来看为什么会产生脏数据,这时候原始数据就会提供很好的样板。再有些时候,针对一些流量作弊的数据,如果按照统一规则,很容易就给过滤掉了,然后运营就问过来为什么对方提供的数据与我们的差异这么多大,这时候同样需要去看原始日志。因而,ODS的意义,在于保存最完整的数据现场,便于一些特殊场景下的问题排查使用。
DWD:如果采集的数据没有问题了,我们这里就需要做数据的预处理了。例如存在HDFS上的标准格式,我们就用字符串的格式来统一存储。还有时候因为场景要求,需要直接转成Parquent、ORC等列存格式,也需要在这里做转换。但预处理并不是简单的转换格式,还需要处理一些脏数据,例如字段缺失、格式错误、乱码、空值,等等,在这一层处理好之后,后续的计算便不需要再担心各种各样的异常情况,对于开发效率的提升有着极大的帮助。有些时候还要发挥一些特定作用,因为业务的意外导致各种各样的错误数据进来,也是时有发生的。比如客户消费了,金额总得是正的吧,但如果业务那边产生了一些错误,需要将金额设置成负值,虽然业务那边好处理了,但数据这里就头疼了。所以还需要经常打一些补丁,来处理金额负值这种异常情况。所以看起来DWD像是多余的一层,但当业务场景足够复杂之后,它所发挥的作用还是很大的。这里数据预处理主要采用MR来进行,基本上遇不到数据倾斜等问题。
DWS:当所有的数据都存好了,处理完脏数据之后,下一步我们就需要考虑如何处理和组织统计逻辑了。数据仓库之所以叫数据仓库,正是因为DWS层的重要。数据模型有很多,如:3NF范式模型、维度模型(星座、雪花、星型)、Data Vault等,但最常用的还是星型模型。通常我们会根据主题来进行表数据的统计,这里还有一个常用的说法,叫“中间层”。例如我们数据层次自上往下分别是:用户、广告投放计划、计划详情,用户本身有行业、主体公司等属性,广告投放计划包括了单元、创意等属性,计划详情包括了投放类型、投放地域等属性。那么我们在这个DWS层,就需要针对所有可能的维度,包括用户、行业、主体公司、广告投放计划、单元、创意、计划详情、投放类型、投放地域做统计,每个类型都尽可能的冗余维度信息,例如用户维度的统计要把行业、主体公司等维度冗余进来,放到一张表里。这么做虽然特别违反三范式的原则,也违反很多模型,但是冗余尽可能多的信息,对于提高下游计算的速度、减少运算数据量、简化业务逻辑、合并计算单元等具有特别大的好处。
ADS:当需求足够多时,我们要提供的报表就不是几十张的概念了,而是成百上千张,这么多的表怎么保证数据的一致性呢?怎么保证需求响应的速度呢?基本上都是ADS层需要面临的问题。在前一个层次DWS中,我们把所有的主题都尽可能多的冗余了维度信息,因此这里需要尽量从单一中间层表中进行数据统计,中间层的数据一致性,就代表了最终业务数据的一致性。响应速度同理,在某些不得不关联的业务场景下,因为中间层的存在,使得数据量减少了很多,需求响应速度也就提升了很多。
DIM:维度信息
1.1 初识Flink
Flink起源于Stratosphere项目,Stratosphere是在2010~2014年由3所地处柏林的大学和欧洲的一些其他的大学共同进行的研究项目,2014年4月Stratosphere的代码被复制并捐赠给了Apache软件基金会,参加这个孵化项目的初始成员是Stratosphere系统的核心开发人员,2014年12月,Flink一跃成为Apache软件基金会的顶级项目。
在德语中,Flink一词表示快速和灵巧,项目采用一只松鼠的彩色图案作为logo,这不仅是因为松鼠具有快速和灵巧的特点,还因为柏林的松鼠有一种迷人的红棕色,而Flink的松鼠logo拥有可爱的尾巴,尾巴的颜色与Apache软件基金会的logo颜色相呼应,也就是说,这是一只Apache风格的松鼠。
Flink Logo
Flink项目的理念是:“Apache Flink是为分布式、高性能、随时可用以及准确的流处理应用程序打造的开源流处理框架”。
Apache Flink是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。Flink被设计在所有常见的集群环境中运行,以内存执行速度和任意规模来执行计算。
1.2 Flink的重要特点
1.2.1 事件驱动型(Event-driven)
事件驱动型应用是一类具有状态的应用,它从一个或多个事件流提取数据,并根据到来的事件触发计算、状态更新或其他外部动作。比较典型的就是以kafka为代表的消息队列几乎都是事件驱动型应用。
与之不同的就是SparkStreaming微批次,如图:
事件驱动型:
1.2.2 流与批的世界观
批处理的特点是有界、持久、大量,非常适合需要访问全套记录才能完成的计算工作,一般用于离线统计。
流处理的特点是无界、实时, 无需针对整个数据集执行操作,而是对通过系统传输的每个数据项执行操作,一般用于实时统计。
在spark的世界观中,一切都是由批次组成的,离线数据是一个大批次,而实时数据是由一个一个无限的小批次组成的。
而在flink的世界观中,一切都是由流组成的,离线数据是有界限的流,实时数据是一个没有界限的流,这就是所谓的有界流和无界流。
无界数据流:无界数据流有一个开始但是没有结束,它们不会在生成时终止并提供数据,必须连续处理无界流,也就是说必须在获取后立即处理event。对于无界数据流我们无法等待所有数据都到达,因为输入是无界的,并且在任何时间点都不会完成。处理无界数据通常要求以特定顺序(例如事件发生的顺序)获取event,以便能够推断结果完整性。
有界数据流:有界数据流有明确定义的开始和结束,可以在执行任何计算之前通过获取所有数据来处理有界流,处理有界流不需要有序获取,因为可以始终对有界数据集进行排序,有界流的处理也称为批处理。
这种以流为世界观的架构,获得的最大好处就是具有极低的延迟。
2 Flink运行时的组件
Flink运行时架构主要包括四个不同的组件,它们会在运行流处理应用程序时协同工作:作业管理器(JobManager)、资源管理器(ResourceManager)、任务管理器(TaskManager),以及分发器(Dispatcher)。因为Flink是用Java和Scala实现的,所以所有组件都会运行在Java虚拟机上。每个组件的职责如下:
l 作业管理器(JobManager)
控制一个应用程序执行的主进程,也就是说,每个应用程序都会被一个不同的JobManager所控制执行。JobManager会先接收到要执行的应用程序,这个应用程序会包括:作业图(JobGraph)、逻辑数据流图(logical dataflow graph)和打包了所有的类、库和其它资源的JAR包。JobManager会把JobGraph转换成一个物理层面的数据流图,这个图被叫做“执行图”(ExecutionGraph),包含了所有可以并发执行的任务。JobManager会向资源管理器(ResourceManager)请求执行任务必要的资源,也就是任务管理器(TaskManager)上的插槽(slot)。一旦它获取到了足够的资源,就会将执行图分发到真正运行它们的TaskManager上。而在运行过程中,JobManager会负责所有需要中央协调的操作,比如说检查点(checkpoints)的协调。
l 资源管理器(ResourceManager)
主要负责管理任务管理器(TaskManager)的插槽(slot),TaskManger插槽是Flink中定义的处理资源单元。Flink为不同的环境和资源管理工具提供了不同资源管理器,比如YARN、Mesos、K8s,以及standalone部署。当JobManager申请插槽资源时,ResourceManager会将有空闲插槽的TaskManager分配给JobManager。如果ResourceManager没有足够的插槽来满足JobManager的请求,它还可以向资源提供平台发起会话,以提供启动TaskManager进程的容器。另外,ResourceManager还负责终止空闲的TaskManager,释放计算资源。
l 任务管理器(TaskManager)
Flink中的工作进程。通常在Flink中会有多个TaskManager运行,每一个TaskManager都包含了一定数量的插槽(slots)。插槽的数量限制了TaskManager能够执行的任务数量。启动之后,TaskManager会向资源管理器注册它的插槽;收到资源管理器的指令后,TaskManager就会将一个或者多个插槽提供给JobManager调用。JobManager就可以向插槽分配任务(tasks)来执行了。在执行过程中,一个TaskManager可以跟其它运行同一应用程序的TaskManager交换数据。
l 分发器(Dispatcher)
可以跨作业运行,它为应用提交提供了REST接口。当一个应用被提交执行时,分发器就会启动并将应用移交给一个JobManager。由于是REST接口,所以Dispatcher可以作为集群的一个HTTP接入点,这样就能够不受防火墙阻挡。Dispatcher也会启动一个Web UI,用来方便地展示和监控作业执行的信息。Dispatcher在架构中可能并不是必需的,这取决于应用提交运行的方式。
3.1 Window及基于体温数据的Demo
3.1.1 Window概述
streaming流式计算是一种被设计用于处理无限数据集的数据处理引擎,而无限数据集是指一种不断增长的本质上无限的数据集,而window是一种切割无限数据为有限块进行处理的手段。
Window是无限数据流处理的核心,Window将一个无限的stream拆分成有限大小的”buckets”桶,我们可以在这些桶上做计算操作。
3.1.2 Window类型
Window可以分成两类:
Ø CountWindow:按照指定的数据条数生成一个Window,与时间无关。
Ø TimeWindow:按照时间生成Window。
对于TimeWindow,可以根据窗口实现原理的不同分成三类:滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)和会话窗口(Session Window)。
1. 滚动窗口(Tumbling Windows)
将数据依据固定的窗口长度对数据进行切片。
特点:时间对齐,窗口长度固定,没有重叠。
滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠。例如:如果你指定了一个5分钟大小的滚动窗口,窗口的创建如下图所示:
图 滚动窗口
适用场景:适合做BI统计等(做每个时间段的聚合计算)。
2. 滑动窗口(Sliding Windows)
滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。
特点:时间对齐,窗口长度固定,可以有重叠。
滑动窗口分配器将元素分配到固定长度的窗口中,与滚动窗口类似,窗口的大小由窗口大小参数来配置,另一个窗口滑动参数控制滑动窗口开始的频率。因此,滑动窗口如果滑动参数小于窗口大小的话,窗口是可以重叠的,在这种情况下元素会被分配到多个窗口中。
例如,你有10分钟的窗口和5分钟的滑动,那么每个窗口中5分钟的窗口里包含着上个10分钟产生的数据,如下图所示:
图 滑动窗口
适用场景:对最近一个时间段内的统计(求某接口最近5min的失败率来决定是否要报警)。
3. 会话窗口(Session Windows)
由一系列事件组合一个指定时间长度的timeout间隙组成,类似于web应用的session,也就是一段时间没有接收到新数据就会生成新的窗口。
特点:时间无对齐。
session窗口分配器通过session活动来对元素进行分组,session窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况,相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。一个session窗口通过一个session间隔来配置,这个session间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的session将关闭并且后续的元素将被分配到新的session窗口中去。
Window-demo 实时输出五秒内体温最高的人的信息
package com.morant.apitest
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
object WindowTest {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.getConfig.setAutoWatermarkInterval(100)
val inputStream: DataStream[String] = env.readTextFile("D:\\DEVELOP_CODE\\workspace\\FlinkTest\\src\\main\\resources\\Temperature.txt")
// val inputStream = env.socketTextStream("172.19.177.124", 7777)
// Transform操作 val dataStream: DataStream[Temperature] = inputStream
.map(data => {
val dataArray = data.split(",")
Temperature(dataArray(0).trim, dataArray(1).trim.toLong, dataArray(2).trim.toDouble)
})
// .assignAscendingTimestamps(_.timestamp * 1000L) // 对乱序数据分配时间戳和watermark .assignTimestampsAndWatermarks( new BoundedOutOfOrdernessTimestampExtractor[Temperature](Time.seconds(1)) {
override def extractTimestamp(t: Temperature): Long = t.timestamp * 1000L } )
// 统计每个温度计器每5秒内的温度最大值 val processedStream = dataStream
.keyBy(_.id)
// .window(TumblingProcessingTimeWindows.of(Time.hours(1))) .timeWindow(Time.seconds(5)) // 定义长度为5秒的滚动窗口 .reduce((curMaxTempData, newData) =>
Temperature(curMaxTempData.id, newData.timestamp, newData.temperature.max(curMaxTempData.temperature)
)
)
// .reduce()
processedStream.print()
env.execute("window test")
}
}
4.1 侧输出流(SideOutput)及体温大于37.5放入测输出流的Demo
大部分的DataStream API的算子的输出是单一输出,也就是某种数据类型的流。除了split算子,可以将一条流分成多条流,这些流的数据类型也都相同。process function的side outputs功能可以产生多条流,并且这些流的数据类型可以不一样。一个side output可以定义为OutputTag[X]对象,X是输出流的数据类型。process function可以通过Context对象发射一个事件到一个或者多个side outputs。
SideOutPut 将体温高于37.5的信息再测输出流中输出
case class Temperature( id: String, timestamp: Long, temperature: Double )
object SideOutputTest {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment env.setParallelism(1)
val inputStream: DataStream[String] = env.readTextFile("D:\\DEVELOP_CODE\\workspace\\FlinkTest\\src\\main\\resources\\Temperature.txt")
// val inputStream = env.socketTextStream("192.168.1.101", 7777)
// Transform操作 val dataStream: DataStream[Temperature] = inputStream
.map(data => {
val dataArray = data.split(",")
Temperature(dataArray(0).trim, dataArray(1).trim.toLong, dataArray(2).trim.toDouble)
})
val highTempStream = dataStream
.process(new SplitTempMonitor())
highTempStream.print("low")
highTempStream.getSideOutput(new OutputTag[String]("high-temp")).print("high")
env.execute("process function test")
}
}
// 自定义process function,实现分流操作class SplitTempMonitor() extends ProcessFunction[Temperature, Temperature]{
override def processElement(value: Temperature, ctx: ProcessFunction[Temperature, Temperature]#Context, out: Collector[Temperature]): Unit = {
// 判断当前数据的温度值,如果在37.5以上,输出到侧输出流 if( value.temperature > 37.5 ){
ctx.output(new OutputTag[String]("high-temp"), value.id+"->有病")
} else{
// 37.5度以下的数据,输出到主流 out.collect(value)
}
}
}