本來Streaming整合kafka是由兩種方式的,第一種是Receiver DStream,第二種是Direct DStream
但是由于目前kafka版本升級到2.0以上了,并且我用的kafka版本是/usr/local/kafka_2.11-2.1.1,我們就不再介紹Receiver DStream這種方式了。
為什么要使用Direct DStream方式尼?他又三個大的優點,如下:
1、簡化的并行性:使用DirectStream,Spark流將創建與要使用的Kafka分區一樣多的RDD分區,這些分區都將并行地從Kafka讀取數據。所以在Kafka和RDD分區之間有一對一的映射,這更容易理解和調整
2、高效率:在第一種方法中實現零數據丟失需要將數據存儲在提前寫入日志中,從而進一步復制數據。這實際上是低效的,因為數據有效地被復制了兩次—一次是由Kafka復制的,第二次是由提前寫入日志復制的。第二種方法消除了問題,因為沒有接收器,因此不需要提前寫入日志。只要您有足夠的卡夫卡保留,就可以從卡夫卡恢復消息。
3、恰好一次語義:第一種方法使用Kafka的高級API將消耗的偏移存儲在ZooKeeper中。這是傳統上使用卡夫卡數據的方法。雖然這種方法(與提前寫入日志結合)可以確保零數據丟失(即至少一次語義),但在某些故障下,某些記錄可能會被消耗兩次的可能性很小。這是因為火花流可靠接收的數據與ZooKeeper跟蹤的偏移量不一致。因此,在第二種方法中,我們使用不使用ZooKeeper的簡單Kafka API。偏移量由檢查點內的火花流跟蹤。這消除了Spark流和ZooKeeper/Kafka之間的不一致性,因此盡管失敗,Spark流有效地接收到每個記錄一次。為了實現結果輸出的一次性語義,將數據保存到外部數據存儲的輸出操作必須是等冪的,或者是保存結果和偏移量的原子事務
但是這種方式有一個缺點:這種方式不能在zk中更新偏移量,因此也不能通過zk的監控工具看到運行進度,因此需要你自己處理這個偏移量,
下面是我寫的整合步驟樣例:、
第一步、啟動zk和kafka、創建生產者和消費者
啟動zk?
./zkServer.sh start
啟動kafka
./kafka-server-start.sh ${KAFKA_HOME}/config/server.properties
創建topic
./kafka-topics.sh --create --zookeeper 10.101.3.3:2181 --replication-factor 1 --partitions 1 --topic kafka_streaming
查看是否創建成功
./kafka-topics.sh --list --zookeeper 10.101.3.3:2181
創建topic的對應的生產者
./kafka-console-producer.sh --broker-list 10.101.3.3:9092 --topic kafka_streaming
創建topic對應的消費者
./kafka-console-consumer.sh --bootstrap-server 10.101.3.3:9092 --topic kafka_streaming --from-beginning
檢驗下是否能夠正常通訊:
第二步、添加spark-streaming-kafka依賴jar包
第三步、使用jdk1.8和kafkka0.10_2.11和spark streaming2.11版本來編寫整合程序。
首先我們先解釋下用到的兩個特殊類。
LocationStrategies
新的Kafka使用者API將預先獲取消息到緩沖區。因此,出于性能原因,Spark集成將緩存的消費者保留在執行程序上(而不是為每個批處理重新創建它們),并且更喜歡在具有適當使用者的主機位置上安排分區,這一點很重要。
在大多數情況下,您應該使用LocationStrategies.PreferConsistent,如上所示。這將在可用執行程序之間均勻分配分區。如果您的執行程序與Kafka代理在同一主機上,請使用PreferBrokers,它更愿意為該分區安排Kafka領導者的分區。最后,如果分區之間的負載有明顯偏差,請使用PreferFixed。這允許您指定分區到主機的顯式映射(任何未指定的分區將使用一致的位置)。
消費者的緩存的默認最大大小為64.如果您希望處理超過(64 *個執行程序數)Kafka分區,則可以通過spark.streaming.kafka.consumer.cache.maxCapacity更改此設置。
如果要禁用Kafka使用者的緩存,可以將spark.streaming.kafka.consumer.cache.enabled設置為false。可能需要禁用緩存來解決SPARK-19185中描述的問題。一旦SPARK-19185解決,可以在Spark的更高版本中刪除此屬性。
緩存由topicpartition和group.id鍵入,因此每次調用createDirectStream時都要使用單獨的group.id。?
ConsumerStrategies
新的Kafka消費者API有許多不同的方法來指定主題,其中一些需要相當大的后對象實例化設置。 ConsumerStrategies提供了一種抽象,即使從檢查點重新啟動后,Spark也可以獲得正確配置的消費者。
ConsumerStrategies.Subscribe,如上所示,允許您訂閱固定的主題集合。 SubscribePattern允許您使用正則表達式來指定感興趣的主題。請注意,與0.8集成不同,使用Subscribe或SubscribePattern應響應在正在運行的流期間添加分區。最后,Assign允許您指定固定的分區集合。所有這三個策略都重載了構造函數,允許您指定特定分區的起始偏移量。
如果您具有上述選項無法滿足的特定使用者設置需求,則ConsumerStrategy是您可以擴展的公共類。
程序:
package com.liushun;/*
@auth:liushun
@date:2019-06-20 16:03
Spark Streaming整合kafka,使用Driect DStream方式
*/
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.Optional;
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.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaInputDStream;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka010.ConsumerStrategies;
import org.apache.spark.streaming.kafka010.KafkaUtils;
import org.apache.spark.streaming.kafka010.LocationStrategies;
import scala.Tuple2;
import java.util.*;
import java.util.regex.Pattern;
public class KaFKaUnionStreamingWordCnt {
private static final PatternSPACE = Pattern.compile(" ");
public static void main(String[] args) {
SparkConf sc=new SparkConf().setMaster("local[2]").setAppName("KaFKaUnionStreamingWordCnt");
JavaStreamingContext jssc=new JavaStreamingContext(sc, Durations.seconds(5));//5秒一次
? ? ? ? jssc.checkpoint(".");//設置上一個批次的值存在的目錄,在生產環境中,放在hdfs某個文件下,相對安全些
// 首先要創建一份kafka參數map
? ? ? ? Map kafkaParams =new HashMap();
// 這里是不需要zookeeper節點,所以這里放broker.list
? ? ? ? String brokerslist="10.101.3.3:9092";
String topics ="kafka_streaming";
//Kafka服務監聽端口
? ? ? ? kafkaParams.put("bootstrap.servers",brokerslist);
//指定kafka輸出key的數據類型及編碼格式(默認為字符串類型編碼格式為uft-8)
? ? ? ? kafkaParams.put("key.deserializer", StringDeserializer.class);
//指定kafka輸出value的數據類型及編碼格式(默認為字符串類型編碼格式為uft-8)
? ? ? ? kafkaParams.put("value.deserializer", StringDeserializer.class);
//消費者ID,隨意指定
? ? ? ? kafkaParams.put("group.id","jis");
//指定從latest(最新,其他版本的是largest這里不行)還是smallest(最早)處開始讀取數據
? ? ? ? kafkaParams.put("auto.offset.reset","latest");
//如果true,consumer定期地往zookeeper寫入每個分區的offset
? ? ? ? kafkaParams.put("enable.auto.commit",false);
//Topic分區
? ? ? ? Map offsets =new HashMap<>();
offsets.put(new TopicPartition(topics,0),0L);
Collection topicsSet =new HashSet<>(Arrays.asList(topics.split(",")));
//通過KafkaUtils.createDirectStream(...)獲得kafka數據,kafka相關參數由kafkaParams指定
? ? ? ? try {
JavaInputDStream> kafkaStream = KafkaUtils.createDirectStream(
jssc,
LocationStrategies.PreferConsistent(),
ConsumerStrategies.Subscribe(topicsSet, kafkaParams, offsets)
//也可以用不指定offset的構造函數替換上部的訂閱
//ConsumerStrategies.Subscribe(topicsSet, kafkaParams)
);
//使用map先對InputDStream進行取值操作
? ? ? ? ? ? JavaDStream lines=kafkaStream.map(new Function, String>() {
@Override
? ? ? ? ? ? ? ? public String call(ConsumerRecord consumerRecord)throws Exception {
return consumerRecord.value();
}
});
//再使用flatMap進行行記錄的空格操作
? ? ? ? ? ? JavaDStream words = lines.flatMap(x -> Arrays.asList(SPACE.split(x)).iterator());
JavaPairDStream pairs? = words.mapToPair(s ->new Tuple2<>(s,1));
//狀態保留
? ? ? ? ? ? JavaPairDStream runningCounts = pairs.updateStateByKey(
new Function2, Optional, Optional>() {
@Override
? ? ? ? ? ? ? ? ? ? ? ? public Optional call(List values, Optional state)throws Exception {
Integer updateValue =0;
if (state.isPresent()) {//是否為空
// 如果有值就獲取
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? updateValue = state.get();
}
// 累加
? ? ? ? ? ? ? ? ? ? ? ? ? ? for (Integer value : values) {
updateValue += value;
}
return Optional.of(updateValue);
}
}
);
runningCounts.print();
jssc.start();
jssc.awaitTermination();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
打包提交到spark集群中
./spark-submit --class com.liushun.KaFKaUnionStreamingWordCnt --master yarn /usr/local/spark-2.1.1-bin-hadoop2.6/bin/SparkStreamTest-1.1-SNAPSHOT.jar
驗證正確性: