文章目录
环境部署
相关环境部署的笔记如下:
-
zookeeper和spark安装:在zookeeper上搭建Spark集群的小笔记
-
kafka安装和使用:kafka学习笔记
-
sbt下载和安装:Linux无坑安装sbt
项目主要架构
数据总体上可以分为静态数据和流数据。对静态数据和流数据的处理,对应着两种截然不同的计算模式:批量计算和实时计算。批量计算以“静态数据”为对象,可以在很充裕的时间内对海量数据进行批量处理,计算得到有价值的信息。Hadoop就是典型的批处理模型,由HDFS和HBase存放大量的静态数据,由MapReduce负责对海量数据执行批量计算。流数据必须采用实时计算,实时计算最重要的一个需求是能够实时得到计算结果,一般要求响应时间为秒级。当只需要处理少量数据时,实时计算并不是问题;但是,在大数据时代,不仅数据格式复杂、来源众多,而且数据量巨大,这就对实时计算提出了很大的挑战。因此,针对流数据的实时计算——流计算,应运而生。
流计算处理过程包括数据实时采集、数据实时计算和实时查询服务。
- 数据实时采集:数据实时采集阶段通常采集多个数据源的海量数据,需要保证实时性、低延迟与稳定可靠。目前有许多互联网公司发布的开源分布式日志采集系统均可满足每秒数百MB的数据采集和传输需求,如Kafka和Flume等。
- 数据实时计算:流计算处理系统接收数据采集系统不断发来的实时数据,实时地进行分析计算,并反馈实时结果。
- 实时查询服务:流计算的第三个阶段是实时查询服务,经由流计算框架得出的结果可供用户进行实时查询、展示或储存。(一般是把计算结果实时地推送给用户)
-
实时计算架构
- python读取csv文件,传送数据到kafka
- spark-streaming和kafka集成
- spark-streaming读取传送到kafka的数据,进行实时统计,统计后的数据再推送到kafka
- flask实时接收处理好的kafka数据,利用flask-socketio推送到客户端,进行绘图展示
-
批量计算架构
- 把待分析的数据集导入hive
- hive编写HQL语句批量分析数据
- sqoop把hive中分析后的结果导入mysql中
- flask读取mysql数据,利用echarts绘图进行可视化分析
具体步骤
流计算步骤
在统计淘宝双11的各省份的实时销量、淘宝双11的实时年龄分布、淘宝双11的各购物行为所占的比重、淘宝双11那天的日活量均采用流计算。
python连接kafka
在这一步中,python读取csv文件并连接kafka,把数据传送过去
需要安装pykafka、pandas等第三方库
pip install pykafka
python连接kafka代码
# coding: utf-8
import pandas as pd
from pykafka import KafkaClient
import json
class DataHandle:
def __init__(self):
self.path = "data_format/user_log.csv"
self.double11_path = "data_format/double11_user_log.csv"
self.double11Buy_path = "data_format/double11Buy_user_log.csv"
def select_double11AndBuy(self):
data = pd.read_csv(self.path)
data.drop_duplicates(inplace = True)
# #筛选出双11那天的用户购买记录数据并保存
buy_data = data.loc[data['action'] == 2]
double11_buyData = buy_data[(buy_data["month"] == 11)&(buy_data["day"] == 11)]
double11_buyData.to_csv(self.double11Buy_path,index=None)
#筛选出双11的用户行为记录(点击、收藏、购买、关注)
double11_data = data[(data["month"] == 11)&(data["day"] == 11)]
double11_data.to_csv(self.double11_path,index = None)
return 0
def get_area_data(self):
"""获得双11那天的省份销量数据"""
area_data = pd.read_csv(self.double11Buy_path)
return area_data["province"]
def get_transform_data(self):
transform_data = pd.read_csv(self.double11_path)
return transform_data["action"]
def get_age_data(self):
transform_data = pd.read_csv(self.double11Buy_path)
return transform_data["age_range"]
class operateKafka:
def __init__(self):
self.myhosts = "Master:9092"
self.client = KafkaClient(hosts=self.myhosts)
def sendMessage(self, dataList,topic_name):
topic = self.client.topics[topic_name]
with topic.get_sync_producer() as producer:
for data in dataList:
mydict = str(data)
python_to_json = json.dumps(mydict, ensure_ascii=False)
producer.produce((str(python_to_json)).encode())
if __name__ == "__main__":
dataHandle = DataHandle()
myopKafka = operateKafka()
dataHandle.select_double11AndBuy()
#各个地区的销量
myopKafka.sendMessage(dataHandle.get_area_data().values,"area_data2")
myopKafka.sendMessage(dataHandle.get_transform_data().values,"action_class")
myopKafka.sendMessage(dataHandle.get_age_data().values,"age_data")
执行上述代码之后可在kafka集群这边任意一个节点上测试是否能接收到数据,从而判断数据是否成功传送到kafka。(以实时省份销量数据为例)
cd /usr/local/kafka/bin
./kafka-console-consumer.sh --bootstrap-server Master:9092 --topic area_data2 --from-beginning
spark-streaming集成kafka
Spark Streaming的基本原理是将实时输入数据流以时间片(秒级)为单位进行拆分,然后经Spark引擎以类似批处理的方式处理每个时间片数据,能做到秒级响应,不能做到毫秒级响应。Spark Streaming实际上是仿照流计算,并不是真正实时的流计算框架。
kafka安装:kafka学习笔记,为和下面的spark-streaming-kafka集成jar包适配,建议装0.8或者0.10版本的kafka(分情况,看spark版本)。
下载spark-streaming-kafka的jar包:
spark2.3.0版本以下(不包括2.3.0)的可以下载spark-streaming-kafka-0-8的集成:spark-streaming-kafka-0-8,选择与自己scala和spark版本相对应的jar包下载
spark2.3.0以上版本(包括2.3.0)的可以下载spark-streaming-kafka-0-10以上的集成:spark-streaming-kafka-0-10
把下载的jar包拷贝到/usr/local/spark/jars下
在/usr/local/spark/jars目录下,新建一个kafka目录,把kafka安装目录的libs目录下的所有jar文件复制到/usr/local/spark/jars/kafka目录下,
cd /usr/local/kafka/libs
cp ./* /usr/local/spark/jars/kafka
下面测试spark和kafka环境是否连通
启动spark-shell
cd /usr/local/spark/bin
./spark-shell --master spark://Master:7077
在spark-shell里引入如下jar包
import org.apache.spark.streaming.kafka010._
若引入jar包不出错,说明kafka和spark环境已经连通
编写并运行spark-streaming程序(实时词频统计)
scala的官方API文档:https://www.scala-lang.org/api/2.11.12/#scala.package,此文档对应的scala版本是2.11.12,也可以到:https://docs.scala-lang.org/api/all.html 查找与自己的scala版本相对应的API文档。
spark的官方API文档:http://spark.apache.org/docs/2.4.5/api/scala/index.html#org.apache.spark.package,此文档对应的scala版本是2.11.12,也可以到:http://spark.apache.org/docs/ 查找与自己的spark版本相对应的API文档。
创建项目spark_connect_kafka
cd /usr/local/spark/mycode
mkdir spark_connect_kafka
cd spark_connect_kafka
mkdir -p src/main/scala
编写scala代码
vi kafkaCount.scala
package org.apache.spark.examples.streaming
import java.util.HashMap
import org.apache.kafka.clients.producer.{
KafkaProducer, ProducerConfig, ProducerRecord}
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.common.serialization.StringDeserializer
import org.json4s._
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.write
import org.apache.spark.SparkConf
import org.apache.spark.streaming._
import org.apache.spark.streaming.Interval
import org.apache.spark.streaming.kafka010._
object KafkaWordCount {
//数据格式化时需要
implicit val formats = DefaultFormats
def main(args: Array[String]): Unit={
//判断参数输入是否齐全,当不齐全时退出程序
if (args.length < 4) {
System.err.println("Usage: KafkaWordCount <brokers> <groupId> <topics>")
System.exit(1)
}
/* 输入的四个参数分别代表着
* 1. brokers为要消费的topics所在的broker地址
* 2. group为消费者所在的组
* 3. topics该消费者所消费的topics
* 4. topics2为要推送到kafka的数据分析结果的topics
*/
//把输入的4个参数封装成数组
val Array(brokers, groupId, topics,topics2) = args
val sparkConf = new SparkConf().setAppName("KafkaWordCount")
val ssc = new StreamingContext(sparkConf, Seconds(1))
//把检查点写入本地磁盘文件,不写入hadoop不必启动hadoop
ssc.checkpoint("file:///home/hadoop/data/checkpoint")
//一个consumer-group可以消费多个topic,这里把topics按照“,”分割放进set里(虽然我们一般只是传进去一个topic)
val topicsSet = topics.split(",").toSet
val kafkaParams = Map[String, Object](
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> brokers,
ConsumerConfig.GROUP_ID_CONFIG -> groupId,
"auto.offset.reset" -> "earliest",
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer])
//创建连接Kafka的消费者链接
val messages = KafkaUtils.createDirectStream[String, String](
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String, String](topicsSet, kafkaParams))
//获取输入的每行数据,将输入的每行用空格分割成一个个word(这里的代码逻辑是词频统计的逻辑)
val lines = messages.map(_.value)
val words = lines.flatMap(_.split(" "))
// 对每一秒的输入数据进行reduce,然后将reduce后的数据发送给Kafka
val wordCounts = words.map(x => (x, 1L))
.reduceByKeyAndWindow(_+_,_-_, Seconds(3), Seconds(1), 3).foreachRDD(rdd => {
if(rdd.count !=0 ){
//创建kafka生产者配置文件(数据结构为HashMap,初始为空,不断添加配置)
val props = new HashMap[String, Object]()
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "Master:9092")
//key序列号方式
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringSerializer")
//value序列号方式
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
"org.apache.kafka.common.serialization.StringSerializer")
//实例化一个Kafka生产者
val producer = new KafkaProducer[String, String](props)
//rdd.colect即将rdd中数据转化为数组,然后write函数将rdd内容转化为json格式
val str = write(rdd