Spark Streaming的原理说明的文章很多,这里不做介绍。本文主要介绍使用Kafka作为数据源的编程模型,编码实践,以及一些优化说明
spark streaming:http://spark.apache.org/docs/1.6.0/streaming-programming-guide.html
streaming-kafka-integration:http://spark.apache.org/docs/1.6.0/streaming-kafka-integration.html
目前Spark Streaming 的kafka编程主要包括两种模型
1. 基于Receiver
2. Direct(无Receiver)
这种方式利用接收器(Receiver)来接收kafka中的数据,其最基本是使用Kafka高阶用户API接口。对于所有的接收器,从kafka接收来的数据会存储在spark的executor中,之后spark streaming提交的job会处理这些数据
在spark1.3之后,引入了Direct方式。不同于Receiver的方式,Direct方式没有receiver这一层,其会周期性的获取Kafka中每个topic的每个partition中的最新offsets,之后根据设定的maxRatePerPartition来处理每个batch
不同于Receiver的方式(是从Zookeeper中读取offset值,那么自然zookeeper就保存了当前消费的offset值,那么如果重新启动开始消费就会接着上一次offset值继续消费)。而在Direct的方式中,是直接从kafka来读数据,那么offset需要自己记录,可以利用checkpoint、数据库或文件记录或者回写到zookeeper中进行记录
package com.eric.kafka.producer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Properties;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
/**
* Hello world!
*/
public class ProcuderSample {
private final Producer producer;
public final static String TOPIC = "spark_streaming_test_topic";
public final static Integer BATCH_SIZE = 2000;
private ProcuderSample() {
Properties props = new Properties();
// 此处配置的是kafka的端口
props.put("metadata.broker.list", "server1-2-5-24-138:9092,server1-3-5-24-139:9092,server1:9092");
// 配置value的序列化类
props.put("serializer.class", "kafka.serializer.StringEncoder");
// 配置key的序列化类
props.put("key.serializer.class", "kafka.serializer.StringEncoder");
props.put("request.required.acks", "-1");
producer = new Producer(new ProducerConfig(props));
}
public void deadLoopSendMessage(){
int recordCount=0;
List> tmpList=new ArrayList>();
while(true){
Random rand=new Random();
// 批量发送数据
// String randResult=recordCount+":"+rand.nextInt(100);
String randResult=rand.nextInt(10)+"";
tmpList.add(new KeyedMessage(TOPIC, randResult , randResult));
if (tmpList.size()%BATCH_SIZE==0){
producer.send(tmpList);
tmpList.clear();
}
// producer.send(new KeyedMessage(TOPIC, randResult , randResult));
recordCount+=1;
}
}
public static void main(String[] args) {
new ProcuderSample().deadLoopSendMessage();
}
}
# encoding:utf-8
__author__ = 'eric.sun'
"""演示如何使用Spark Streaming 通过Kafka Streaming实现WordCount
执行命令:./spark-submit --master spark://server1-1-5-24-137:7077 --packages org.apache.spark:spark-streaming-kafka_2.10:1.6.0 ../examples/kafka_streaming.py > log
Kafka数据源程序:https://github.com/Eric-aihua/practise.git/java_cookbook/cookbook/src/main/java/com/eric/kafka/producer/ProcuderSample.java
"""
from pyspark import SparkContext
from pyspark import SparkConf
from pyspark.streaming import StreamingContext
from pyspark.streaming.kafka import KafkaUtils
def start():
sconf=SparkConf()
# sconf.set('spark.streaming.blockInterval','100')
sconf.set('spark.cores.max' , 8)
sc=SparkContext(appName='KafkaWordCount',conf=sconf)
ssc=StreamingContext(sc,2)
numStreams = 3
kafkaStreams = [KafkaUtils.createStream(ssc,"server1-2-5-24-138:2181,server1-3-5-24-139:2181,server1-4-5-24-140:2181","streaming_test_group",{"spark_streaming_test_topic":1}) for _ in range (numStreams)]
unifiedStream = ssc.union(*kafkaStreams)
print unifiedStream
#统计生成的随机数的分布情况
result=unifiedStream.map(lambda x:(x[0],1)).reduceByKey(lambda x, y: x + y)
result.pprint()
ssc.start() # Start the computation
ssc.awaitTermination() # Wait for the computation to terminate
if __name__ == '__main__':
start()
# encoding:utf-8
__author__ = 'eric.sun'
"""演示如何使用Spark Streaming 通过Kafka Direct Streaming实现WordCount
执行命令:./spark-submit --master spark://server1-1-5-24-137:7077 --packages org.apache.spark:spark-streaming-kafka_2.10:1.6.0 ../examples/kafka_streaming.py > log
Kafka数据源程序:https://github.com/Eric-aihua/practise.git/java_cookbook/cookbook/src/main/java/com/eric/kafka/producer/ProcuderSample.java
使用Direct的好处
1:根据topic的分区数默认的创建对应数量的rdd分区数
2:Receiver的方式需要通过Write AHead Log的方式确保数据不丢失,Direct的方式不需要
3:一次处理:使用Kafka Simple API对数据进行读取,使用checkpoint对offset进行记录
问题:
基于Zookeeper的Kafka监控工具不能获取offset的值了,需要在每次Batch处理后,可以对Zookeeper的值进行设置
"""
from pyspark import SparkContext
from pyspark import SparkConf
from pyspark.streaming import StreamingContext
from pyspark.streaming.kafka import KafkaUtils
def start():
sconf=SparkConf()
sconf.set('spark.cores.max' , 8)
sc=SparkContext(appName='KafkaDirectWordCount',conf=sconf)
ssc=StreamingContext(sc,2)
brokers="server1-2-5-24-138:9092,server1-3-5-24-139:9092,server1-4-5-24-140:9092"
topic='spark_streaming_test_topic'
kafkaStreams = KafkaUtils.createDirectStream(ssc,[topic],kafkaParams={"metadata.broker.list": brokers})
#统计生成的随机数的分布情况
result=kafkaStreams.map(lambda x:(x[0],1)).reduceByKey(lambda x, y: x + y)
#打印offset的情况,此处也可以写到Zookeeper中
#You can use transform() instead of foreachRDD() as your
# first method call in order to access offsets, then call further Spark methods.
kafkaStreams.transform(storeOffsetRanges).foreachRDD(printOffsetRanges)
result.pprint()
ssc.start() # Start the computation
ssc.awaitTermination() # Wait for the computation to terminate
offsetRanges = []
def storeOffsetRanges(rdd):
global offsetRanges
offsetRanges = rdd.offsetRanges()
return rdd
def printOffsetRanges(rdd):
for o in offsetRanges:
print "%s %s %s %s %s" % (o.topic, o.partition, o.fromOffset, o.untilOffset,o.untilOffset-o.fromOffset)
if __name__ == '__main__':
start()
Spark streaming+Kafka的使用中,当数据量较小,很多时候默认配置和使用便能够满足情况,但是当数据量大的时候,就需要进行一定的调整和优化,而这种调整和优化本身也是不同的场景需要不同的配置。
对于这三种出现序列化的地方,我们都可以通过使用Kryo序列化类库,来优化序列化和反序列化的性能。Spark默认使用的是Java的序列化机制,也就是ObjectOutputStream/ObjectInputStream API来进行序列化和反序列化。但是Spark同时支持使用Kryo序列化库,Kryo序列化类库的性能比Java序列化类库的性能要高很多。官方介绍,Kryo序列化机制比Java序列化机制,性能高10倍左右。Spark之所以默认没有使用Kryo作为序列化类库,是因为Kryo要求最好要注册所有需要进行序列化的自定义类型,因此对于开发者来说,这种方式比较麻烦。
以下是使用Kryo的代码示例,我们只要设置序列化类,再注册要序列化的自定义类型即可(比如算子函数中使用到的外部变量类型、作为RDD泛型类型的自定义类型等):
// 创建SparkConf对象。 val conf = new SparkConf.setMaster(...).setAppName(...) //设置序列化器为KryoSerializer。
conf.set("spark.serializer","org.apache.spark.serializer.KryoSerializer") //注册要序列化的自定义类型。
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))