大數據開發文檔
文章目錄
本文檔主要講述了flume+kafka+spark的單機分佈式搭建,由淺入深,介紹了常見大數據流處理流程
單機版環境搭建及相關DEMO
Flume
Flume基本介紹與架構
Flume是Cloudera提供的一個高可用的,高可靠的,分佈式的海量日誌採集、聚合和傳輸的系統。Flume基於流式架構,靈活簡單。
Flume出生日記
有很多的服務和系統
- network devices
- operating system
- web servers
- Applications
這些系統都會產生很多的日誌,那麼把這些日誌拿出來,用來分析時非常有用的。
如何解決數據從其他的server上移動到Hadoop上?
shell cp hadoop集羣上的機器上, hadoop fs -put …/ 直接拷貝日誌,但是沒辦法監控,而cp的時效性也不好,容錯負載均衡也沒辦法做
======>
Flume誕生了
Flume架構
Flume組成架構如圖1-1,所示:
圖1-1 Flume組成架構
Agent
Agent是一個JVM進程,它以事件的形式將數據從源頭送至目的,是Flume數據傳輸的基本單元。
Agent主要有3個部分組成,Source、Channel、Sink。
Source
Source是負責接收數據到Flume Agent的組件。Source組件可以處理各種類型、各種格式的日誌數據,包括avro、thrift、exec、jms、spooling directory、netcat、sequence generator、syslog、http、legacy。
Channel
Channel是位於Source和Sink之間的緩衝區。因此,Channel允許Source和Sink運作在不同的速率上。Channel是線程安全的,可以同時處理幾個Source的寫入操作和幾個Sink的讀取操作。
Flume自帶兩種Channel:Memory Channel
和File Channel
Memory Channel
是內存中的隊列。Memory Channel
在不需要關心數據丟失的情景下適用。如果需要關心數據丟失,那麼Memory Channel
就不應該使用,因爲程序死亡、機器宕機或者重啓都會導致數據丟失。
File Channel
將所有事件寫到磁盤。因此在程序關閉或機器宕機的情況下不會丟失數據。
Sink
Sink不斷地輪詢Channel中的事件且批量地移除它們,並將這些事件批量寫入到存儲或索引系統、或者被髮送到另一個Flume Agent。
Sink是完全事務性的。在從Channel批量刪除數據之前,每個Sink用Channel啓動一個事務。批量事件一旦成功寫出到存儲系統或下一個Flume Agent,Sink就利用Channel提交事務。事務一旦被提交,該Channel從自己的內部緩衝區刪除事件。
Sink組件目的地包括hdfs、logger、avro、thrift、ipc、file、null、HBase、solr、自定義。
Event
傳輸單元,Flume數據傳輸的基本單元,以事件的形式將數據從源頭送至目的地。
Flume拓撲結構
Flume的拓撲結構如圖1-3、1-4、1-5和1-6所示:
圖1-3 Flume Agent連接
圖1-4 單source,多channel、sink
圖1-5 Flume負載均衡
圖1-6 Flume Agent聚合
Flume安裝部署
Flume的安裝相對簡單,但是前提是要先下好Java環境JDK,1.8以上即可,JDK安裝可以查看Kafka安裝流程,這裏以Linux下的安裝爲例
Flume安裝地址
安裝部署
- 解壓apache-flume-1.7.0-bin.tar.gz到/usr/local/目錄下(安裝包詳見安裝包文件夾flume文件夾下的tar.gz壓縮包)
#把下載的包移動到目錄
$ sudo mv apache-flume-1.7.0-bin.tar.gz /usr/local
#解壓
$ sudo tar -zxvf apache-flume-1.7.0-bin.tar.gz /usr/local/
- 修改apache-flume-1.7.0-bin的名稱爲flume
$ sudo mv apache-flume-1.7.0-bin flume
- 將flume/conf下的flume-env.sh.template文件修改爲flume-env.sh,並配置flume-env.sh文件
$ mv flume-env.sh.template flume-env.sh
$ vi flume-env.sh
export JAVA_HOME=/opt/module/jdk1.8.0_144(這裏路徑替換爲本機JDK安裝目錄)
案例實操
-
監控端口數據
-
案例需求
:首先,Flume監控本機44444端口,然後通過telnet工具向本機44444端口發送消息,最後Flume將監聽的數據實時顯示在控制檯。 -
需求分析
:
-
-
實現步驟:
-
安裝telnet工具
在/usr/local目錄下創建flume-telnet文件夾。
$ mkdir flume-telnet
再將rpm軟件包(xinetd-2.3.14-40.el6.x86_64.rpm、telnet-0.17-48.el6.x86_64.rpm和telnet-server-0.17-48.el6.x86_64.rpm)拷入/usr/local/flume-telnet文件夾下面。執行RPM軟件包安裝命令:
$ sudo rpm -ivh xinetd-2.3.14-40.el6.x86_64.rpm $ sudo rpm -ivh telnet-0.17-48.el6.x86_64.rpm $ sudo rpm -ivh telnet-server-0.17-48.el6.x86_64.rpm
-
-
判斷44444端口是否被佔用
判斷44444端口是否佔用,如果被佔用則kill掉或者更換端口
$ sudo netstat -tunlp | grep 44444 功能描述:netstat命令是一個監控TCP/IP網絡的非常有用的工具,它可以顯示路由表、實際的網絡連接以及每一個網絡接口設備的狀態信息。 基本語法:netstat [選項] 選項參數: -t或--tcp:顯示TCP傳輸協議的連線狀況; -u或--udp:顯示UDP傳輸協議的連線狀況; -n或--numeric:直接使用ip地址,而不通過域名服務器; -l或--listening:顯示監控中的服務器的Socket; -p或--programs:顯示正在使用Socket的程序識別碼和程序名稱;
-
創建Flume Agent配置文件
flume-telnet-logger.conf
在flume目錄下創建job文件夾並進入job文件夾
$ mkdir job $ cd job/
-
在job文件夾下創建Flume Agent配置文件
flume-telnet-logger.conf
$ touch flume-telnet-logger.conf # 如果覺得vim上手難度太大,可以使用gedit來進行編輯 $ vim flume-telnet-logger.conf # 在conf文件中加入以下內容 # Name the components on this agent a1.sources = r1 a1.sinks = k1 a1.channels = c1 # Describe/configure the source a1.sources.r1.type = netcat a1.sources.r1.bind = localhost a1.sources.r1.port = 44444 # Describe the sink a1.sinks.k1.type = logger # Use a channel which buffers events in memory a1.channels.c1.type = memory a1.channels.c1.capacity = 1000 a1.channels.c1.transactionCapacity = 100 # Bind the source and sink to the channel a1.sources.r1.channels = c1 a1.sinks.k1.channel = c1
注:配置文件來源於官方手冊
-
先開啓flume監聽端口
$ bin/flume-ng agent --conf conf/ --name a1 --conf-file job/flume-telnet-logger.conf -Dflume.root.logger=INFO,console 參數說明: --conf conf/ :表示配置文件存儲在conf/目錄 --name a1 :表示給agent起名爲a1 --conf-file job/flume-telnet.conf :flume本次啓動讀取的配置文件是在job文件夾下的flume-telnet.conf文件。 -Dflume.root.logger==INFO,console :-D表示flume運行時動態修改flume.root.logger參數屬性值,並將控制檯日誌打印級別設置爲INFO級別。日誌級別包括:log、info、warn、error。
-
使用telnet工具向本機的44444端口發送內容
$ telnet localhost 44444
-
將A服務器上的日誌實時採集到B服務器
一般跨節點都是使用
avro sink
技術選型有兩種方案:
-
exec source + memory channel + avro sink
// Flume的關鍵就是寫配置文件,仍然是在conf文件夾下創建配置文件 // avro-memory-sink.conf # Name the components on this agent exec-memory-avro.sources = exec-source exec-memory-avro.sinks = arvo-sink exec-memory-avro.channels = memory-channel # Describe/configure the source exec-memory-avro.sources.exec-source.type = exec exec-memory-avro.sources.exec-source.command = tail -F $FLUME_HOME/logs/flume.log exec-memory-avro.sources.exec-source.shell = /bin/sh -c # Describe the sink exec-memory-avro.sinks.arvo-sink.type = avro exec-memory-avro.sinks.arvo-sink.hostname = localhost exec-memory-avro.sinks.arvo-sink.port = 44444 # Use a channel which buffers events in memory exec-memory-avro.channels.memory-channel.type = memory exec-memory-avro.channels.memory-channel.capacity = 1000 exec-memory-avro.channels.memory-channel.transactionCapacity = 100 # Bind the source and sink to the channel exec-memory-avro.sources.exec-source.channels = memory-channel exec-memory-avro.sinks.arvo-sink.channel = memory-channel
-
avro source + memory channel + logger sink
// avro-logger-sink.conf # Name the components on this agent avro-memory-logger.sources = avro-source avro-memory-logger.sinks = logger-sink avro-memory-logger.channels = memory-channel # Describe/configure the source avro-memory-logger.sources.avro-source.type = avro avro-memory-logger.sources.avro-source.bind = localhost avro-memory-logger.sources.avro-source.port = 44444 # Describe the sink avro-memory-logger.sinks.logger-sink.type = logger # Use a channel which buffers events in memory avro-memory-logger.channels.memory-channel.type = memory avro-memory-logger.channels.memory-channel.capacity = 1000 avro-memory-logger.channels.memory-channel.transactionCapacity = 100 # Bind the source and sink to the channel avro-memory-logger.sources.avro-source.channels = memory-channel avro-memory-logger.sinks.logger-sink.channel = memory-channel
接下來啓動兩個配置
先啓動avro-memory-logger flume-ng agent \ --name avro-memory-logger \ --conf $FLUME_HOME/conf \ --conf-file $FLUME_HOME/conf/avro-memory-logger.conf \ -Dflume.root.logger=INFO,console 再啓動另外一個 flume-ng agent --name exec-memory-avro --conf $FLUME_HOME/conf \ --conf-file $FLUME_HOME/conf/exec-memory-avro.conf \ -Dflume.root.logger=INFO,console
-
一個可能因爲手誤出現的bug
log4j:WARN No appenders could be found for logger (org.apache.flume.lifecycle.LifecycleSupervisor).
log4j:WARN Please initialize the log4j system properly.log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
出現這個錯誤是因爲路徑沒有寫對
往監聽的日誌中輸入一段字符串,可以看到我們的logger sink 已經成功接收到信息
上面Flume的基本流程圖如下
Kafka
Kafka是由Apache軟件基金會開發的一個開源流處理平臺,由Scala和Java編寫。該項目的目標是爲處理實時數據提供一個統一、高吞吐、低延遲的平臺。其持久化層本質上是一個“按照分佈式事務日誌架構的大規模發佈/訂閱消息隊列”,[3]這使它作爲企業級基礎設施來處理流式數據非常有價值。此外,Kafka可以通過Kafka Connect連接到外部系統(用於數據輸入/輸出),並提供了Kafka Streams——一個Java流式處理庫。
具體的架構可以查看官網的intro部分。
因爲在實際編程中使用kafka_2.11-0.11.00以上版本和使用以下版本的Java API 不一致,所以推薦直接參照官網的文檔進行編程。
環境搭建
單機單節點
搭建說明
需要有一定的Linux操作經驗,對於沒有權限之類的問題要懂得通過命令解決
Kafka的安裝相比Flume來說更加複雜,因爲Kafka依賴於Zookeeper
環境說明:
- os:Ubuntu 18.04
- zookeeper:zookeeper 3.4.9
- kafka:kafka_2.11-0.11.0.0
- jdk:jdk 8(kafka啓動需要使用到jdk)
詳細說明:
一、jdk安裝
jdk分爲以下幾種:jre、openjdk、 oracle jdk,這裏我們要安裝的是oracle jdk(推薦安裝)
add-apt-repository ppa:webupd8team/java
apt-get update
apt-get install oracle-java8-installer
apt-get install oracle-java8-set-default
測試安裝版本:
二、安裝配置zookeeper單機模式
下載zookeeper 3.4.5,開始安裝(軟件包詳見軟件包下的kafka中的壓縮包):
cd /usr/local
wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.5/zookeeper-3.4.5.tar.gz
等待安裝成功:
解壓:
tar -zxvf zookeeper-3.4.5.tar.gz
解壓後同目錄下便存在相同文件夾:
切換到conf目錄下:
cd zookeeper-3.4.5/conf/
複製zoo_sample.cfg到zoo.cfg:
cp zoo_sample.cfg zoo.cfg
然後編輯zoo.cfg如下(其它不用管,默認即可):
initLimit=10
syncLimit=5
dataDir=/home/young/zookeeper/data
clientPort=2181
別忘了新建dataDir目錄:
mkdir /home/young/zookeeper/data
爲zookeeper創建環境變量,打開/etc/profile文件,並在最末尾添加如下內容:
vi /etc/profile
添加內容如下:
export ZOOKEEPER_HOME=/home/young/zookeeper
export PATH=.:$ZOOKEEPER_HOME/bin:$JAVA_HOME/bin:$PATH
配置完成之後,切換到zookeeper/bin目錄下,啓動服務:
關閉服務:
這裏暫時先關閉zookeeper服務,防止下面使用kafka啓動時報端口占用錯誤。
三、安裝配置kafka單機模式
下載kafka(安裝包詳見軟件包kafka下的壓縮包):
cd /usr/local
wget https://www.apache.org/dyn/closer.cgi?path=/kafka/0.11.0.0/kafka_2.11-0.11.0.0.tgz
解壓:
tar -zxvf kafka_2.11-0.11.0.0.tgz
進入kafka/config目錄下:
以上文件是需要修改的文件,下面一個個修改配置:
配置server.properties:
以下爲修改的,其他爲默認即可:
#broker.id需改成正整數,單機爲1就好
broker.id=1
#指定端口號
port=9092
#localhost這一項還有其他要修改,詳細見下面說明
host.name=localhost
#指定kafka的日誌目錄
log.dirs=/usr/local/kafka_2.11-0.11.0.0/kafka-logs
#連接zookeeper配置項,這裏指定的是單機,所以只需要配置localhost,若是實際生產環境,需要在這裏添加其他ip地址和端口號
zookeeper.connect=localhost:2181
配置zookeeper.properties:
#數據目錄
dataDir=/usr/local/kafka_2.11-0.11.0.0/zookeeper/data
#客戶端端口
clientPort=2181
host.name=localhost
配置producer.properties:
zookeeper.connect=localhost:2181
配置consumer.properties:
zookeeper.connect=localhost:2181
最後還需要拷貝幾個jar文件到kafka的libs目錄,分別是zookeeper-xxxx.jar、log4j-xxxx.jar、slf4j-simple-xxxx.jar,最後如下:
四、kafka的使用
啓動zookeeper服務:
bin/zookeeper-server-start.sh config/zookeeper.properties
新開一個窗口啓動kafka服務:
bin/kafka-server-start.sh config/server.properties
至此單機服務搭建已經全部完成
單機多節點
對於單機單節點只需要使用一個配置文件來啓動即可,那麼對於單機多節點,只需要建立多個配置文件,並且啓動即可。比如我們需要有三個節點。
然後我們的每個server properies
裏面的端口以及ID要不一致
server-1.properties
server-2.properties
server-3.properties
當然其對應的log對應目錄也要修改,這個就不多說了
然後在控制檯啓動
> bin/kafka-server-start.sh config/server-1.properties &
> bin/kafka-server-start.sh config/server-2.properties &
> bin/kafka-server-start.sh config/server-3.properties &
通過jps -m 能看到三個kafka即可(可能以普通用戶看不到相應的進程,只是因爲沒給到權限,可以給權限或者直接sudo su切換到超級用戶)
Kafka控制檯的一些命令操作
控制檯中我們可以通過命令建立topic,並且開啓一個消費者一個生產者來模擬通信,這些在官網的quickstart中都有詳盡的描述
通過我們的一個叫topic的標籤,我們建立了一個生產者和一個消費者,可以明顯看到消費者接收到了生產者的消息。其他比較常用的命令,比如describe
等可以自行探索。
Java API控制Kafka
接下來會說一個簡單的在Java中使用Kafka小例子
這裏都是基於2.11_0.11.0.0.0
版本以及之後的編程來說明,更低版本相應的API有些許變化,低版本中很多函數已經被替代和廢除。
基本配置
- 首先在Idea中建立一個新的Maven項目,這裏我們選擇一個achetype:scala-archetype-simple
-
接下來我們把Maven文件配置好,並且auto import dependencies,這裏如果沒有選擇auto import,我們可以在Pom.xml右鍵找到maven選項裏面有一個reload
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.test.spark</groupId> <artifactId>spark streaming</artifactId> <version>1.0</version> <inceptionYear>2008</inceptionYear> <properties> <scala.version>2.7.0</scala.version> <kafka.version>0.11.0.0</kafka.version> </properties> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> <version>${kafka.version}</version> </dependency> </dependencies> <build> <sourceDirectory>src/main/scala</sourceDirectory> <testSourceDirectory>src/test/scala</testSourceDirectory> <plugins> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <scalaVersion>${scala.version}</scalaVersion> <args> <arg>-target:jvm-1.5</arg> </args> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <configuration> <downloadSources>true</downloadSources> <buildcommands> <buildcommand>ch.epfl.lamp.sdt.core.scalabuilder</buildcommand> </buildcommands> <additionalProjectnatures> <projectnature>ch.epfl.lamp.sdt.core.scalanature</projectnature> </additionalProjectnatures> <classpathContainers> <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer> <classpathContainer>ch.epfl.lamp.sdt.launching.SCALA_CONTAINER</classpathContainer> </classpathContainers> </configuration> </plugin> </plugins> </build> <reporting> <plugins> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <configuration> <scalaVersion>${scala.version}</scalaVersion> </configuration> </plugin> </plugins> </reporting> </project>
-
因爲我們使用Java編程,所以我們在main下面建立一個java文件夾,並且把整個文件夾設爲source,如下圖
-
-
然後我們在這個例子會涉及到幾個Class,包括啓動的Class,消費者,生產者,配置
代碼分析
//KafkaProperties.java
package com.test.spark.kafka;
/**
* Kafka常用配置文件
*/
public class KafkaProperties {
public static final String ZK= "211.83.96.204:2181";
public static final String TOPIC= "test";
public static final String BROKER_LIST = "211.83.96.204:9092";
public static final String GROUP_ID = "test_group1";
}
首先看一下配置文件,爲了配置能更加全局化好修改,我們直接建立一個配置文件,把可能需要的一些全局參數放進來,方便後續開發。其中有zookeeper的IP
,Topic名稱
,服務器列表
以及group_id
。
// KafkaProducerClient.java
package com.test.spark.kafka;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
/**
* Kafka 生產者
*/
public class KafkaProducerClient extends Thread{
private String topic;
private Producer<String, String> producer;
public KafkaProducerClient(String topic) {
this.topic = topic;
Properties properties = new Properties();
properties.put("bootstrap.servers","localhost:9092");
// properties.put("serializer.class","kafka.serializer.StringEncoder");
properties.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
properties.put("request.required.acks","1");
producer = new KafkaProducer<String, String>(properties);
}
@Override
public void run() {
int messageNo = 1;
while(true) {
String message = "message_" + messageNo;
producer.send(new ProducerRecord<String, String>(topic, message));
System.out.println("Sent: " + message);
messageNo ++;
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
消費者中我們使用多線程的方式,循環發送消息
// KafkaConsumerClient.java
package com.test.spark.kafka;
import kafka.consumer.ConsumerConnector$class;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
/**
* Kafka消費者
*/
public class KafkaConsumerClient {
private String topic;
public KafkaConsumerClient(String topic) {
this.topic = topic;
}
public void start() {
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", KafkaProperties.GROUP_ID);//不同ID 可以同時訂閱消息
props.put("enable.auto.commit", "false");//自動commit
props.put("auto.commit.interval.ms", "1000");//定時commit的週期
props.put("session.timeout.ms", "30000");//consumer活性超時時間
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
consumer.subscribe(Arrays.asList(this.topic));//訂閱TOPIC
try {
while(true) {//輪詢
ConsumerRecords<String, String> records =consumer.poll(Long.MAX_VALUE);//超時等待時間
for (TopicPartition partition : records.partitions()) {
List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
for (ConsumerRecord<String, String> record : partitionRecords) {
System.out.println("receive" + ": " + record.value());
}
consumer.commitSync();//同步
}
}
} finally
{
consumer.close();
}
}
}
在消費中我們會輪詢消息
Flume+Kafka配合
把logger sink ===> kafka sink
sink kafka: producer
所以啓動一個kafka的consumer,直接對接到kafka sink消費掉即可
//avro-memory-kafka.conf
# Name the components on this agent
avro-memory-kafka.sources = avro-source
avro-memory-kafka.sinks = kafka-sink
avro-memory-kafka.channels = memory-channel
# Describe/configure the source
avro-memory-kafka.sources.avro-source.type = avro
avro-memory-kafka.sources.avro-source.bind = localhost
avro-memory-kafka.sources.avro-source.port = 44444
# Describe the sink
avro-memory-kafka.sinks.kafka-sink.type = org.apache.flume.sink.kafka.KafkaSink
avro-memory-kafka.sinks.kafka-sink.brokerList = localhost:9092
avro-memory-kafka.sinks.kafka-sink.topic = test
avro-memory-kafka.sinks.kafka-sink.batchSize = 5
avro-memory-kafka.sinks.kafka-sink.requiredAcks = 1
# Use a channel which buffers events in memory
avro-memory-kafka.channels.memory-channel.type = memory
avro-memory-kafka.channels.memory-channel.capacity = 1000
avro-memory-kafka.channels.memory-channel.transactionCapacity = 100
# Bind the source and sink to the channel
avro-memory-kafka.sources.avro-source.channels = memory-channel
avro-memory-kafka.sinks.kafka-sink.channel = memory-channel
[外鏈圖片轉存失敗(img-o7uizDQ2-1569486879031)(…/%E5%A4%A7%E6%95%B0%E6%8D%AE%E6%9C%80%E7%BB%88%E7%89%88%E6%96%87%E6%A1%A3/kafka%E5%AD%A6%E4%B9%A0/flume/connect_flume_kafka.png)]
注意這個batchSize,在數據量沒有到達設定的閾值時,他會有一個timeout,這之後纔會有數據發過來
Spark
Spark 簡介
- 什麼是Spark?Spark作爲Apache頂級的開源項目,是一個快速、通用的大規模數據處理引擎,和Hadoop的MapReduce計算框架類似,但是相對於MapReduce,Spark憑藉其可伸縮、基於內存計算等特點,以及可以直接讀寫Hadoop上任何格式數據的優勢,進行批處理時更加高效,並有更低的延遲。相對於“one stack to rule them all”的目標,實際上,Spark已經成爲輕量級大數據快速處理的統一平臺,各種不同的應用,如實時流處理、機器學習、交互式查詢等,都可以通過Spark建立在不同的存儲和運行系統上。
- Spark是基於內存計算的大數據並行計算框架。Spark基於內存計算,提高了在大數據環境下數據處理的實時性,同時保證了高容錯性和高可伸縮性,允許用戶將Spark部署在大量廉價硬件之上,形成集羣。
- Spark於2009年誕生於加州大學伯克利分校AMPLab。目前,已經成爲Apache軟件基金會旗下的頂級開源項目。相對於MapReduce上的批量計算、迭代型計算以及基於Hive的SQL查詢,Spark可以帶來上百倍的性能提升。目前Spark的生態系統日趨完善,Spark SQL的發佈、Hive on Spark項目的啓動以及大量大數據公司對Spark全棧的支持,讓Spark的數據分析範式更加豐富。
Spark環境搭建
Hadoop安裝(Spark依賴於Hadoop安裝)
Hadoop可以通過HadoopDownloadOne 或者HadoopDownloadTwo 下載,一般選擇下載最新的穩定版本,即下載 “stable” 下的hadoop-2.x.y.tar.gz
這個格式的文件(詳見安裝文件夾中的hadoop-2.7.7)
$ sudo tar -zxf hadoop-2.7.7.tar.gz -C /usr/local # 解壓到/usr/local中
$ cd /usr/local/
$ sudo mv ./hadoop-2.6.0/ ./hadoop # 將文件夾名改爲hadoop
$ sudo chown -R hadoop ./hadoop # 修改文件權限
Hadoop 解壓後即可使用。輸入如下命令來檢查 Hadoop 是否可用,成功則會顯示 Hadoop 版本信息:
$ cd /usr/local/hadoop
$ ./bin/hadoop version
Hadoop單機配置及運行測試
Hadoop 默認模式爲非分佈式模式(本地模式),無需進行其他配置即可運行。非分佈式即單 Java 進程,方便進行調試。
現在我們可以執行例子來感受下 Hadoop 的運行。Hadoop 附帶了豐富的例子(運行 ./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.7.jar 可以看到所有例子
),包括 wordcount、terasort、join、grep 等。
$ cd /usr/local/hadoop
$ mkdir ./input
$ cp ./etc/hadoop/*.xml ./input # 將配置文件作爲輸入文件
$ ./bin/Hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar grep ./input ./output 'dfs[a-z.]+'
$ cat ./output/* # 查看運行結果
注意,Hadoop 默認不會覆蓋結果文件,因此再次運行上面實例會提示出錯,需要先將 ./output 刪除。
如果中間提示 Error: JAVA_HOME is not set and could not be found. 的錯誤,則說明之前設置 JAVA_HOME 環境變量那邊就沒設置好,請按教程先設置好 JAVA_HOME 變量,否則後面的過程都是進行不下去的。如果已經按照前面教程在.bashrc文件中設置了JAVA_HOME,還是出現 Error: JAVA_HOME is not set and could not be found. 的錯誤,那麼,請到hadoop的安裝目錄修改配置文件“/usr/local/hadoop/etc/hadoop/hadoop-env.sh”,在裏面找到“export JAVA_HOME=${JAVA_HOME}”這行,然後,把它修改成JAVA安裝路徑的具體地址,比如,“export JAVA_HOME=/usr/lib/jvm/default-java”,然後,再次啓動Hadoop。
Spark安裝
此處採用Spark和Hadoop一起安裝使用,這樣,就可以讓Spark使用HDFS存取數據。需要說明的是,當安裝好Spark以後,裏面就自帶了scala環境,不需要額外安裝scala。在安裝spark之前,需要先安裝Java和Hadoop。
需要的具體運行環境如下:
Ø Ubuntu16.04以上
Ø Hadoop 2.7.1以上
Ø Java JDK 1.8以上
Ø Spark 2.1.0 以上
Ø Python 3.4以上
(此次系統環境使用的Ubuntu16.04,自帶Python,不需額外安裝)
由於已經安裝了Hadoop,所以在Choose a package type
後面需要選擇Pre-build with user-provided Hadoop
,然後點擊Download Spark
後面的spark-2.1.0-bin-without-hadoop.tgz
下載即可。需要說明的是,Pre-build with user-provided Hadoop:屬於“Hadoop free”版,這樣下載到的Spark,可應用到任意Hadoop版本。
Spark部署模式主要有四種:Local模式
(單機模式)、Standalone模式
(使用Spark自帶的簡單集羣管理器)、YARN模式
(使用YARN作爲集羣管理器)和Mesos模式
(使用Mesos作爲集羣管理器)。
這裏介紹Local模式(單機模式)的 Spark安裝。我們選擇Spark 2.4.3版本,並且假設當前使用用戶名hadoop登錄了Linux操作系統。
$ sudo tar -zxf ~/下載/spark-2.4.3-bin-without-hadoop.tgz -C/usr/local/
$ sudo mv ./spark-2.4.3-bin-without-hadoop/ ./spark
$ sudo chown -R hadoop:hadoop ./spark # 此處的 hadoop 爲你的用戶名
安裝後,還需要修改Spark的配置文件spark-env.sh
$ cd /usr/local/spark
$ sudo cp conf/spark-env.sh.template conf/spark-env.sh
$ sudo vim conf/spark-env.sh
#添加下面的環境變量信息
export SPARK_DIST_CLASSPATH=$(/usr/local/hadoop/bin/hadoop:classpath)
有了上面的配置信息以後,Spark就可以把數據存儲到Hadoop分佈式文件系統HDFS中,也可以從HDFS中讀取數據。如果沒有配置上面信息,Spark就只能讀寫本地數據,無法讀寫HDFS數據。
配置完成後就可以直接使用,不需要像Hadoop運行啓動命令。通過運行Spark自帶的示例,驗證Spark是否安裝成功。
$ cd /usr/local/spark
$ bin/run-example SparkPi
過濾後的運行結果如下圖示,可以得到π 的 5 位小數近似值:
Spark不依賴Hadoop安裝
Spark同樣也可以不依賴hadoop進行安裝,但是仍然需要JDK環境,同樣是在Spark官網上,選擇spark-2.4.3-bin-hadoop2.7.tgz。我們直接將其解壓出來,下面我們開始配置環境變量。我們進入編輯/etc/profile,在最後加上如下代碼。
#Spark
export SPARK_HOME=/opt/spark-2.4.3
export PATH=$PATH:$SPARK_HOME/bin
然後進入/spark-2.3.1/bin目錄下即可直接運行spark-shell。
下面配置本地集羣環境,首先我們進入剛剛解壓的Spark目錄,進入/spark-2.2.1/conf/,拷貝一份spark-env.sh。
$ cp spark-env.sh.template spark-env.sh
然後我們編輯這個文件,添加如下環境設置(按自身環境修改)
#export SCALA_HOME=/opt/scala-2.13.0
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-3.b13.el7_5.x86_64 #這裏是你jdk的安裝路徑
export SPARK_HOME=/opt/spark-2.4.3
export SPARK_MASTER_IP=XXX.XX.XX.XXX #將這裏的xxx改爲自己的Linux的ip地址
#export SPARK_EXECUTOR_MEMORY=512M
#export SPARK_WORKER_MEMORY=1G
#export master=spark://XXX.XX.XX.XXX:7070
再回到conf目錄下,拷貝一份slaves。
$ cp slaves.template slaves
在slaves最後加上localhost
,保存即可。最後想要啓動spark,進入安裝目錄下的sbin
文件夾下,運行start-all.sh
輸入登錄密碼,master和worker進程就能按照配置文件啓動。
在Spark Shell 中運行代碼
這裏介紹Spark Shell的基本使用。Spark shell提供了簡單的方式來學習API,並且提供了交互的方式來分析數據。它屬於REPL(Read-Eval-Print Loop,交互式解釋器),提供了交互式執行環境,表達式計算完成就會輸出結果,而不必等到整個程序運行完畢,因此可即時查看中間結果,並對程序進行修改,這樣可以在很大程度上提升開發效率。
Spark Shell支持Scala和Python,本文使用 Scala 來進行介紹。前面已經安裝了Hadoop和Spark,如果Spark不使用HDFS和YARN,那麼就不用啓動Hadoop也可以正常使用Spark。如果在使用Spark的過程中需要用到 HDFS,就要首先啓動 Hadoop。
這裏假設不需要用到HDFS,因此,就沒有啓動Hadoop。現在直接開始使用Spark。Spark-shell命令及其常用的參數如下:
$ ./bin/spark-shell —master
Spark的運行模式取決於傳遞給SparkContext的Master URL的值。Master URL可以是以下任一種形式:
-
local 使用一個Worker線程本地化運行SPARK(完全不併行)
-
local[*] 使用邏輯CPU個數數量的線程來本地化運行Spark
-
local[K] 使用K個Worker線程本地化運行Spark(理想情況下,K應該根據運行機器的CPU核數設定)
-
spark://HOST:PORT 連接到指定的Spark standalone master。默認端口是7077.
-
yarn-client 以客戶端模式連接YARN集羣。集羣的位置可以在HADOOP_CONF_DIR 環境變量中找到。
-
yarn-cluster 以集羣模式連接YARN集羣。集羣的位置可以在HADOOP_CONF_DIR 環境變量中找到。
-
mesos://HOST:PORT 連接到指定的Mesos集羣。默認接口是5050。
需要強調的是,本文采用“本地模式”(local)運行Spark,關於如何在集羣模式下運行Spark,之後的文章會着重介紹。
在Spark中採用本地模式啓動Spark Shell的命令主要包含以下參數:
–master
:這個參數表示當前的Spark Shell要連接到哪個master,如果是local[*],就是使用本地模式啓動spark-shell,其中,中括號內的星號表示需要使用幾個CPU核心(core);
–jars
: 這個參數用於把相關的JAR包添加到CLASSPATH中;如果有多個jar包,可以使用逗號分隔符連接它們;
比如,要採用本地模式,在4個CPU核心上運行spark-shell:
$ cd /usr/local/spark
$ /bin/spark-shell —master local[4]
或者,可以在CLASSPATH中添加code.jar,命令如下:
$ cd /usr/local/spark
$ ./bin/spark-shell -master local[4] --jars code.jar
可以執行spark-shell –help
命令,獲取完整的選項列表,具體如下:
$ cd /usr/local/spark
$ ./bin/spark-shell —help
[外鏈圖片轉存失敗(img-36hzRWo9-1569486879033)(spark-shell.png)]
上面是命令使用方法介紹,下面正式使用命令進入spark-shell環境,可以通過下面命令啓動spark-shell環境:
scala> 8*2+5
res0: Int = 21
最後,可以使用命令“:quit”退出Spark Shell,如下所示:
scala>:quit
或者,也可以直接使用“Ctrl+D”組合鍵,退出Spark Shell
Scala編寫wordCount
任務需求
學會了上文基本的安裝和執行後,現在練習一個任務:編寫一個Spark應用程序,對某個文件中的單詞進行詞頻統計。
準備工作:進入Linux系統,打開“終端”,進入Shell命令提示符狀態,然後,執行如下命令新建目錄:
$ cd /usr/local/spark
$ mkdir mycode
$ cd mycode
$ mkdir wordcount
$ cd wordcount
然後,在/usr/local/spark/mycode/wordcount
目錄下新建一個包含了一些語句的文本文件word.txt
,命令如下:
$ vim word.txt
首先可以在文本文件中隨意輸入一些單詞,用空格隔開,編寫Spark程序對該文件進行單詞詞頻統計。然後,按鍵盤Esc鍵退出vim編輯狀態,輸入“:wq”保存文件並退出vim編輯器。
在Spark-Shell中執行詞頻統計
-
啓動Spark-Shell
首先,登錄Linux系統(要注意記住登錄採用的用戶名,本教程統一採用hadoop用戶名進行登錄),打開“終端”(可以在Linux系統中使用Ctrl+Alt+T組合鍵開啓終端),進入shell命令提示符狀態,然後執行以下命令進入spark-shell:
$ cd /usr/local/spark
$ ./bin_spark-shell
$ …這裏省略啓動過程顯示的一大堆信息
$ scala>
啓動進入spark-shell需要一點時間,在進入spark-shell後,我們可能還需要到Linux文件系統中對相關目錄下的文件進行編輯和操作(比如要查看spark程序執行過程生成的文件),這個無法在park-shell中完成,因此,這裏再打開第二個終端,用來在Linux系統的Shell命令提示符下操作。
-
加載本地文件
在開始具體詞頻統計代碼之前,需要考慮如何加載文件,文件可能位於本地文件系統中,也有可能存放在分佈式文件系統HDFS中,下面先介紹介紹如何加載本地文件,以及如何加載HDFS中的文件。首先,請在第二個終端窗口下操作,用下面命令到達
/usr/local/spark/mycode/wordcount
目錄,查看一下上面已經建好的word.txt的內容:$ cd /usr/local/spark/mycode/wordcount $ cat word.txt
Cat命令會把word.txt文件的內容全部顯示到屏幕上。
現在切換回spark-shell,然後輸入下面命令:
scala> val textFile = sc.textFile(“file:///usr/local/spark/mycode/wordcount/word.txt”)
上面代碼中,val後面的是變量textFile,而sc.textFile()中的這個textFile是sc的一個方法名稱,這個方法用來加載文件數據。這兩個textFile不是一個東西,不要混淆。實際上,val後面的是變量textFile,你完全可以換個變量名稱,比如,val lines = sc.textFile(“file:///usr/local/spark/mycode/wordcount/word.txt”)。這裏使用相同名稱,就是有意強調二者的區別。
注意要加載本地文件,必須採用“file:///”開頭的這種格式。執行上上面這條命令以後,並不會馬上顯示結果,因爲Spark採用惰性機制,只有遇到“行動”類型的操作,纔會從頭到尾執行所有操作。所以,下面我們執行一條“行動”類型的語句,就可以看到結果:
scala>textFile.first()
first()是一個“行動”(Action)類型的操作,會啓動真正的計算過程,從文件中加載數據到變量textFile中,並取出第一行文本。屏幕上會顯示很多反饋信息,這裏不再給出,你可以從這些結果信息中,找到word.txt文件中的第一行的內容。
正因爲Spark採用了惰性機制,在執行轉換操作的時候,即使我們輸入了錯誤的語句,spark-shell也不會馬上報錯,而是等到執行“行動”類型的語句時啓動真正的計算,那個時候“轉換”操作語句中的錯誤就會顯示出來,比如:
val textFile = sc.textFile(“file:///usr/local/spark/mycode/wordcount/word123.txt”)
上面我們使用了一個根本就不存在的word123.txt,執行上面語句時,spark-shell根本不會報錯,因爲,沒有遇到“行動”類型的first()操作之前,這個加載操作時不會真正執行的。然後,我們執行一個“行動”類型的操作first(),如下:
scala> textFile.first()
執行上面語句後,會返回錯誤信息“拒絕連接”,因爲這個word123.txt文件根本就不存在。現在我們可以練習一下如何把textFile變量中的內容再次寫回到另外一個文本文件wordback.txt中:
val textFile = sc.textFile(“file:///usr/local/spark/mycode/wordcount/word.txt”) textFile.saveAsTextFile(“file:///usr/local/spark/mycode/wordcount/writeback”)
上面的saveAsTextFile()括號裏面的參數是保存文件的路徑,不是文件名。saveAsTextFile()是一個“行動”(Action)類型的操作,所以馬上會執行真正的計算過程,從word.txt中加載數據到變量textFile中,然後,又把textFile中的數據寫回到本地文件目錄“_usr_local_spark_mycode_wordcount_writeback/”下面,現在讓我們切換到Linux Shell命令提示符窗口中,執行下面命令:
$ cd /usr/local/spark/mycode/wordcount/writeback/ $ ls
執行結果會顯示,有兩個文件part-00000和_SUCCESS,我們可以使用cat命令查看一下part-00000文件,會發現結果是和上面word.txt中的內容一樣的。
詞頻統計
有了前面的鋪墊性介紹,下面我們開始第一個Spark應用程序:WordCount。請切換到spark-shell窗口,輸入如下命令:
scala> val textFile = sc.textFile(“file:///usr/local/spark/mycode/wordcount/word.txt”) scala> val wordCount = textFile.flatMap(line => line.split(“ “)).map(word => (word, 1)).reduceByKey((a, b) => a + b) scala> wordCount.collect()
上面只給出了代碼,省略了執行過程中返回的結果信息,因爲返回信息很多。下面簡單解釋一下上面的語句。
-
textFile包含了多行文本內容,textFile.flatMap(line => line.split(” “))會遍歷textFile中的每行文本內容,當遍歷到其中一行文本內容時,會把文本內容賦值給變量line,並執行Lamda表達式line => line.split(” “)。line => line.split(” “)是一個Lamda表達式,左邊表示輸入參數,右邊表示函數裏面執行的處理邏輯,這裏執行line.split(” “),也就是針對line中的一行文本內容,採用空格作爲分隔符進行單詞切分,從一行文本切分得到很多個單詞構成的單詞集合。這樣,對於textFile中的每行文本,都會使用Lamda表達式得到一個單詞集合,最終,多行文本,就得到多個單詞集合。textFile.flatMap()操作就把這多個單詞集合“拍扁”得到一個大的單詞集合。
-
然後,針對這個大的單詞集合,執行map()操作,也就是map(word => (word, 1)),這個map操作會遍歷這個集合中的每個單詞,當遍歷到其中一個單詞時,就把當前這個單詞賦值給變量word,並執行Lamda表達式word => (word, 1),這個Lamda表達式的含義是,word作爲函數的輸入參數,然後,執行函數處理邏輯,這裏會執行(word, 1),也就是針對輸入的word,構建得到一個tuple,形式爲(word,1),key是word,value是1(表示該單詞出現1次)。
-
程序執行到這裏,已經得到一個RDD,這個RDD的每個元素是(key,value)形式的tuple。最後,針對這個RDD,執行reduceByKey((a, b) => a + b)操作,這個操作會把所有RDD元素按照key進行分組,然後使用給定的函數(這裏就是Lamda表達式:(a, b) => a + b),對具有相同的key的多個value進行reduce操作,返回reduce後的(key,value),比如(“hadoop”,1)和(“hadoop”,1),具有相同的key,進行reduce以後就得到(“hadoop”,2),這樣就計算得到了這個單詞的詞頻。
-
編寫獨立應用程序執行詞頻統計
在上面spark-shell編寫wordcount後,下面我們編寫一個Scala應用程序來實現詞頻統計。首先登錄Linux系統,進入Shell命令提示符狀態,然後執行下面命令:
$ cd /usr/local/spark/mycode/wordcount/
$ mkdir -p src/main/scala 這裏加入-p選項,可以一起創建src目錄及其子目錄
然後在“/usr/local/spark/mycode/wordcount/src/main/scala”目錄下新建一個test.scala文件,裏面包含如下代碼:
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext./
import org.apache.spark.SparkConf
object WordCount {
def main(args: Array[String]) {
val inputFile = “file:///usr/local/spark/mycode/wordcount/word.txt”
val conf = new SparkConf().setAppName(“WordCount”).setMaster(“local[2]”)
val sc = new SparkContext(conf)
val textFile = sc.textFile(inputFile)
val wordCount = textFile.flatMap(line => line.split(“ “)).map(word => (word, 1)).reduceByKey((a, b) => a + b)
wordCount.foreach(println)
}
}
注意,SparkConf().setAppName(“WordCount”).setMaster(“local[2]”)這句語句,也可以刪除.setMaster(“local[2]”),只保留 val conf = new SparkConf().setAppName(“WordCount”)
。
如果test.scala沒有調用SparkAPI,則只要使用scalac命令編譯後執行即可。此處test.scala程序依賴 Spark API,因此需要通過 sbt 進行編譯打包。首先執行如下命令:
$ cd /usr/local/spark/mycode/wordcount/
$ vim simple.sbt
通過上面代碼,新建一個simple.sbt文件,請在該文件中輸入下面代碼:
name := “Simple Project”
version := “1.0”
scalaVersion := “2.11.8”
libraryDependencies += “org.apache.spark” %% “spark-core” % “2.1.0”
下面我們使用sbt打包Scala程序。爲保證sbt能正常運行,先執行如下命令檢查整個應用程序的文件結構,應該是類似下面的文件結構:
$ ./src
$ ./src/main
$ ./src/main/scala
$ ./src/main/scala/test.scala
$ ./simple.sbt
$ ./word.txt
接着,我們就可以通過如下代碼將整個應用程序打包成 JAR(首次運行同樣需要下載依賴包 ):
$ cd /usr/local/spark/mycode/wordcount/ 請一定把這目錄設置爲當前目錄
$ /usr/local/sbt/sbt package
上面執行過程需要消耗幾分鐘時間,屏幕上會返回一下信息:
hadoop@dblab-VirtualBox:_usr_local_spark_mycode_wordcount$ /usr_local_sbt_sbt package
OpenJDK 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0
[info] Set current project to Simple Project (in build file:/usr_local_spark_mycode_wordcount/)
[info] Updating {file:/usr_local_spark_mycode_wordcount/}wordcount…
[info] Resolving jline#jline;2.12.1 …
[info] Done updating.
[info] Packaging _usr_local_spark_mycode_wordcount_target_scala-2.11_simple-project_2.11-1.0.jar …
[info] Done packaging.
[success] Total time: 34 s, completed 2017-2-20 10:13:13
若屏幕上返回上述信息表明打包成功,生成的 jar 包的位置爲/usr/local/spark/mycode/wordcount/target/scala-2.11_simple-project_2.11-1.0.jar
。
最後通過spark-submit 運行程序。我們就可以將生成的jar包通過spark-submit提交到Spark中運行了,命令如下:
$ /usr/local/spark/bin/spark-submit —class “WordCount” /usr/local/spark/mycode/wordcount/target/scala-2.11_simple-project_2.11-1.0.jar
最終得到的詞頻統計結果類似如下:
(Spark,1)
(is,1)
(than,1)
(fast,1)
(love,2)
(i,1)
(I,1)
(hadoop,2)
Flume_Kafka_SparkStreaming實現詞頻統計
準備工作
在做這個project之前,需要預先準備好的環境如下:
安裝kafka(參考第一節)、安裝flume(參考第二節)、安裝Spark(參考第三節) 。
做完上面三個工作之後,我們開始進入正式的詞頻統計Demo。
Spark準備工作
要通過Kafka連接Spark來進行Spark Streaming操作,Kafka和Flume等高級輸入源,需要依賴獨立的庫(jar文件)。也就是說Spark需要jar包讓Kafka和Spark streaming相連。按照我們前面安裝好的Spark版本,這些jar包都不在裏面,爲了證明這一點,我們現在可以測試一下。請打開一個新的終端,輸入以下命令啓動spark-shell:
$ cd /usr/local/spark
$ ./bin/spark-shell
啓動成功後,在spark-shell中執行下面import語句:
import org.apache.spark.streaming.kafka._
程序報錯,因爲找不到相關jar包。根據Spark官網的說明,對於Spark版本,如果要使用Kafka,則需要下載spark-streaming-kafka相關jar包。Jar包下載地址(注意版本對應關係)。
接下來需要把這個文件複製到Spark目錄的jars目錄下,輸入以下命令:
$ cd /usr/local/spark/jars
$ mkdir kafka
$ cp ./spark-streaming-kafka-0-8_2.11-2.1.0.jar /usr/local/spark/jars/kafka
下面把Kafka安裝目錄的libs目錄下的所有jar文件複製到/usr/local/spark/jars/kafka
目錄下輸入以下命令:至此,所有環境準備工作已全部完成,下面開始編寫代碼。
Project 過程
-
編寫Flume配置文件flume_to_kafka.conf
輸入命令:
$ cd /usr/local/kafka/libs $ ls $ cp ./* /usr/local/spark/jars/kafka
內容如下:
a1.sources=r1 a1.channels=c1 a1.sinks=k1 #Describe/configure the source a1.sources.r1.type=netcat a1.sources.r1.bind=localhost a1.sources.r1.port=33333 #Describe the sink a1.sinks.k1.type=org.apache.flume.sink.kafka.KafkaSink a1.sinks.k1.kafka.topic=test a1.sinks.k1.kafka.bootstrap.servers=localhost:9092 a1.sinks.k1.kafka.producer.acks=1 a1.sinks.k1.flumeBatchSize=20 #Use a channel which buffers events in memory a1.channels.c1.type=memory a1.channels.c1.capacity=1000000 a1.channels.c1.transactionCapacity=1000000 #Bind the source and sink to the channel a1.sources.r1.channels=c1 a1.sinks.k1.channel=c1
-
編寫Spark Streaming程序(進行詞頻統計的程序)
首先創建scala代碼的目錄結構,輸入命令:
$ cd /usr/local/spark/mycode $ mkdir flume_to_kafka $ cd flume_to_kafka $ mkdir -p src/main/scala $ cd src/main/scala $ vim KafkaWordCounter.scala
KafkaWordCounter.scala是用於單詞詞頻統計,它會把從kafka發送過來的單詞進行詞頻統計,代碼內容如下:
reduceByKeyAndWindow函數作用解釋如下:
package org.apache.spark.examples.streaming import org.apache.spark._ import org.apache.spark.SparkConf import org.apache.spark.streaming._ import org.apache.spark.streaming.kafka._ import org.apache.spark.streaming.StreamingContext._ import org.apache.spark.streaming.kafka.KafkaUtils object KafkaWordCounter{ def main(args:Array[String]){ StreamingExamples.setStreamingLogLevels() val sc=new SparkConf().setAppName("KafkaWordCounter").setMaster("local[2]") val ssc=new StreamingContext(sc,Seconds(10)) ssc.checkpoint("file:///usr/local/spark/mycode/flume_to_kafka/checkpoint") //設置檢查點 val zkQuorum="localhost:2181" //Zookeeper服務器地址 val group="1" //topic所在的group,可以設置爲自己想要的名稱,比如不用1,而是val group = "test-consumer-group" val topics="test" //topics的名稱 val numThreads=1 //每個topic的分區數 val topicMap=topics.split(",").map((_,numThreads.toInt)).toMap val lineMap=KafkaUtils.createStream(ssc,zkQuorum,group,topicMap) val lines=lineMap.map(_._2) val words=lines.flatMap(_.split(" ")) val pair=words.map(x => (x,1)) val wordCounts=pair.reduceByKeyAndWindow(_ + _,_ - _,Minutes(2),Seconds(10),2) wordCounts.print ssc.start ssc.awaitTermination } }
reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks]) 更加高效的reduceByKeyAndWindow,每個窗口的reduce值,是基於先前窗口的reduce值進行增量計算得到的;它會對進入滑動窗口的新數據進行reduce操作,並對離開窗口的老數據進行“逆向reduce”操作。但是,只能用於“可逆reduce函數”,即那些reduce函數都有一個對應的“逆向reduce函數”(以InvFunc參數傳入);
此代碼中就是一個窗口轉換操作reduceByKeyAndWindow,其中,Minutes(2)是滑動窗口長度,Seconds(10)是滑動窗口時間間隔(每隔多長時間滑動一次窗口)。reduceByKeyAndWindow中就使用了加法和減法這兩個reduce函數,加法和減法這兩種reduce函數都是“可逆的reduce函數”,也就是說,當滑動窗口到達一個新的位置時,原來之前被窗口框住的部分數據離開了窗口,又有新的數據被窗口框住,但是,這時計算窗口內單詞的詞頻時,不需要對當前窗口內的所有單詞全部重新執行統計,而是隻要把窗口內新增進來的元素,增量加入到統計結果中,把離開窗口的元素從統計結果中減去,這樣,就大大提高了統計的效率。尤其對於窗口長度較大時,這種“逆函數”帶來的效率的提高是很明顯的。
-
創建StreamingExamples.scala
繼續在當前目錄(/usr/local/spark/mycode/flume_to_kafka/src/main/scala)下創建StreamingExamples.scala代碼文件,用於設置log4j,輸入命令:
vim StreamingExamples.scala
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) } } }
-
創建StreamingExamples.scala
繼續在當前目錄(/usr/local/spark/mycode/flume_to_kafka/src/main/scala)下創建StreamingExamples.scala代碼文件,用於設置log4j,輸入命令:
vim StreamingExamples.scala
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) } } }
-
打包文件simple.sbt
輸入命令:
$ cd /usr/local/spark/mycode/flume_to_kafka $ vim simple.sbt
內容如下:
name := "Simple Project" version := "1.0" scalaVersion := "2.11.8" libraryDependencies += "org.apache.spark" %% "spark-core" % "2.1.0" libraryDependencies += "org.apache.spark" % "spark-streaming_2.11" % "2.1.0" libraryDependencies += "org.apache.spark" % "spark-streaming-kafka-0-8_2.11" % "2.1.0"
要注意版本號一定要設置正確,在/usr/local/spark/mycode/flume_to_kafka目錄下輸入命令:
$ cd /usr/local/spark/mycode/flume_to_kafka $ find .
打包之前,這條命令用來查看代碼結構,目錄結構如下所示:
- 打包編譯
一定要在/usr/local/spark/mycode/flume_to_kafka目錄下運行打包命令。
輸入命令:
$ cd /usr/local/spark/mycode/flume_to_kafka
$ /usr/local/sbt/sbt package
第一次打包的過程可能會很慢,請耐心等待幾分鐘。打包成功後,會看到SUCCESS的提示。
- 啓動zookeeper和kafka
#啓動zookeeper:
$ cd /usr/local/kafka
$ ./bin/zookeeper-server-start.sh config/zookeeper.properties
# 新開一個終端,啓動Kafka:
$ cd /usr/local/kafka
$ bin/kafka-server-start.sh config/server.properties
- 運行程序KafkaWordCounter
打開一個新的終端,我們已經創建過topic,名爲test(這是之前在flume_to_kafka.conf中設置的topic名字),端口號2181。在終端運行KafkaWordCounter
程序,進行詞頻統計,由於現在沒有啓動輸入,所以只有提示信息,沒有結果。
輸入命令:
$ cd /usr/local/spark
$/usr/local/spark/bin/spark-submit --driver-class-path /usr/local/spark/jars/*:/usr/local/spark/jars/kafka/* --class "org.apache.spark.examples.streaming.KafkaWordCounter" /usr/local/spark/mycode/flume_to_kafka/target/scala-2.11/simple-project_2.11-1.0.jar
其中”/usr/local/spark/jars/“和”/usr/local/spark/jars/kafka/”用來指明引用的jar包,“org.apache.spark.examples.streaming.KafkaWordCounter”代表包名和類名,這是編寫KafkaWordCounter.scala裏面的包名和類名,最後一個參數用來說明打包文件的位置。
執行該命令後,屏幕上會顯示程序運行的相關信息,並會每隔10秒鐘刷新一次信息,用來輸出詞頻統計的結果,此時還只有提示信息,如下所示:
[外鏈圖片轉存失敗(img-ZnMkMxIN-1569486879035)(result_one.png)]
在啓動Flume之前,Zookeeper和Kafka要先啓動成功,不然啓動Flume會報連不上Kafka的錯誤。
- 啓動flume agent
打開第四個終端,在這個新的終端中啓動Flume Agent
輸入命令:
$ cd /usr/local/flume
$ bin/flume-ng agent --conf ./conf --conf-file ./conf/flume_to_kafka.conf --name a1 -Dflume.root.logger=INFO,console
啓動agent以後,該agent就會一直監聽localhost的33333端口,這樣,我們下面就可以通過“telnet localhost 33333”命令向Flume Source發送消息。這個終端也不要關閉,讓它一直處於監聽狀態。
- 發送消息
打開第五個終端,發送消息。輸入命令:
$ telnet localhost 33333
這個端口33333是在flume conf文件中設置的source
在這個窗口裏面隨便敲入若干個字符和若干個回車,這些消息都會被Flume監聽到,Flume把消息採集到以後彙集到Sink,然後由Sink發送給Kafka的topic(test)。因爲spark Streaming程序不斷地在監控topic,在輸入終端和前面運行詞頻統計程序那個終端窗口內看到統計結果。
分佈式環境搭建及相關DEMO
Flume
Flume在分佈式環境下跟單機下一致,只需要在一臺機器上搭建即可。
Kafka
搭建高吞吐量Kafka分佈式發佈訂閱消息集羣
-
Zookeeper集羣: 121.48.163.195:2181 , 113.54.154.68:2181,113.54.159.232:2181
-
kafka 集羣: 121.48.163.195 , 113.54.154.68,113.54.159.232
搭建 kafka 集羣
kafka 集羣: 121.48.163.195 , 113.54.154.68,113.54.159.232
-
下載kafka和zookeeper
步驟和前面單機版一致
-
修改配置
$ vim /usr/local/kafka_2.12-0.11.0.0/config/server.properties 設置broker.id 第一臺爲broker.id = 0 第二臺爲broker.id = 1 第三臺爲broker.id = 2 注意這個broker.id每臺服務器不能重複 然後設置zookeeper的集羣地址 zookeeper.connect=121.48.163.195:2181 , 113.54.154.68:2181,113.54.159.232:2181
-
修改zookeeper配置文件
$ vim /usr/local/zookeeper-3.4.5/conf/zoo.cfg #添加server.1 server.2 server.3 server.1=121.48.163.195:2888:3888 server.2=113.54.154.68:2888:3888 server.3=113.54.159.232:2888:3888 #添加id $ sudo echo "1" > /usr/local/zookeeper-3.4.5/data/myid(每臺機器的id可以和brokerid保持一致)
-
啓動服務
# 每臺機器運行命令,但是在實際大型集羣中可以使用腳本的方式一鍵啓動 $ bin/kafka-server-start.sh config/server.properties &
-
創建主題
$ /usr/local/kafka_2.12-0.11.0.0/bin/kafka-topics.sh --create --zookeeper 121.48.163.195:2181 , 113.54.154.68:2181,113.54.159.232:2181 --replication-factor 2 --partitions 1 --topic ymq --replication-factor 2 #複製兩份 --partitions 1 #創建1個分區 --topic #主題爲ymq # 運行list topic命令,可以看到該主題: $ /usr/local/kafka_2.12-0.11.0.0/bin/kafka-topics.sh --list --zookeeper 121.48.163.195:2181 , 113.54.154.68:2181,113.54.159.232:2181
-
其它操作
其它操作基本語法差不多一致,不再贅述,詳情可以參考官網
-
Kafka Manager
Spark
-
選取三臺服務器
- 121.48.163.195 主節點
- 113.54.154.68 從節點
- 113.54.159.232 從節點
設置三臺服務器root用戶,之後操作都用root用戶進行,便於管理
-
修改hosts文件
$ sudo vim /etc/hosts # 在上面加上服務器ip 121.48.163.195 Master 113.54.154.68 Slave1 113.54.159.232 Slave2
修改完之後
source /etc/hosts
-
SSH無密碼驗證配置
-
安裝和啓動ssh協議
我們需要兩個服務:ssh和rsync。可以通過下面命令查看是否已經安裝:
rpm -qa|grep openssh rpm -qa|grep rsync 如果沒有安裝ssh和rsync,可以通過下面命令進行安裝: sudo apt install ssh (安裝ssh協議) sudo apt install rsync (rsync是一個遠程數據同步工具,可通過LAN/WAN快速同步多臺主機間的文件) service sshd restart (啓動服務)
-
配置Master無密碼登錄所有Slave
配置Master節點,以下是在Master節點的配置操作。
- 在Master節點上生成密碼對,在Master節點上執行以下命令:
ssh-keygen -t rsa -P ‘’
生成的密鑰對:id_rsa和id_rsa.pub,默認存儲在"/root/.ssh"目錄下。
- 接着在Master節點上做如下配置,把id_rsa.pub追加到授權的key裏面去。
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
-
修改ssh配置文件"/etc/ssh/sshd_config"的下列內容,將以下內容的註釋去掉,在三臺機器上均進行修改:
RSAAuthentication yes # 啓用 RSA 認證 PubkeyAuthentication yes # 啓用公鑰私鑰配對認證方式 AuthorizedKeysFile .ssh/authorized_keys # 公鑰文件路徑(和上面生成的文件同)
-
重啓ssh服務,才能使剛纔設置有效。
service sshd restart
-
驗證無密碼登錄本機是否成功
ssh localhost
-
接下來的就是把公鑰複製到所有的Slave機器上。使用下面的命令進行復制公鑰:
$ scp /root/.ssh/id_rsa.pub root@Slave1:/root/ $ scp /root/.ssh/id_rsa.pub root@Slave2:/root/
接着配置Slave節點,以下是在Slave1節點的配置操作。
1>在"/root/“下創建”.ssh"文件夾,如果已經存在就不需要創建了。
mkdir /root/.ssh
2)將Master的公鑰追加到Slave1的授權文件"authorized_keys"中去。
cat /root/id_rsa.pub >> /root/.ssh/authorized_keys
3)修改"/etc/ssh/sshd_config",具體步驟參考前面Master設置的第3步和第4步。
4)用Master使用ssh無密碼登錄Slave1
ssh 114.55.246.77
5)把"/root/"目錄下的"id_rsa.pub"文件刪除掉。
rm –r /root/id_rsa.pub
重複上面的5個步驟把Slave2服務器進行相同的配置。
-
配置Slave無密碼登錄Master
以下是在Slave1節點的配置操作。
1)創建"Slave1"自己的公鑰和私鑰,並把自己的公鑰追加到"authorized_keys"文件中,執行下面命令:
ssh-keygen -t rsa -P ‘’
cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys
2)將Slave1節點的公鑰"id_rsa.pub"複製到Master節點的"/root/"目錄下。
scp /root/.ssh/id_rsa.pub root@Master:/root/
以下是在Master節點的配置操作。
1)將Slave1的公鑰追加到Master的授權文件"authorized_keys"中去。
cat ~/id_rsa.pub >> ~/.ssh/authorized_keys
2)刪除Slave1複製過來的"id_rsa.pub"文件。
rm –r /root/id_rsa.pub
配置完成後測試從Slave1到Master無密碼登錄。
ssh 114.55.246.88
按照上面的步驟把Slave2和Master之間建立起無密碼登錄。這樣,Master能無密碼驗證登錄每個Slave,每個Slave也能無密碼驗證登錄到Master。
-
-
安裝基礎環境(JAVA和SCALA環境)
這裏不再贅述
-
Hadoop2.7.3完全分佈式搭建
以下是在Master節點操作:
-
下載二進制包hadoop-2.7.7.tar.gz
-
解壓並移動到相應目錄,我習慣將軟件放到/opt目錄下,命令如下:
$ tar -zxvf hadoop-2.7.3.tar.gz $ mv hadoop-2.7.7 /opt
-
修改對應的配置文件,修改/etc/profile,增加如下內容:
export HADOOP_HOME=/opt/hadoop-2.7.3/ export PATH=$PATH:$HADOOP_HOME/bin export PATH=$PATH:$HADOOP_HOME/sbin export HADOOP_MAPRED_HOME=$HADOOP_HOME export HADOOP_COMMON_HOME=$HADOOP_HOME export HADOOP_HDFS_HOME=$HADOOP_HOME export YARN_HOME=$HADOOP_HOME export HADOOP_ROOT_LOGGER=INFO,console export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib"
-
修改完成後執行
$ source /etc/profile
-
修改$HADOOP_HOME/etc/hadoop/hadoop-env.sh,修改JAVA_HOME 如下:
export JAVA_HOME=/usr/local/jdk1.8.0_121
-
修改$HADOOP_HOME/etc/hadoop/slaves,將原來的localhost刪除,改成如下內容:
-
Slave1
-
Slave2
-
-
修改$HADOOP_HOME/etc/hadoop/core-site.xml
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://Master:9000</value> </property> <property> <name>io.file.buffer.size</name> <value>131072</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/hadoop-2.7.7/tmp</value> </property> </configuration>
-
修改$HADOOP_HOME/etc/hadoop/hdfs-site.xml
<configuration> <property> <name>dfs.namenode.secondary.http-address</name> <value>Master:50090</value> </property> <property> <name>dfs.replication</name> <value>2</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>file:/opt/hadoop-2.7.7/hdfs/name</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>file:/opt/hadoop-2.7.7/hdfs/data</value> </property> </configuration>
-
cp mapred-site.xml.template mapred-site.xml
,並修改$HADOOP_HOME/etc/hadoop/mapred-site.xml<configuration> <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.address</name> <value>Master:19888</value> </property> </configuration>
-
修改$HADOOP_HOME/etc/hadoop/yarn-site.xml
<configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</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> </configuration>
-
複製Master節點的hadoop文件夾到Slave1和Slave2上
$ scp -r /opt/hadoop-2.7.7 root@Slave1:/opt $ scp -r /opt/hadoop-2.7.7 root@Slave2:/opt
-
在Slave1和Slave2上分別修改/etc/profile,過程同Master一樣
-
在Master節點啓動集羣,啓動之前格式化一下namenode:
-
Hadoop namenode -format
-
啓動:
/opt/hadoop-2.7.7/sbin/start-all.sh
-
至此hadoop的完全分佈式搭建完畢
-
-
查看集羣是否啓動成功:
$ jps -m Master顯示: SecondaryNameNode ResourceManager NameNode Slave顯示: NodeManager DataNode
-
-
Spark完全分佈式環境搭建
以下操作都在Master節點進行。
-
下載二進制包spark-2.4.3-bin-hadoop2.7.tgz
-
解壓並移動到相應目錄,命令如下:
$ tar -zxvf spark-2.4.3-bin-hadoop2.7.tgz $ mv hadoop-2.7.3 /opt
-
修改相應的配置文件,修改/etc/profie,增加如下內容:
export SPARK_HOME=/opt/spark-2.4.3-bin-hadoop2.7/ export PATH=$PATH:$SPARK_HOME/bin
-
複製spark-env.sh.template成spark-env.sh
$ cp spark-env.sh.template spark-env.sh
-
修改$SPARK_HOME/conf/spark-env.sh,添加如下內容:
export JAVA_HOME=/usr/local/jdk1.8.0_121 export SCALA_HOME=/usr/share/scala export HADOOP_HOME=/opt/hadoop-2.7.3 export HADOOP_CONF_DIR=/opt/hadoop-2.7.3/etc/hadoop export SPARK_MASTER_IP=114.55.246.88 export SPARK_MASTER_HOST=114.55.246.88 export SPARK_LOCAL_IP=114.55.246.88 export SPARK_WORKER_MEMORY=1g export SPARK_WORKER_CORES=2 export SPARK_HOME=/opt/spark-2.4.3-bin-hadoop2.7 export SPARK_DIST_CLASSPATH=$(/opt/hadoop-2.7.3/bin/hadoop classpath)
-
複製slaves.template成slaves
$ cp slaves.template slaves
-
修改$SPARK_HOME/conf/slaves,添加如下內容:
Master Slave1 Slave2
-
將配置好的spark文件複製到Slave1和Slave2節點
$ scp /opt/spark-2.4.3-bin-hadoop2.7 root@Slave1:/opt $ scp /opt/spark-2.4.3-bin-hadoop2.7 root@Slave2:/opt
-
修改Slave1和Slave2配置
在Slave1和Slave2上分別修改/etc/profile,增加Spark的配置,過程同Master一樣。
在Slave1和Slave2修改$SPARK_HOME/conf/spark-env.sh,將export SPARK_LOCAL_IP=114.55.246.88改成Slave1和Slave2對應節點的IP。
-
在Master節點啓動集羣
/opt/spark-2.4.3-bin-hadoop2.7/sbin/start-all.sh
-
查看集羣是否啓動成功
$ jps -m Master在Hadoop的基礎上新增了: Master Slave在Hadoop的基礎上新增了: Worker
-
在我的博客查看更多
作者:槐洛文