Spark 实时计算整合案例
1.概述
最近有同学问道,除了使用 Storm 充当实时计算的模型外,还有木有其他的方式来实现实时计算的业务。了解到,在使用 Storm 时,需要编写基于编程语言的代码。比如,要实现一个流水指标的统计,需要去编写相应的业务代码,能不能有一种简便的方式来实现这一需求。在解答了该同学的疑惑后,整理了该实现方案的一个案例,供后面的同学学习参考。
2.内容
实现该方案,整体的流程是不变的,我这里只是替换了其计算模型,将 Storm 替换为 Spark,原先的数据收集,存储依然可以保留。
2.1 Spark Overview
Spark 出来也是很久了,说起它,应该并不会陌生。它是一个开源的类似于 Hadoop MapReduce 的通用并行计算模型,它拥有 Hadoop MapReduce 所具有的有点,但与其不同的是,MapReduce 的 JOB 中间输出结果可以保存在内存中,不再需要回写磁盘,因而,Spark 能更好的适用于需要迭代的业务场景。
2.2 Flow
上面只是对 Spark 进行了一个简要的概述,让大家知道其作用,由于本篇博客的主要内容并不是讲述 Spark 的工作原理和计算方法,多的内容,这里笔者就不再赘述,若是大家想详细了解 Spark 的相关内容,可参考官方文档。[参考地址]
接下来,笔者为大家呈现本案例的一个实现流程图,如下图所示:
通过上图,我们可以看出,首先是采集上报的日志数据,将其存放于消息中间件,这里消息中间件采用的是 Kafka,然后在使用计算模型按照业务指标实现相应的计算内容,最后是将计算后的结果进行持久化,DB 的选择可以多样化,这里笔者就直接使用了 Redis 来作为演示的存储介质,大家所示在使用中,可以替换该存储介质,比如将结果存放到 HDFS,HBase Cluster,或是 MySQL 等都行。这里,我们使用 Spark SQL 来替换掉 Storm 的业务实现编写。
3.实现
在介绍完上面的内容后,我们接下来就去实现该内容,首先我们要生产数据源,实际的场景下,会有上报好的日志数据,这里,我们就直接写一个模拟数据类,实现代码如下所示:
object KafkaIPLoginProducer {
private val uid = Array("123dfe", "234weq","213ssf") private val random = new Random() private var pointer = -1 def getUserID(): String = {
pointer = pointer + 1
if (pointer >= users.length) {
pointer = 0
uid(pointer)
} else {
uid(pointer)
}
} def plat(): String = {
random.nextInt(10) + "10"
} def ip(): String = {
random.nextInt(10) + ".12.1.211"
} def country(): String = {
"中国" + random.nextInt(10)
} def city(): String = {
"深圳" + random.nextInt(10)
} def location(): JSONArray = {
JSON.parseArray("[" + random.nextInt(10) + "," + random.nextInt(10) + "]")
} def main(args: Array[String]): Unit = {
val topic = "test_data3"
val brokers = "dn1:9092,dn2:9092,dn3:9092"
val props = new Properties()
props.put("metadata.broker.list", brokers)
props.put("serializer.class", "kafka.serializer.StringEncoder") val kafkaConfig = new ProducerConfig(props)
val producer = new Producer[String, String](kafkaConfig) while (true) {
val event = new JSONObject() event
.put("_plat", "1001")
.put("_uid", "10001")
.put("_tm", (System.currentTimeMillis / 1000).toString())
.put("ip", ip)
.put("country", country)
.put("city", city)
.put("location", JSON.parseArray("[0,1]"))
println("Message sent: " + event)
producer.send(new KeyedMessage[String, String](topic, event.toString)) event
.put("_plat", "1001")
.put("_uid", "10001")
.put("_tm", (System.currentTimeMillis / 1000).toString())
.put("ip", ip)
.put("country", country)
.put("city", city)
.put("location", JSON.parseArray("[0,1]"))
println("Message sent: " + event)
producer.send(new KeyedMessage[String, String](topic, event.toString)) event
.put("_plat", "1001")
.put("_uid", "10002")
.put("_tm", (System.currentTimeMillis / 1000).toString())
.put("ip", ip)
.put("country", country)
.put("city", city)
.put("location", JSON.parseArray("[0,1]"))
println("Message sent: " + event)
producer.send(new KeyedMessage[String, String](topic, event.toString)) event
.put("_plat", "1002")
.put("_uid", "10001")
.put("_tm", (System.currentTimeMillis / 1000).toString())
.put("ip", ip)
.put("country", country)
.put("city", city)
.put("location", JSON.parseArray("[0,1]"))
println("Message sent: " + event)
producer.send(new KeyedMessage[String, String](topic, event.toString))
Thread.sleep(30000)
}
}
}
上面代码,通过 Thread.sleep() 来控制数据生产的速度。接下来,我们来看看如何实现每个用户在各个区域所分布的情况,它是按照坐标分组,平台和用户ID过滤进行累加次数,逻辑用 SQL 实现较为简单,关键是在实现过程中需要注意的一些问题,比如对象的序列化问题。这里,细节的问题,我们先不讨论,先看下实现的代码,如下所示:
object IPLoginAnalytics { def main(args: Array[String]): Unit = {
val sdf = new SimpleDateFormat("yyyyMMdd")
var masterUrl = "local[2]"
if (args.length > 0) {
masterUrl = args(0)
} // Create a StreamingContext with the given master URL
val conf = new SparkConf().setMaster(masterUrl).setAppName("IPLoginCountStat")
val ssc = new StreamingContext(conf, Seconds(5)) // Kafka configurations
val topics = Set("test_data3")
val brokers = "dn1:9092,dn2:9092,dn3:9092"
val kafkaParams = Map[String, String](
"metadata.broker.list" -> brokers, "serializer.class" -> "kafka.serializer.StringEncoder") val ipLoginHashKey = "mf::ip::login::" + sdf.format(new Date()) // Create a direct stream
val kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics) val events = kafkaStream.flatMap(line => {
val data = JSONObject.fromObject(line._2)
Some(data)
}) def func(iter: Iterator[(String, String)]): Unit = {
while (iter.hasNext) {
val item = iter.next()
println(item._1 + "," + item._2)
}
} events.foreachRDD { rdd =>
// Get the singleton instance of SQLContext
val sqlContext = SQLContextSingleton.getInstance(rdd.sparkContext)
import sqlContext.implicits._
// Convert RDD[String] to DataFrame
val wordsDataFrame = rdd.map(f => Record(f.getString("_plat"), f.getString("_uid"), f.getString("_tm"), f.getString("country"), f.getString("location"))).toDF() // Register as table
wordsDataFrame.registerTempTable("events")
// Do word count on table using SQL and print it
val wordCountsDataFrame = sqlContext.sql("select location,count(distinct plat,uid) as value from events where from_unixtime(tm,'yyyyMMdd') = '" + sdf.format(new Date()) + "' group by location")
var results = wordCountsDataFrame.collect().iterator /**
* Internal Redis client for managing Redis connection {@link Jedis} based on {@link RedisPool}
*/
object InternalRedisClient extends Serializable { @transient private var pool: JedisPool = null def makePool(redisHost: String, redisPort: Int, redisTimeout: Int,
maxTotal: Int, maxIdle: Int, minIdle: Int): Unit = {
makePool(redisHost, redisPort, redisTimeout, maxTotal, maxIdle, minIdle, true, false, 10000)
} def makePool(redisHost: String, redisPort: Int, redisTimeout: Int,
maxTotal: Int, maxIdle: Int, minIdle: Int, testOnBorrow: Boolean,
testOnReturn: Boolean, maxWaitMillis: Long): Unit = {
if (pool == null) {
val poolConfig = new GenericObjectPoolConfig()
poolConfig.setMaxTotal(maxTotal)
poolConfig.setMaxIdle(maxIdle)
poolConfig.setMinIdle(minIdle)
poolConfig.setTestOnBorrow(testOnBorrow)
poolConfig.setTestOnReturn(testOnReturn)
poolConfig.setMaxWaitMillis(maxWaitMillis)
pool = new JedisPool(poolConfig, redisHost, redisPort, redisTimeout) val hook = new Thread {
override def run = pool.destroy()
}
sys.addShutdownHook(hook.run)
}
} def getPool: JedisPool = {
assert(pool != null)
pool
}
} // Redis configurations
val maxTotal = 10
val maxIdle = 10
val minIdle = 1
val redisHost = "dn1"
val redisPort = 6379
val redisTimeout = 30000
InternalRedisClient.makePool(redisHost, redisPort, redisTimeout, maxTotal, maxIdle, minIdle)
val jedis = InternalRedisClient.getPool.getResource
while (results.hasNext) {
var item = results.next()
var key = item.getString(0)
var value = item.getLong(1)
jedis.hincrBy(ipLoginHashKey, key, value)
}
} ssc.start()
ssc.awaitTermination() }
} /** Case class for converting RDD to DataFrame */
case class Record(plat: String, uid: String, tm: String, country: String, location: String) /** Lazily instantiated singleton instance of SQLContext */
object SQLContextSingleton { @transient private var instance: SQLContext = _ def getInstance(sparkContext: SparkContext): SQLContext = {
if (instance == null) {
instance = new SQLContext(sparkContext)
}
instance
}
}
我们在开发环境进行测试的时候,使用 local[k] 部署模式,在本地启动 K 个 Worker 线程来进行计算,而这 K 个 Worker 在同一个 JVM 中,上面的示例,默认使用 local[k] 模式。这里我们需要普及一下 Spark 的架构,架构图来自 Spark 的官网,[链接地址]
这里,不管是在 local[k] 模式,Standalone 模式,还是 Mesos 或是 YARN 模式,整个 Spark Cluster 的结构都可以用改图来阐述,只是各个组件的运行环境略有不同,从而导致他们可能运行在分布式环境,本地环境,亦或是一个 JVM 实利当中。例如,在 local[k] 模式,上图表示在同一节点上的单个进程上的多个组件,而对于 YARN 模式,驱动程序是在 YARN Cluster 之外的节点上提交 Spark 应用,其他组件都是运行在 YARN Cluster 管理的节点上的。
而对于 Spark Cluster 部署应用后,在进行相关计算的时候会将 RDD 数据集上的函数发送到集群中的 Worker 上的 Executor,然而,这些函数做操作的对象必须是可序列化的。上述代码利用 Scala 的语言特性,解决了这一问题。
4.结果预览
在完成上述代码后,我们执行代码,看看预览结果如下,执行结果,如下所示:
4.1 启动生产线程
4.2 Redis 结果预览
5.总结
整体的实现内容不算太复杂,统计的业务指标,这里我们使用 SQL 来完成这部分工作,对比 Storm 来说,我们专注 SQL 的编写就好,难度不算太大。可操作性较为友好。
6.结束语
这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!
Spark 实时计算整合案例的更多相关文章
- demo2 Kafka+Spark Streaming+Redis实时计算整合实践 foreachRDD输出到redis
基于Spark通用计算平台,可以很好地扩展各种计算类型的应用,尤其是Spark提供了内建的计算库支持,像Spark Streaming.Spark SQL.MLlib.GraphX,这些内建库都提供了 ...
- 【Streaming】30分钟概览Spark Streaming 实时计算
本文主要介绍四个问题: 什么是Spark Streaming实时计算? Spark实时计算原理流程是什么? Spark 2.X下一代实时计算框架Structured Streaming Spark S ...
- Spark实时案例
1.概述 最近有同学问道,除了使用 Storm 充当实时计算的模型外,还有木有其他的方式来实现实时计算的业务.了解到,在使用 Storm 时,需要编写基于编程语言的代码.比如,要实现一个流水指标的统计 ...
- 56、Spark Streaming: transform以及实时黑名单过滤案例实战
一.transform以及实时黑名单过滤案例实战 1.概述 transform操作,应用在DStream上时,可以用于执行任意的RDD到RDD的转换操作.它可以用于实现,DStream API中所没有 ...
- 【转】Spark Streaming 实时计算在甜橙金融监控系统中的应用及优化
系统架构介绍 整个实时监控系统的架构是先由 Flume 收集服务器产生的日志 Log 和前端埋点数据, 然后实时把这些信息发送到 Kafka 分布式发布订阅消息系统,接着由 Spark Streami ...
- Spark Streaming实时计算框架介绍
随着大数据的发展,人们对大数据的处理要求也越来越高,原有的批处理框架MapReduce适合离线计算,却无法满足实时性要求较高的业务,如实时推荐.用户行为分析等. Spark Streaming是建立在 ...
- 基于Kafka的实时计算引擎如何选择?Flink or Spark?
1.前言 目前实时计算的业务场景越来越多,实时计算引擎技术及生态也越来越成熟.以Flink和Spark为首的实时计算引擎,成为实时计算场景的重点考虑对象.那么,今天就来聊一聊基于Kafka的实时计算引 ...
- Spark Streaming——Spark第一代实时计算引擎
虽然SparkStreaming已经停止更新,Spark的重点也放到了 Structured Streaming ,但由于Spark版本过低或者其他技术选型问题,可能还是会选择SparkStreami ...
- Spark练习之通过Spark Streaming实时计算wordcount程序
Spark练习之通过Spark Streaming实时计算wordcount程序 Java版本 Scala版本 pom.xml Java版本 import org.apache.spark.Spark ...
随机推荐
- Big Event in HDU(HDU1171)可用背包和母函数求解
Big Event in HDU HDU1171 就是求一个简单的背包: 题意:就是给出一系列数,求把他们尽可能分成均匀的两堆 如:2 10 1 20 1 结果是:20 10.才最均匀! 三 ...
- LINQ to SQL大全
LINQ to SQL语句 (1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的 ...
- java 继承多态的一些理解不和不理解
1.向上转型的一个误区 一直以为Child 继承Parent以后, Parent p = new Child(); p可以调用Child类中拓展Parent的方法,原来必须在强制转换成Child类才 ...
- 从SQLSERVER/MYSQL数据库中随机取一条或者N条记录
从SQLSERVER/MYSQL数据库中随机取一条或者N条记录 很多人都知道使用rand()函数但是怎麽使用可能不是每个人都知道 建立测试表 USE [sss] GO ,NAME ) DEFAULT ...
- 控制反转(Inversion of Control)之我的理解
关于控制反转(Inversion of Control),在具体实现上也有许多其它的叫法,如依赖倒置(Dependency Inversion Principles, DIP).依赖注入(Depend ...
- Nim教程【三】
这是国内第一个关于Nim的系列教程 (至少我百度和必应是没有找到类似的教程) 先说废话 有人说 Golang的编译器/工具链也学互联网行业跟风拿用户当测试,简直一点素质没有. 还有人说 Go社区的风气 ...
- jdbc实现简单的增删改查
先是Book类. 略 然后一个主页,写一个表单,提交Book的信息到AddBook. 略 AddBook.jsp连接jdbc,并向Book表插入. <%@ page language=" ...
- [转]HTTP协议及其请求头分析
众所周知,Internet的基本协议是TCP/IP协议,目前广泛采用的FTP.Archie Gopher等是建立在TCP/IP协议之上的应用层协议,不同的协议对应着不同的应用. WWW服务器使用 ...
- Command模式
Command模式只是封装了一个没有任何变量的函数. interface Command{ void Excute();} 具有强烈的分解功能的味道.把函数层面的任务提升到了类的层面(一个类仅仅是为了 ...
- NGUI ScrollView 循环 Item 实现性能优化
今天来说说一直都让我在项目中头疼的其中一个问题,NGUI 的scrollView 列表性能问题,实现循环使用item减少性能上的开销. 希望能够给其他同学们使用和提供一个我个人的思路,这个写的不是太完 ...