1、简介
流处理在实际生产中体现的价值越来越大,Apache Flink这个纯流式计算框架也正在被越来越多的公司所关注并尝试使用其流上的功能。
在2017年波兰华沙大数据峰会上,有一家叫做GetInData的公司,分享了一个关于他们内部如何使用Flink的session window的例子,并因此获评最佳演讲。PPT:STREAMING ANALYTICS BETTER THAN CLASSIC BATCH – WHEN AND WHY ?
基于此演讲,该公司后续写了一个系列blog(2篇),详细的阐述了使用session window的来龙去脉。本文基于这两篇blog,做些简要的说明,也正好参考下别人是如何从传统的批的方式(spark),转而使用现代的流处理技术(Flink)来更好的实现其业务功能的。
2、User Sessionization
该公司是一家信息技术服务公司,由前Spotify员工组建。他们的这个案例受Spotify的启发,基于user session进行实时的数据分析。
关于Spotify提供的每周歌曲推荐,可以参考InfoQ上的一篇文章:Spotify每周歌曲推荐算法解析。
首先,基于用户的session,你可以作如下事情:
1、仪表板上显示一些KPI,例如用户在每周的Discover Weekly播放列表中听了多长时间,或者连续听了多少首歌曲等。
2、通过这些指标,你可以改进你的推荐算法,并发出一些警告及时的捕获一些变化较大的信息,例如澳大利亚用户听Discover Weekly播放列表中歌曲的时间太短。
3、而且,基于当前的数据分析,我们可以根据不同的用户,做些个性化的歌曲推荐和广告推荐。
3、古典的批处理架构
许多公司使用类似于Kafka、Hadoop、Spark、Oozie来分析用户session问题。
古典的批处理架构如下:用户会话的数据被实时的发送到kafka,之后使用批处理工具例如Campus(Gobblin)将kafka中的数据定时发送到HDFS,这里假设1小时抽取一次;之后由Spark来每小时进行一次批处理的Job,以计算用户session的数据分析。
但是这里有一个问题是用户可能会一直在听歌曲,因此session持续的时间很长,这样得到的结果就是不正确的。一种可选的解决方法就是通过维护一个每小时的中间结果来连接Job。关于使用古典的批处理来实现user session的问题,有一本专门的书来说明:Hadoop Application Architectures。
尽管批处理可以处理user session问题,但是依然有很多缺点:
1、首先,这条pipeline上边的组件太多,例如Gobblin,你需要部署额外的组件或者写更多的代码来维持pipeline的可用性。
2、延迟性太高。这种架构不能实时的给出alerts,所有的结果必须等到1个小时后才能得到。
4、微批架构
降低延迟并缩短结果反馈时间最简单的方式看起来就是使用类似于Spark Streaming这种微批架构了。
这种架构省去了Gobblin、Oozie、HDFS分区等组件,通过配置Spark Streaming的Job以每10分钟、5分钟或1分钟的批次,来实现更低的延迟。
但是,Spark Streaming本身没有内建支持Session问题的处理,由于其微批架构,用户不得不通过自定义的代码实现user session,同时这种方式不得不自己维持每个批次的状态信息。你可以通过mapWithState方法来维护每个user的session状态,有很多文章都提到如何构建一个user session,但是都没有提到实现过程中可能遇到的诸多问题。
5、现实世界的事件流
在现实世界,数据是无界的。有可能产生乱序、延迟现象。例如用户在飞机上是飞行模式(离线模式),此时正在听spotify的歌曲,但是直到飞机降落才上线,此时数据的产生就是乱序的数据。而且由于经过kafka,由于并行处理的网络等原因,迟到的数据也是无处不在。
因此,如果还是采用Spark Streaming这种架构,这些问题的产生很可能不能正确的处理,这样的结果就是不正确的。
6、解决流处理的问题
到这里,让我们问问我们自己,我们为什么要用传统的批处理、微批处理的方式来对待流数据呢?
在GetInData,我们找到了最简单的、最重要、最正确的流处理引擎—Apache Flink。通过使用Flink,实施起来不但十分简单,代码量很少,而且可以更快速的得到正确的结果。
7、实施案例
我们通过使用Flink很容易的解决了user session的问题,真的只有几行代码!!!
案例A表示一个用户在一个独立的session中听了多久的歌曲
案例B表示一个用户在播放列表中连续听了多少首歌曲
首先,第一步我们需要从kafka中消费数据。通过Flink内部的检查点机制,可以保证exactly once的处理,这仅仅需要提供几个kafka的参数:
sEnv.addSource(new FlinkKafkaConsumer09[Event](conf.topic(),
getSerializationSchema,
kafkaProperties(conf.kafkaBroker()))
)