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

场景模拟

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

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. 将数组分割为几个等长度的子数组(使用slice)

    先了解一下slice方法: slice() 1.定义:slice()可从已有数组中截取返回指定的元素,形成一个新的数组: 语法:arrayObject.slice(start,end): 参数 描述 ...

  2. Linux Ubuntu从零开始部署web环境及项目 -----快捷键设置(四)

    上篇将了如何在linux部署web项目,这篇介绍如何设置常用快捷键 一.路径快捷键设置 临时快捷键设置:  执行XShel,输入: alias 'aa=cd /etc/sysconfig'       ...

  3. .NET 动态脚本语言

    Script.NET (S#) 是一种允许为你的应用程序自定义行为,与本地.NET对象.类型和组件交互动态的脚本语言.托管应用程序本身可以改变一个xml配置脚本运行时的默认行为,更换绑定的方法,属性, ...

  4. oracle pctfree和pctused 详解

    一.建立表时候,注意PCTFREE参数的作用 PCTFREE:为一个块保留的空间百分比,表示数据块在什么情况下可以被insert,默认是10,表示当数据块的可用空间低于10%后,就不可以被insert ...

  5. oracle pl/sql 变量

    一.变量介绍在编写pl/sql程序时,可以定义变量和常量:在pl/sql程序中包括有:1).标量类型(scalar)2).复合类型(composite) --用于操作单条记录3).参照类型(refer ...

  6. 使用 TUN 设备实现一个简单的 UDP 代理隧道

    若要实现在 Linux 下的代理程序,方法有很多,比如看着 RFC 1928 来实现一个 socks5 代理并自行设置程序经过 socks5 代理等方式,下文是使用 Linux 提供的 tun/tap ...

  7. 第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ---干净的终止一个线程

    干净的终止一个线程  我曾经在第2章产生一个后台线程,用以输出一张屏幕外的 bitmap 图.我们必须解决的一个最复杂的问题就是,如果用户企图结束程序,而这张bitmap 图尚未完成,怎么办?第2章的 ...

  8. 如何写一个jquery插件

      本文总结整理一下如何写一个jquery插件?虽然现今各种mvvm框架异常火爆,但是jquery这个陪伴我们成长,给我们带来很多帮助的优秀的库不应该被我们抛弃,写此文章,作为对以往欠下的笔记的补充, ...

  9. mint-ui vue双向绑定

    由于最近项目需求,用上了mint-ui来重构移动端页面,从框架本身来讲我觉得很强大了,用起来也很不错,但是文档就真的是,,,,让我无言以对,给的api对于我们这些小菜鸟来讲真的是处处是坑呀(ps:用v ...

  10. python --- json模块和pickle模块详解

    json:JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式(用于数据序列化和反序列化).(适用于多种编程语言,可以与其他编程语言做数据交换 ...