在这篇文章里,我们模拟了一个场景,实时分析订单数据,统计实时收益。

场景模拟

我试图覆盖工程上最为常用的一个场景:

1)首先,向Kafka里实时的写入订单数据,JSON格式,包含订单ID-订单类型-订单收益

2)然后,spark-streaming每十秒实时去消费kafka中的订单数据,并以订单类型分组统计收益

3)最后,spark-streaming统计结果实时的存入本地MySQL。

前提条件

安装

1)spark:我使用的yarn-client模式下的spark,环境中集群客户端已经搞定

2)zookeeper:我使用的是这个集群:10.93.21.21:2181,10.93.18.34:2181,10.93.18.35:2181

3)kafka:我使用的是standalone模式:10.93.21.21:9093

4)mysql:10.93.84.53:3306

语言

python:pykafka,pip install pykafka

java:spark,spark-streaming

下面开始

1、数据写入kafka

  • kafka写入

我们使用pykafka模拟数据实时写入,代码如下:

kafka_producer.py

# -* coding:utf8 *-
import time
import json
import uuid
import random
import threading
from pykafka import KafkaClient # 创建kafka实例
hosts = '10.93.21.21:9093'
client = KafkaClient(hosts=hosts) # 打印一下有哪些topic
print client.topics # 创建kafka producer句柄
topic = client.topics['kafka_spark']
producer = topic.get_producer() # work
def work():
while 1:
msg = json.dumps({
"id": str(uuid.uuid4()).replace('-', ''),
"type": random.randint(1, 5),
"profit": random.randint(13, 100)})
producer.produce(msg) # 多线程执行
thread_list = [threading.Thread(target=work) for i in range(10)]
for thread in thread_list:
thread.setDaemon(True)
thread.start() time.sleep(60) # 关闭句柄, 退出
producer.stop()

可以看到,我们写入的形式是一个json,订单id是一个uuid,订单类型type从1-5随机,订单收益profit从13-100随机,形如

{"id": ${uid}, "type": 1, "profit": 30}

注意:1)python对kafka的读写不需要借助zookeeper,2)使用多线程的形式写入,让数据量具有一定的规模。

执行producer,会持续写入数据1分钟。

python kafka_producer.py
  • 验证一下

kafka_consumer.py

# -* coding:utf8 *-
from pykafka import KafkaClient hosts = '10.93.21.21:9093'
client = KafkaClient(hosts=hosts)
# 消费者
topic = client.topics['kafka_spark']
consumer = topic.get_simple_consumer(consumer_group='test', auto_commit_enable=True, auto_commit_interval_ms=1,
consumer_id='test')
for message in consumer:
if message is not None:
print message.offset, message.value

执行,可以消费kafka刚才写入的数据

python kafka_consumer.py

2、spark-streaming

1)先解决依赖

其中比较核心的是spark-streaming和kafka集成包spark-streaming-kafka_2.10,还有spark引擎spark-core_2.10

json和mysql看大家爱好。

pom.xml

<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka_2.10</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.10</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.10</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.19</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
</dependencies>

2)MySQL准备

  • 建表

我们的结果去向是MySQL,先建立一个结果表。

id:主键,自增id

type:订单类型

profit:每个spark batch聚合出的订单收益结果

time:时间戳

CREATE TABLE `order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` int(11) DEFAULT NULL,
`profit` int(11) DEFAULT NULL,
`time` mediumtext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=utf8
  • Java客户端

采用了单例线程池的模式简单写了一下。

ConnectionPool.java

package com.xiaoju.dqa.realtime_streaming;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.LinkedList; public class ConnectionPool {
private static LinkedList<Connection> connectionQueue; static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} public synchronized static Connection getConnection() {
try {
if (connectionQueue == null) {
connectionQueue = new LinkedList<Connection>();
for (int i = 0; i < 5; i++) {
Connection conn = DriverManager.getConnection(
"jdbc:mysql://10.93.84.53:3306/big_data",
"root",
"1234");
connectionQueue.push(conn);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return connectionQueue.poll(); }
public static void returnConnection(Connection conn){connectionQueue.push(conn);}
}

3)代码实现

我用java写的,不会用scala很尴尬。

即时用java整个的处理过程依然比较简单。跟常见的wordcount也没有多大的差别。

SparkStreaming特点

spark的特点就是RDD,通过对RDD的操作,来屏蔽分布式运算的复杂度。

而spark-streaming的操作对象是RDD的时间序列DStream,这个序列的生成是跟batch的选取有关。例如我这里Batch是10s一个,那么每隔10s会产出一个RDD,对RDD的切割和序列的生成,spark-streaming对我们透明了。唯一暴露给我们的DStream和原生RDD的使用方式基本一致。

这里需要讲解一下MySQL写入注意的事项。

MySQL写入

在处理mysql写入时使用了foreachPartition方法,即,在foreachPartition中使用borrow mysql句柄。

这样做的原因是:

1)你无法再Driver端创建mysql句柄,并通过序列化的形式发送到worker端

2)如果你在处理rdd中创建mysql句柄,很容易对每一条数据创建一个句柄,在处理过程中很快内存就会溢出。

OrderProfitAgg.java

package com.xiaoju.dqa.realtime_streaming;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaPairReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka.KafkaUtils;
import scala.Tuple2; import java.sql.Connection;
import java.sql.Statement;
import java.util.*; /*
* 生产者可以选用kafka自带的producer脚本
* bin/kafka-console-producer.sh --broker-list localhost:9093 --topic test
* */
public class OrderProfitAgg { public static void main(String[] args) throws InterruptedException {
/*
* kafka所注册的zk集群
* */
String zkQuorum = "10.93.21.21:2181,10.93.18.34:2181,10.93.18.35:2181"; /*
* spark-streaming消费kafka的topic名称, 多个以逗号分隔
* */
String topics = "kafka_spark,kafka_spark2"; /*
* 消费组 group
* */
String group = "bigdata_qa"; /*
* 消费进程数
* */
int numThreads = 2; /*
* 选用yarn队列模式, spark-streaming程序的app名称是"order profit"
* */
SparkConf sparkConf = new SparkConf().setMaster("yarn-client").setAppName("order profit"); /*
* 创建sc, 全局唯一, 设置logLevel可以打印一些东西到控制台
* */
JavaSparkContext sc = new JavaSparkContext(sparkConf);
sc.setLogLevel("WARN"); /*
* 创建jssc, spark-streaming的batch是每10s划分一个
* */
JavaStreamingContext jssc = new JavaStreamingContext(sc, Durations.seconds(10)); /*
* 准备topicMap
* */
Map<String ,Integer> topicMap = new HashMap<String, Integer>();
for (String topic : topics.split(",")) {
topicMap.put(topic, numThreads);
} /*
* kafka数据流
* */
List<JavaPairReceiverInputDStream<String, String>> streams = new ArrayList<JavaPairReceiverInputDStream<String, String>>();
for (int i = 0; i < numThreads; i++) {
streams.add(KafkaUtils.createStream(jssc, zkQuorum, group, topicMap));
}
/*
* 从kafka消费数据的RDD
* */
JavaPairDStream<String, String> streamsRDD = streams.get(0);
for (int i = 1; i < streams.size(); i++) {
streamsRDD = streamsRDD.union(streams.get(i));
} /*
* kafka消息形如: {"id": ${uuid}, "type": 1, "profit": 35}
* 统计结果, 以type分组的总收益
* mapToPair, 将kafka消费的数据, 转化为type-profit key-value对
* reduceByKey, 以type分组, 聚合profit
* */
JavaPairDStream<Integer, Integer> profits = streamsRDD.mapToPair(new PairFunction<Tuple2<String, String>, Integer, Integer>() {
@Override
public Tuple2<Integer, Integer> call(Tuple2<String, String> s_tuple2) throws Exception {
JSONObject jsonObject = JSON.parseObject(s_tuple2._2);
return new Tuple2<Integer, Integer>(jsonObject.getInteger("type"), jsonObject.getInteger("profit"));
}
}).reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer i1, Integer i2) throws Exception {
return i1 + i2;
}
}); /*
* 输出结果到MySQL
* 需要为每一个partition创建一个MySQL句柄, 使用foreachPartition
* */
profits.foreachRDD(new Function<JavaPairRDD<Integer, Integer>, Void>() {
@Override
public Void call(JavaPairRDD<Integer, Integer> integerIntegerJavaPairRDD) throws Exception { integerIntegerJavaPairRDD.foreachPartition(new VoidFunction<Iterator<Tuple2<Integer, Integer>>>() {
@Override
public void call(Iterator<Tuple2<Integer, Integer>> tuple2Iterator) throws Exception {
Connection connection = ConnectionPool.getConnection();
Statement stmt = connection.createStatement();
long timestamp = System.currentTimeMillis();
while(tuple2Iterator.hasNext()) {
Tuple2<Integer, Integer> tuple = tuple2Iterator.next();
Integer type = tuple._1;
Integer profit = tuple._2;
String sql = String.format("insert into `order` (`type`, `profit`, `time`) values (%s, %s, %s)", type, profit, timestamp);
stmt.executeUpdate(sql);
}
ConnectionPool.returnConnection(connection);
}
});
return null;
}
}); /*
* 开始计算, 等待计算结束
* */
jssc.start();
try {
jssc.awaitTermination();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
jssc.close();
}
}
}

4)打包方法

编写pom.xml build tag。

mvn clean package打包即可。

pom.xml

<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<!--这里要替换成jar包main方法所在类 -->
<!--<mainClass>com.bigdata.qa.hotdog.driver.WordCount</mainClass>-->
<mainClass>com.xiaoju.dqa.realtime_streaming.OrderProfitAgg</mainClass> </manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- 指定在打包节点执行jar包合并操作 -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>

3、执行与结果

1)执行kafka_producer.py

python kafka_producer.py

2) 执行spark-streaming

这里使用的是默认参数提交yarn队列。

spark-submit --queue=root.XXXX realtime-streaming-1.0-SNAPSHOT-jar-with-dependencies.jar

3)查看结果

到MySQL中查看结果,每隔10秒会聚合出type=1-5的5条数据。

例如第一条数据,就是type=4这种类型的业务,在10s内收益是555473元。业务量惊人啊。哈哈。

完结。

使用spark-streaming实时读取Kafka数据统计结果存入MySQL的更多相关文章

  1. Spark Streaming 实现读取Kafka 生产数据

    在kafka 目录下执行生产消息命令: ./kafka-console-producer  --broker-list nodexx:9092 --topic  201609 在spark bin 目 ...

  2. spark读取kafka数据 createStream和createDirectStream的区别

    1.KafkaUtils.createDstream 构造函数为KafkaUtils.createDstream(ssc, [zk], [consumer group id], [per-topic, ...

  3. Spark Streaming的容错和数据无丢失机制

    spark是迭代式的内存计算框架,具有很好的高可用性.sparkStreaming作为其模块之一,常被用于进行实时的流式计算.实时的流式处理系统必须是7*24运行的,同时可以从各种各样的系统错误中恢复 ...

  4. 【转】Spark Streaming 实时计算在甜橙金融监控系统中的应用及优化

    系统架构介绍 整个实时监控系统的架构是先由 Flume 收集服务器产生的日志 Log 和前端埋点数据, 然后实时把这些信息发送到 Kafka 分布式发布订阅消息系统,接着由 Spark Streami ...

  5. Spark练习之通过Spark Streaming实时计算wordcount程序

    Spark练习之通过Spark Streaming实时计算wordcount程序 Java版本 Scala版本 pom.xml Java版本 import org.apache.spark.Spark ...

  6. spark streaming中维护kafka偏移量到外部介质

    spark streaming中维护kafka偏移量到外部介质 以kafka偏移量维护到redis为例. redis存储格式 使用的数据结构为string,其中key为topic:partition, ...

  7. SparkStreaming直连方式读取kafka数据,使用MySQL保存偏移量

    SparkStreaming直连方式读取kafka数据,使用MySQL保存偏移量 1. ScalikeJDBC 2.配置文件 3.导入依赖的jar包 4.源码测试 通过MySQL保存kafka的偏移量 ...

  8. Spark Streaming实时计算框架介绍

    随着大数据的发展,人们对大数据的处理要求也越来越高,原有的批处理框架MapReduce适合离线计算,却无法满足实时性要求较高的业务,如实时推荐.用户行为分析等. Spark Streaming是建立在 ...

  9. 【慕课网实战】Spark Streaming实时流处理项目实战笔记十三之铭文升级版

    铭文一级: 第10章 Spark Streaming整合Kafka spark-submit \--class com.imooc.spark.KafkaReceiverWordCount \--ma ...

随机推荐

  1. java基础知识6-- 抽象类,抽象方法,接口,构造方法,类方法等易混淆的知识点

    一.抽象类和抽象方法 (B 继承  抽象类A) 抽象类:有抽象方法的一定是抽象类 抽象方法:方法名前有abstract修饰,且方法没有方法体,即{},不需要实现任何功能,只是声明 1.抽象类中的方法有 ...

  2. log4j 日志脱敏处理 + java properties文件加载

    Java 加载Properties 配置文件: ResourceBundle bundle = ResourceBundle.getBundle("log4j_filter"); ...

  3. websphere部署--web应用-以自己的项目为例

    启动websphere: 1) 启动Manager: /home/wasadmin/IBM/WebSphere/AppServer/bin/startManager.sh 2) 启动Node:     ...

  4. SimpleRpc-系统边界以及整体架构

    系统边界 什么是系统边界?系统边界就是在系统设计之初,对系统所要实现的功能进行界定,不乱添加,不多添加.这么做的好处就是,系统简单明了,主旨明确,方便开发和用户使用.举个例子,一个自动售货机的本职工作 ...

  5. CANVAS模仿龙卷风特效

    大学时候,有一段时间对flash比较感兴趣.去图书馆借了一本很厚很厚的falsh书籍. 翻了几页之后,就再没有往后看过.印象比较深的是作者说他用flash完成了一个龙卷风效果. 一直到现在我也没有看到 ...

  6. 编译httpd细节

    html { font-family: sans-serif } body { margin: 0 } article,aside,details,figcaption,figure,footer,h ...

  7. Java 数组扩容

    在添加数据到达数组的上限的时候数组进行扩容: public void resizeArrayCaptcity(){ if(size>=arr.length){ Emp [] arr2=new   ...

  8. Linux学习——shell编程之变量

    shell编程之变量:Linux shell编程基础中的变量. 包括Bash变量的分类和各变量的详细使用,如:用户自定义变量.环境变量.语系变量.位置参数变量和预定义变量. 1:什么是Bash变量? ...

  9. C++ Primer Plus 6 第二章

    // myfirst.cpp--displays a message #include <iostream> // a PREPROCESSOR directive int main() ...

  10. 【机器学习实战】第6章 支持向量机(Support Vector Machine / SVM)

    第6章 支持向量机 <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/lates ...