spark+kafka实时数据分析

spark+kafka实时数据分析

一、项目内容

1. 项目流图

在这里插入图片描述

环境要求

centos 7以上
Spark: 2.1.0
Scala: 2.11.8
kafka: 0.8.2.2
Python: 3.x(3.0以上版本)
Flask: 0.12.1
Flask-SocketIO: 2.8.6
kafka-python: 1.3.3

2. 数据处理和python操作kafka

本项目采用的数据集压缩包为data_format.zip点击这里下载data_format.zip
链接: data_format.zip.数据集,该数据集压缩包是淘宝2015年双11前6个月(包含双11)的交易数据(交易数据有偏移,但是不影响实验的结果),里面包含3个文件,分别是用户行为日志文件user_log.csv 、回头客训练集train.csv 、回头客测试集test.csv. 在这个案例中只是用user_log.csv这个文件,下面列出文件user_log.csv的数据格式定义:
用户行为日志user_log.csv,日志中的字段定义如下:
user_id | 买家id
item_id | 商品id
cat_id | 商品类别id
merchant_id | 卖家id
brand_id | 品牌id
month | 交易时间:月
day | 交易事件:日
action | 行为,取值范围{0,1,2,3},0表示点击,1表示加入购物车,2表示购买,3表示关注商品
age_range | 买家年龄分段:1表示年龄<18,2表示年龄在[18,24],3表示年龄在[25,29],4表示年龄在[30,34],5表示年龄在[35,39],6表示年龄在[40,49],7和8表示年龄>=50,0和NULL则表示未知
gender | 性别:0表示女性,1表示男性,2和NULL表示未知
province| 收获地址省份
数据具体格式如下:
在这里插入图片描述
这个项目实时统计每秒中男女生购物人数,因此针对每条购物日志,我们只需要获取gender即可,然后发送给Kafka,接下来Spark Streaming再接收gender进行处理。

3. 数据处理展示结果

(1)数据读取并放入kafka
在这里插入图片描述
(2) 数据处理结果
在这里插入图片描述

二、项目环境搭建

1. 安装Java环境

1.1 官网下载java安装包,解压缩到对应目录
下载上传对应文件到服务器opt目录下面,然后进行解压

tar -zxvf  jdk-8u77-linux-x64.tar.gz  -C  /usr/hadoop

1.2. 配置java环境变量

vi /etc/profile
文件底部添加该配置文件JAVA_HOME配置的路径和自己java安装位置一致
export JAVA_HOME=/usr/hadoop/jdk1.8.0_77
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=$PATH:$JAVA_HOME/bin:$PATH
保存退出
让配置文件生效
source  /etc/profile

1.3 查看java环境是否安装成功

java –version 

在这里插入图片描述

2. 安装hadoop

2.1. 配置hosts文件
将各服务器的ip和名称配置到/etc/hosts文件中
此步骤目的是让服务器之间可以通过服务器名称来访问。使用ifconfig命令可以查看本机ip,使用hostname命令可以查看本服务器名称。使用vi命令编辑/etc/hosts文件,将三台服务器的ip和名称写入文件,然后输入Ecs键,输入 :wq (冒号加wq)保存退出。注意:三台服务器上均需要配置!
修改主机名称

hostnamectl set-hostname master

参考配置(服务器名称和ip请根据实际情况配置):
master 表示名字节点的 hostname 10.67.15.126 是其IP地址

10.67.15.126 master
10.67.15.121 slave1
10.67.15.122 slave2

2.2. 下载hadoop
百度下载并上传到服务器opt目录下面
然后解压到目录/usr/hadoop下
(解压缩后文件夹为hadoop-2.7.7)

tar -zxvf 解压文件名  -C  /usr/hadoop

2.3. 配置环境文件
在hadoop-2.7.7目录下创建四个目录:tmp、hdfs、hdfs/data、hdfs/name配置hadoop集群是需要在每一台服务器上都配置,这里我们只在主服务器上完成配置,配置完成之后我们将配置好的hadoop复制到其他服务器即可。步骤3 配置hadoop 中的所有步骤均在主服务器上操作。可以使用mkdir命令来创建目录。后面我们配置hadoop的时候会用到这些目录,三个目录的作用如下:

tmp目录用来存储hadoop的临时文件,
hdfs/data目录用来存储hdfs的文件块
hdfs/name目录用来存储元数据信息

2.3.1. 配置core-site.xml文件

进入hadoop-2.7.7目录下的etc/hadoop目录下,修改core-site.xml文件,在配置中增加如下配置项:
fs.defaultFS :远程访问hdfs系统文件的方式,一般使用hdfs://<namenode服务器名>:9000
hadoop.tmp.dir:hadoop临时目录,使用我们之前创建的tmp目录,格式file:<tmp路径>
io.file.buffer.size: 流文件的缓冲区,单位为字节。这里配置:131072 表示128KB
参考配置如下:master为NameNode服务器名称(请根据现场实际情况配置)。

<property> 
    <name>fs.defaultFS</name> 
    <value>hdfs://master:9000</value> 
</property> 
<property> 
    <name>hadoop.tmp.dir</name> 
    <value>file:/usr/hadoop/hadoop-2.7.7/tmp</value> 
</property> 
<property> 
        <name>io.file.buffer.size</name> 
        <value>131702</value> 
</property>

2.3.2. 配置hdfs-site.xml文件
进入hadoop-2.7.3目录下的etc/hadoop目录下,修改hdfs-site.xml文件,该文件主要是hdfs系统的相关配置,在配置中增加如下配置项:
dfs.namenode.name.dir :使用之前我们创建的hdfs/name目录
dfs.datanode.data.dir:使用之前我们创建的hdfs/data目录
dfs.replication: 文件保存的副本数
dfs.namenode.secondary.http-address:http访问hdfs系统的地址(一般使用服务器名称)和端口
dfs.webhdfs.enabled:是否开启web访问hdfs系统。设置为true可以让外部系统使用http协议访问hdfs系统。

参考配置如下:master为NameNode服务器名称(请根据现场实际情况配置)。

<property>
        <name>dfs.namenode.name.dir</name>
        <value>file:/usr/hadoop/hadoop-2.7.7/hdfs/name/</value>
</property>
<property>
        <name>dfs.datanode.data.dir</name>
        <value>file:/usr/hadoop/hadoop-2.7.7/hdfs/data</value>
</property>
<property>
        <name>dfs.replication</name>
        <value>2</value>
</property>
<property>
        <name>dfs.namenode.secondary.http-address</name>
        <value>master:9001</value>
</property>
<property>
<name>dfs.webhdfs.enabled</name>
<value>true</value>
</property>

2.3.3. 配置mapred-site.xml文件
mappred.xml文件中主要配置mapreduce相关的配置。
进入hadoop-2.7.3目录下的etc/hadoop目录下,将文件mapred-site.xml.template使用mv命令重命名为mapred-site.xml,并修改该文件增加如下配置项:
mapreduce.framework.name :运行mapreduce使用的框架,2.0以上的都是用的yarn
mapreduce.jobhistory.address:历史服务器的地址
mapreduce.jobhistory.webapp.address: 历史服务器的web访问地址

参考配置如下:master为NameNode服务器名称(请根据现场实际情况配置)。

<property>
      <name>mapreduce.framework.name</name>
      <value>yarn</value>
</property>
<property>
      <name>mapreduce.jobhistory.address</name>
      <value>master:10020</value>
</property>
<property>
      <name>mapreduce.jobhistory.webapp.address</name>
      <value>master:19888</value>
</property>

2.3.4. 配置yarn-site.xml文件
yarn-site.xml文件中主要配置yarn框架相关的配置。
进入hadoop-2.7.3目录下的etc/hadoop目录下,修改yarn-site.xml文件,增加如下配置项:
yarn.nodemanager.aux-services:NodeManager上运行的附属服务。需配置成mapreduce_shuffle,才可运行MapReduce程序
yarn.nodemanager.auxservices.mapreduce.shuffle.class:mapreduce执行shuffle使用的class
yarn.resourcemanager.address:resourceManager的地址,客户端通过该地址向RM提交应用程序,杀死应用程序等
yarn.resourcemanager.scheduler.address:ResourceManager 对ApplicationMaster暴露的访问地址。ApplicationMaster通过该地址向RM申请资源、释放资源
yarn.resourcemanager.resource-tracker.address: ResourceManager 对NodeManager暴露的地址.。NodeManager通过该地址向RM汇报心跳,领取任务等
yarn.resourcemanager.admin.address:ResourceManager 对管理员暴露的访问地址。管理员通过该地址向RM发送管理命令。
yarn.resourcemanager.webapp.address:ResourceManager对外web ui地址。用户可通过该地址在浏览器中查看yarn框架中运行的任务状态
yarn.nodemanager.resource.memory-mb:NodeManager运行所需要的内存,单位MB。
参考配置如下:master为NameNode服务器名称(请根据现场实际情况配置)。

<property>
   <name>yarn.nodemanager.aux-services</name>
   <value>mapreduce_shuffle</value>
</property>
<property>
   <name>yarn.nodemanager.auxservices.mapreduce.shuffle.class</name>
   <value>org.apache.hadoop.mapred.ShuffleHandler</value>
</property>
<property>
   <name>yarn.resourcemanager.address</name>
   <value>master:8032</value>
</property>
<property>
   <name>yarn.resourcemanager.scheduler.address</name>
   <value>master:8030</value>
</property>
<property>
   <name>yarn.resourcemanager.resource-tracker.address</name>
   <value>master:8031</value>
</property>
<property>
   <name>yarn.resourcemanager.admin.address</name>
   <value>master:8033</value>
</property>
<property>
   <name>yarn.resourcemanager.webapp.address</name>
   <value>master:8088</value>
</property>
<property>
     <name>yarn.nodemanager.resource.memory-mb</name>
     <value>1536</value>
</property>

2.3.5. 修改slaves,增加从服务器
进入hadoop-2.7.3目录下的etc/hadoop目录下,修改slaves文件,将其中的localhost去掉,并所有的从服务器名称添加进去,每台服务器名称占一行。
参考配置如下(服务器名称请根据自己实际情况配置):

slave1
slave2

2.3.6. 配置hadoop-env.sh、yarn-env.sh的JAVA_HOME
进入hadoop-2.7.3目录下的etc/hadoop目录下,修改hadoop-env.sh、yarn-env.sh 两个文件,增加JAVA_HOME的环境变量配置。查看jdk目录可使用命令echo $JAVA_HOME。
在hadoop-env.sh、yarn-env.sh两个文件中分别增加如下一段代码(jdk目录请根据自己实际情况设置):
export JAVA_HOME=/usr/hadoop/jdk1.8.0_77/

2.4. 配置主服务器免登录访问从服务器
为每个节点分别产生公、私密钥配置以下操作我们选取namenode服务器master作为示例,成功之后,也在另外两台服务器上执行相同的操作!!!
开始之前我们可以先测试下,用ssh命令登录本机或者其他服务器,无论是本机还是其他主机,都是需要输入密码的。
使用ssh-keygen命令可以为本服务器生成公钥(id_dsa.pub)和私钥(Id_dsa),要求输入passphrased的时候直接敲回车。
参考命令:
ssh-keygen -t dsa -f ~/.ssh/id_dsa
再使用cp命令,将公钥文件复制成authorized_keys文件。
参考命令:
cp ~/.ssh/id_dsa.pub ~/.ssh/authorized_keys
此时,在本服务器上生成公钥和私钥的步骤基本完成,使用 ssh 命令登录本机不会再要求输入密码(如果是第一次登录会要求确认是否继续连接)
成功之后,也在另外两台服务器上执行相同的操作!!!
2.5. 让主结点能通过SSH免密码登录两个子结点
将主服务器的公钥文件内容添加到从服务器的authorized_keys文件里,就可以实现主服务器免密码登录从服务器了。以下操作均在主服务器上执行。
使用scp命令,将主服务器的公钥文件id_dsa.pub复制到从服务器上,并命令为master.pub(此文件名可以自己随意起)。
参考命令如下(root为登录从服务器的用户名,slave1为从服务器的名称,请根据自己实际情况修改):

scp ~/.ssh/id_dsa.pub 10.67.15.121:~/.ssh/master.pub
scp ~/.ssh/id_dsa.pub 10.67.15.122:~/.ssh/master.pub

输入此命令后,会要求输入slave1服务器的root账号密码,输入密码即可。
再将上一步生成的master.pub文件内容追加到slave1的authorized_keys文件中,可以登录到slave1上去操作,也可以在master上使用ssh命令远程操作。这里我们使用ssh命令,在master上远程操作,参考命令如下(root为登录从服务器的用户名,slave1为从服务器的名称,master.pub为我们上一步生成的文件,请根据自己实际情况修改):

ssh root@slave1 "cat ~/.ssh/master.pub>>~/.ssh/authorized_keys"
ssh root@slave2 "cat ~/.ssh/master.pub>>~/.ssh/authorized_keys"

输入此命令后,会要求输入slave1服务器的root账号密码,输入密码即可。
如果不出问题,此时master服务就可以通过SSH免密码访问slave1了。
按照上面的方法,让master可以ssh免密码访问另外一台从服务器!!!!
2.6. 将Hadoop复制到各从服务器
通过以上步骤我们基本已经配置好了hadoop的集群配置,现在我们需要将我们配置好的文件复制到其他的服务器上。将文件copy到从服务器上的方法有很多,建议使用scp命令。
参考:在主服务器上执行以下两条命令(其中root为登录从服务器的用户名,slave1,slave2为从服务器的名称,请根据自己实际情况修改):

scp -r /usr/hadoop/hadoop-2.7.7/ 10.67.15.121:/usr/hadoop/
scp -r /usr/hadoop/hadoop-2.7.7/ 10.67.15.122:/usr/hadoop/

2.7. 格式化namenode
在主服务器上,进入hadoop-2.7.3目录下,格式化namenode。
参考命令:

cd /usr/local/zhitu/hadoop-2.7.3
bin/hdfs namenode -format

2.8. 启动Hadoop
在主服务器上,进入hadoop-2.7.3目录下,启动Hadoop集群。
参考命令:

sbin/./start-all.sh

2.9. 使用jps命令查看服务
jps命令为jdk提供的查看java进程的命令,hadoop服务如果启动成功了,可以在主服务器上查看到NameNode ,SecondaryNameNode,ResourceManager 三个服务,从服务器上看到到NodeManager, DataNode 两个服务。
参考命令:

jps

在这里插入图片描述
查看datanode的端口

 hdfs dfsadmin –report 

在这里插入图片描述

3. 安装spark

3.1. 下载spark的地址

http://spark.apache.org/downloads.html

3.2. 下载好的压缩包放到虚拟机opt目录下
在这里插入图片描述
3.3. 解压

tar -zxvf spark-2.3.1-bin-hadoop2.7.tgz -C /usr/hadoop

3.4. 更改名字

mv spark-2.3.1-bin-hadoop2.7.tgz  spark

在这里插入图片描述
3.5. 目录切换到 /opt/spark/conf 文件夹下边
Spark的配置文件都在这个文件夹里边 在修改配置文件之前,查看文件夹权限
在这里插入图片描述
3.6. 修改文件夹权限
chmod -R 7777 ./spark
之后开始修改配置文件 ,进入到conf 文件夹输入
cp spark-env.sh.template spark-env.sh
复制 spark-env.sh.template 文件夹并改名为 spark-env.sh
编辑spark-env.sh 文件 添加以下内容:
在这里插入图片描述
配置完成后 进入sbin目录并查看
在这里插入图片描述
3.7. 启动spark
输入./start-all.sh启动spark进程
之后输入jps 查看进程
Master和worker是spark的进程 剩下的几个是hadoop的进程
所有只要有Master和Worker这两个进程 就说明Spark已经被启动了
在这里插入图片描述
在这里插入图片描述
之后进入spark的bin目录查看
在这里插入图片描述
之后输入 ./pyspark 命令 出现此画面说明正常启动
在这里插入图片描述

5. 安装kafka

5.1. 下载kafka

https://kafka.apache.org/downloads

bash下载

wget https://downloads.apache.org/kafka/2.7.0/kafka_2.12-2.7.0.tgz

5.2. 解压
解压到指定文件夹

[root@master opt]# tar -zxvf  kafka_2.12-2.7.0.tgz -C /usr/hadoop/

5.3. 修改文件权限
首先修改文件名字

cd /usr/Hadoop/
mv kafka_2.12-2.7.0 kafka
chmod -R 777 ./kafka

5.4. 启动kafka
– 启动Zookeeper

cd /usr/Hadoop/kafka
./bin/zookeeper-server-start.sh -daemon ./config/zookeeper.properties

– 启动kafka

./bin/kafka-server-start.sh -daemon ./config/server.properties

– 停止kafka

./bin/kafka-server-stop.sh ./config/server.properties

5.5. 测试kafka
创建并查看topic

./bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
./bin/kafka-topics.sh --list --zookeeper localhost:2181 test

5.6. 发送消息

./bin/kafka-console-producer.sh --broker-list 10.67.15.126:9092 --topic test

在这里插入图片描述
5.7. 接收消息

./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning test

在这里插入图片描述

6. 安装python

6.1. 下载
去清华镜像站下载,上传到云端服务器上

清华镜像站:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/

下载的版本见下,下载64位的Linux版本
6.2. 通过wget下载

wget https://repo.anaconda.com/archive/Anaconda3-5.2.0-Linux-x86_64.sh

6.3. 安装
进入自己的安装目录(上面已经提及),开始安装

cd /usr/local/Anaconda
bash Anaconda3-5.2.0-Linux-x86_64.sh

在安装的时候会碰到一路让你Enter和输入yes的过程
一路Enter和yes就完事了,碰到问你装不装Microsoft的VS code时直接No,就结束了全部的安装
中间安装的时候要注意anaconda3的安装路径,我的默认安装到了/root/anaconda3里面去了,
6.4. 环境配置
这时候你进入到安装目录下,键入conda -V没有任何反应

cd /root/anaconda3
conda -V

在这里插入图片描述
6.5. 利用vim将其打开进行编辑~

vim /etc/profice

6.6. 在其最后一行加入 export PATH=$PATH:【你的安装目录】
注意:刚刚提及要注意安装目录(这里要用到),我的为/root/anaconda3/bin
在这里插入图片描述
然后source /etc/profile
ok~到这里全部结束
6.7. 检验安装结果
输入:

conda -V
pip list

在这里插入图片描述

7. pycharm环境配置

7.1. 下载pycharm
下载地址

https://www.jetbrains.com/pycharm/

在这里插入图片描述
7.2. 安装并配置服务器远程环境实现同步操作
① 安装以后进入pycharm点击添加环境
在这里插入图片描述
② 配置远程SSH环境
在这里插入图片描述
③ 配置服务器目录
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这样就就能配置成功了,我们可以实时同步代码了

8. Spark+kafka实时处理数据

详细分析下步骤:
1.	应用程序将购物日志发送给Kafka,topic为”sex”,因为这里只是统计购物男女生人数,所以只需要发送购物日志中性别属性即可。这里采用模拟的方式发送购物日志,即读取购物日志数据,每间隔相同的时间发送给Kafka。
2.	接着利用Spark Streaming从Kafka主题”sex”读取并处理消息。这里按滑动窗口的大小按顺序读取数据,例如可以按每5秒作为窗口大小读取一次数据,然后再处理数据。
3.	Spark将处理后的数据发送给Kafka,topic为”result”。
4.	然后利用Flask搭建一个web应用程序,接收Kafka主题为”result”的消息。
5.	利用Flask-SocketIO将数据实时推送给客户端。
6.	客户端浏览器利用js框架socketio实时接收数据,然后利用js可视化库hightlights.js库动态展示。

(1) 首先利用python读取user_log.csv文件的gender值并传送到kafka,topic为”sex”中,代码如下:

import csv
import time
from kafka import KafkaProducer

# 实例化一个KafkaProducer示例,用于向Kafka投递消息
producer = KafkaProducer(bootstrap_servers='10.67.15.168:9092')
# 打开数据文件
csvfile = open("/root/spark/hadoop/data/user_log.csv","r", encoding='UTF-8')
# 生成一个可用于读取csv文件的reader
reader = csv.reader(csvfile)

for line in reader:
    gender = line[9]  # 性别在每行日志代码的第9个元素
    if gender == 'gender':
        continue  # 去除第一行表头
    time.sleep(0.1)  # 每隔0.1秒发送一行数据
    # 发送数据,topic为'sex'
    # print(type(line))
    print(line[9].encode('utf8'))
    # 发送数据,topic为'sex'
    producer.send('sex', line[9].encode('utf8'))

(2) 创建一个消费者来消费生产者“Sex”生成的数据

from kafka import KafkaConsumer
consumer = KafkaConsumer('result')
for msg in consumer:
    # print(type(msg))
    print((msg.value).decode('utf8'))

(3) Spark Streaming实时处理数据
本项目在于实时统计每秒中男女生购物人数,而Spark Streaming接收的数据为1,1,0,2…,其中0代表女性,1代表男性,所以对于2或者null值,则不考虑。其实通过分析,可以发现这个就是典型的wordcount问题,而且是基于Spark流计算。女生的数量,即为0的个数,男生的数量,即为1的个数。
因此利用Spark Streaming接口reduceByKeyAndWindow,设置窗口大小为1,滑动步长为1,这样统计出的0和1的个数即为每秒男生女生的人数。

Spark准备工作
Kafka和Flume等高级输入源,需要依赖独立的库(jar文件)。按照我们前面安装好的Spark版本,这些jar包都不在里面,为了证明这一点,我们现在可以测试一下。请打开一个新的终端,然后启动spark-shell:

spark-shell
scala> import org.apache.spark.streaming.kafka010._
<console>:25: error: object kafka is not a member of package org.apache.spark.streaming import org.apache.spark.streaming.kafka010._ ^

你可以看到,马上会报错,因为找不到相关的jar包。然后我们退出spark-shell。
根据Spark官网的说明,对于Spark2.3.0版本,如果要使用Kafka,则需要下载spark-streaming-kafka-0-10_2.11相关jar包。现在请在Linux系统中,打开一个火狐浏览器,请点击这里访问Spark官网,里面有提供spark-streaming-kafka-0-10_2.11-2.3.0.jar文件的下载,其中,2.11表示scala的版本,2.3.0表示Spark版本号。下载后的文件会被默认保存在当前Linux登录用户的下载目录下,本教程统一使用hadoop用户名登录Linux系统,所以,我们就把这个文件复制到Spark目录的jars目录下。请新打开一个终端,输入下面命令:

mkdir /usr/hadoop/spark//jars/kafka
cp ./spark-streaming-kafka-0-10_2.11-2.3.0.jar /usr/hadoop/spark/jars/kafka

下面还要继续把Kafka安装目录的libs目录下的所有jar文件复制到“/usr/local/spark/jars/kafka”目录下,请在终端中执行下面命令:

cd /usr/hadoop/kafka/libs
ls
cp ./* /usr/hadoop/spark/jars/kafka

建立Spark项目

首先在/root/spark/hadoop/新建项目主目录kafka,然后在kafka目录下新建scala文件存放目录以及scala工程文件

mkdir -p /root/spark/hadoop/kafkatest/src/main/scala

接着在src/main/scala文件下创建两个文件,一个是用于设置日志,一个是项目工程主文件,设置日志文件为StreamingExamples.scala
因为我们配置了远程环境,所以我们直接在pycharm上面创建相应的文件直接上传服务器就可以了
在这里插入图片描述
日志处理

package org.apache.spark.examples.streaming
import org.apache.spark.internal.Logging
import org.apache.log4j.{Level, Logger}
/** Utility functions for Spark Streaming examples. */
object StreamingExamples extends Logging {
  /** Set reasonable logging levels for streaming if the user has not configured log4j. */
  def setStreamingLogLevels() {
    val log4jInitialized = Logger.getRootLogger.getAllAppenders.hasMoreElements
    if (!log4jInitialized) {
      // We first log something to initialize Spark's default logging, then we override the
      // logging level.
      logInfo("Setting log level to [WARN] for streaming example." +
        " To override add a custom log4j.properties to the classpath.")
      Logger.getRootLogger.setLevel(Level.WARN)
    }
  }
}

KafkaTest.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 < 3) {
      System.err.println("Usage: KafkaWordCount <brokers> <groupId> <topics>")
      System.exit(1)
    }
    StreamingExamples.setStreamingLogLevels()

    val Array(brokers, groupId, topics) = args
    val sparkConf = new SparkConf().setAppName("KafkaWordCount")
    val ssc = new StreamingContext(sparkConf, Seconds(1))
    //         设置chackpoint
    ssc.checkpoint("checkpoint")
//         创建消费者,从kafka获取数据供spark streaming做实时处理
//         新版本的kafka的参数设置
    val topicsSet = topics.split(",").toSet
    val kafkaParams = Map[String, Object](
      ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> brokers,
      ConsumerConfig.GROUP_ID_CONFIG -> groupId,
      ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
      ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer])
    //         创建数据源
    val messages = KafkaUtils.createDirectStream[String, String](
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String, String](topicsSet, kafkaParams))

    // Get the lines, split them into words, count the words and print
    val lines = messages.map(_.value)
    val words = lines.flatMap(_.split(" "))//将输入的每行用空格分割成一个个word


    // 对每一秒的输入数据进行reduce,然后将reduce后的数据发送给Kafka
    //         val wordCounts = words.map(x => (x, 1L)).reduceByKeyAndWindow(_+_,_-_, Seconds(1), Seconds(1), 1).print
    //         对数据进行窗口截取做实时流分析
    val wordCounts = words.map(x => (x, 1L))
        .reduceByKeyAndWindow(_+_,_-_, Seconds(1), Seconds(1), 1).foreachRDD(rdd => {
          if(rdd.count !=0 ){
               val props = new HashMap[String, Object]()
               //       提供brokers地址
               props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "10.67.15.168:9092")
               //       指定key value中的value可序列化方式,因为网络传输
               props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
               "org.apache.kafka.common.serialization.StringSerializer")
               //       指定key value中的key可序列化方式,因为网络传输
               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.collect)

//               另一种将rdd转化成json的方法
//                 val json=rdd.collect().toList.map{case (word, count) =>(word, count)}
//                 val str=compact(render(json))

//                 println(rdd.collect.getClass().getName()) //Lscala.Tuple2;
//                 println(str)
               // 封装成Kafka消息,topic为"result"
               val message = new ProducerRecord[String, String]("result", null, str)
               // 给Kafka发送消息
               producer.send(message)
          }
      })
    ssc.start()
    ssc.awaitTermination()
  }
}

上面代码注释已经也很清楚了,下面在简要说明下:

  1. 首先按每秒的频率读取Kafka消息;
  2. 然后对每秒的数据执行wordcount算法,统计出0的个数,1的个数,2的个数;
  3. 最后将上述结果封装成json发送给Kafka。

运行项目
编写好程序之后,下面介绍下如何打包运行程序。在/root/hadoop/kafkatest目录下新建文件simple.sbt,输入如下内容:

name := "Simple Project"
version := "1.0"
scalaVersion := "2.11.8"
libraryDependencies += "org.apache.spark" %% "spark-core" % "2.3.0"
libraryDependencies += "org.apache.spark" % "spark-streaming_2.11" % "2.3.0"
libraryDependencies += "org.apache.spark" % "spark-streaming-kafka-0-10_2.11" % "2.3.0"
libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.2.11"

然后,即可编译打包程序,输入如下命令

[root@master kafkatest]# /usr/hadoop/sbt/bin/sbt package

在这里插入图片描述
打包成功之后,接下来编写运行脚本,在/root/spark/hadoop/kafkatest目录下新建startup.sh文件,输入如下内容:

/usr/hadoop/spark/bin/spark-submit --driver-class-path /usr/hadoop/spark/jars/*:/usr/hadoop/spark/jars/kafka/* --class "org.apache.spark.examples.streaming.KafkaWordCount" /root/spark/hadoop/kafkatest/target/scala-2.11/simple-project_2.11-1.0.jar 10.67.15.168:9092 1 sex

其中最后四个为输入参数,含义如下

  1. 10.67.15.168:9092为brokerer地址

  2. 1 为consumer group标签

  3. sex为消费者接收的topic
    最后在/root/spark/hadoop/kafkatest目录下,运行如下命令即可执行刚编写好的Spark Streaming程序
    在这里插入图片描述

    [root@master kafkatest]# sh startup.sh

程序运行成功之后,下面通过之前的KafkaProducer和KafkaConsumer来检测程序。

测试程序
下面开启之前编写的KafkaProducer投递消息,然后将KafkaConsumer中接收的topic改为result,验证是否能接收topic为result的消息,更改之后的KafkaConsumer为

from kafka import KafkaConsumer
 
consumer = KafkaConsumer('result')
for msg in consumer:
    print((msg.value).decode('utf8'))

在同时开启Spark Streaming项目,KafkaProducer以及KafkaConsumer之后,可以在KafkaConsumer运行窗口,出现以下类似数据:

[{"0":4},{"2":4},{"1":6}]
[{"0":9},{"2":6},{"1":5}]
[{"0":10},{"2":5},{"1":3}]
[{"0":5},{"2":6},{"1":9}]

在这里插入图片描述
数据可视化
接下来做的事是,利用Flask-SocketIO实时推送数据,socket.io.js实时获取数据,highlights.js展示数据。
Flask-SocketIO实时推送数据
代码如下:index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DashBoard</title>
    <script src="static/js/socket.io.js"></script>
    <script src="static/js/jquery-3.1.1.min.js"></script>
    <script src="static/js/highcharts.js"></script>
    <script src="static/js/exporting.js"></script>
    <script type="text/javascript" charset="utf-8">
    var socket = io.connect('http://' + document.domain + ':' + location.port);
    socket.on('connect', function() {
        socket.emit('test_connect', {data: 'I\'m connected!'});
    });

    socket.on('test_message',function(message){
        console.log(message);
        var obj = eval(message);
        var result = obj["data"].split(",");
        $('#girl').html(result[0]);
        $('#boy').html(result[1]);
    });

    socket.on('connected',function(){
        console.log('connected');
    });

    socket.on('disconnect', function () {
        console.log('disconnect');
    });
    </script>
</head>
<body>
<div>
    <b>Girl: </b><b id="girl"></b>
    <b>Boy: </b><b id="boy"></b>
</div>
<div id="container" style="width: 600px;height:400px;"></div>

<script type="text/javascript">
    $(document).ready(function () {
    Highcharts.setOptions({
        global: {
            useUTC: false
        }
    });

    Highcharts.chart('container', {
        chart: {
            type: 'spline',
            animation: Highcharts.svg, // don't animate in old IE
            marginRight: 10,
            events: {
                load: function () {

                    // set up the updating of the chart each second
                    var series1 = this.series[0];
                    var series2 = this.series[1];
                    setInterval(function () {
                        var x = (new Date()).getTime(), // current time
                        count1 = $('#girl').text();
                        y = parseInt(count1);
                        series1.addPoint([x, y], true, true);

                        count2 = $('#boy').text();
                        z = parseInt(count2);
                        series2.addPoint([x, z], true, true);
                    }, 1000);
                }
            }
        },
        title: {
            text: '男女生购物人数实时分析'
        },
        xAxis: {
            type: 'datetime',
            tickPixelInterval: 50
        },
        yAxis: {
            title: {
                text: '数量'
            },
            plotLines: [{
                value: 0,
                width: 1,
                color: '#808080'
            }]
        },
        tooltip: {
            formatter: function () {
                return '<b>' + this.series.name + '</b><br/>' +
                    Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +
                    Highcharts.numberFormat(this.y, 2);
            }
        },
        legend: {
            enabled: true
        },
        exporting: {
            enabled: true
        },
        series: [{
            name: '女生购物人数',
            data: (function () {
                // generate an array of random data
                var data = [],
                    time = (new Date()).getTime(),
                    i;

                for (i = -19; i <= 0; i += 1) {
                    data.push({
                        x: time + i * 1000,
                        y: Math.random()
                    });
                }
                return data;
            }())
        },
        {
            name: '男生购物人数',
            data: (function () {
                // generate an array of random data
                var data = [],
                    time = (new Date()).getTime(),
                    i;

                for (i = -19; i <= 0; i += 1) {
                    data.push({
                        x: time + i * 1000,
                        y: Math.random()
                    });
                }
                return data;
            }())
        }]
    });
});
</script>
</body>
</html>
App.py
import json
from flask import Flask, render_template
from flask_socketio import SocketIO
from kafka import KafkaConsumer

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
thread = None
# 实例化一个consumer,接收topic为result的消息
consumer = KafkaConsumer('result', bootstrap_servers='10.67.15.168:9092')

# 一个后台线程,持续接收Kafka消息,并发送给客户端浏览器
def background_thread():
    girl = 0
    boy = 0
    for msg in consumer:
        data_json = msg.value.decode('utf8')
        data_list = json.loads(data_json)
        for data in data_list:
            print(data)
            if '0' in data.keys():
                girl = data['0']
            elif '1' in data.keys():
                boy = data['1']
            else:
                continue
        result = str(girl) + ',' + str(boy)
        print(result)
        socketio.emit('test_message', {'data': result})


# 客户端发送connect事件时的处理函数
@socketio.on('test_connect')
def connect(message):
    print(message)
    global thread
    if thread is None:
        # 单独开启一个线程给客户端发送数据
        thread = socketio.start_background_task(target=background_thread)
    socketio.emit('connected', {'data': 'Connected'})

# 通过访问http://127.0.0.1:5000/访问index.html
@app.route("/")
def handle_mes():
    return render_template("index.html")

# main函数
if __name__ == '__main__':
    # background_thread()
    socketio.run(app, debug=True,host='0.0.0.0',port=5000)

实验结果:
在这里插入图片描述
代码顺序:
① Pycharm执行producer.py
② 服务器端执行sh startup.sh
③ pycharm执行consumer.py
④ Pycharm执行app.py

项目总结:

  1. Hadoop安装一定不要忘记要设置/etc/hosts文件,不然从节点起不来,设置hosts文件以后用hostnamectl set-hostname 设置主机名字,这样免密操作才能做成功!!!
  2. 安装spark的时候一定要注意与hadoop版本的兼容,我的spark-shell用自带的scala,pyspark配置了python环境,按照自己的需要搭建环境。python的版本和spark版本也要对应,spark2.1不支持python3.6。安装对应版本即可
  3. kafka构建集群模式的时候broker.id一定不要重复,重复kafka启动不了,安装kafka需要先启动zookeeper,如果以前安装过zookeeper,需要改zookeeper.properties文件,添加如下配置,当然如果觉得集群麻烦那就建立一个单机的也是可以用kafka的
  4. 安装conda环境其实是为了使用pycharm连接到远程的环境这样可以让我们更好的书写代码,就可以不用将代码或者数据上传到在服务器,在本地和服务器实现实时同步,或者安装jupyter通过web形势写python代码,也做上传文件代码,但需要配置文件路径。两个的原理都是一样的,都是使用服务器的python环境
  5. sbt是一款Spark用来对scala编写程序进行打包的工具,Spark 中没有自带 sbt,需要下载安装,安装完成后需要赋予sbt文件夹权限,最后在程序根目录下执行sbt package,第一次打包需要一些时间,多等等
  6. maven为了和sbt打包编译的内容进行区分,这里再为Maven创建一个代码目录“/sparkapp2”,并在“/kafkatest2/src/main/scala”下建立一个名为 SimpleApp.scala的Scala代码文件,放入和前面一样的代码在“~/kafkatest2”目录中新建文件pom.xml,这样添加相关依赖就可将程序打包成jar包
  • 12
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云计算大数据小朋友

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值