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

场景模拟

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

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. Mybatis(1)

    properties 标签的作用引入外部properties 文件的内容typeAliases 的作用可以用package这个标签批量给这个包下的所有类起一个别名 name属性写包的名字. 默认别名为 ...

  2. 云计算之阿里仓库停止openstack mitaka源报错“No package centos-release-openstack-mitaka available.”

    之前学习了一个月的openstack的mitaka版本,写完脚本放置一段时间,最近准备正式部署突然发现 No package centos-release-openstack-mitaka avail ...

  3. JVM菜鸟进阶高手之路一(一次与笨神,阿飞近距离接触修改JVM)

    转载请注明原创出处,谢谢! 今天在JVMPocket群里面看见,阿牛发了一个gc截图,之后ak47截图了特别恐怖,我就觉得好奇,去看看服务情况,截图日志如下 关于jstat命令详情可以参考:https ...

  4. [UIKit学习]08.关于自定义控件

    自定义控件 选用xib用自定义view代码与xib相关联 示例代码 + (instancetype)shopView { return [self shopViewWithShop:nil]; } + ...

  5. Java对象大小:size和retained size

    最近看到网上很多文章讲如何计算java对象的大小(size),很多观点不敢苟同. 这是其中一篇比较靠前的文章,写的也比较全面: http://blog.csdn.net/iter_zc/article ...

  6. devstack安装openstack newton版本

    准备使用devstack安装openstack N版,搞一套开发环境出来.一连整了4天,遇到各种问题,各种错误,一直到第4天下午4点多才算完成. 在这个过程中感觉到使用devstack搭建openst ...

  7. Oracle 每隔5分钟产生2个clsc*.log文件

    环境: OS:HP-UNIX 数据库:11.2.0.4   双机RAC (一)现象 在清理Oracle日志的时候,发现在$ORACLE_HOME/log/{instance_id}/client下面存 ...

  8. 寻找bug并消灭系列——记录在Android开发所遇到的bug(一)

    之前使用了Android Studio的插件直接为button绑定了监听器,并实现onClick方法(我的onClick方法无论点击哪一个都是要实现setcontentview这个方法设置layout ...

  9. Canal 同步异常分析:Could not find first log file name in binary log index file

    文章首发于[博客园-陈树义],点击跳转到原文Canal同步异常分析:Could not find first log file name in binary log index file. 公司搜索相 ...

  10. M-自适应宽高样式

    1 绝对定位 position: absolute; top: 0px; bottom: 0px; left: 0px; width: 100%; overflow: hidden;