SparkStreaming读Kafka:
无状态流处理:
object MyReadKafkaHandler {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("mytest").setMaster("local[2]")
val sc = SparkContext.getOrCreate(conf)
// 流处理的上下文类
val ssc = new StreamingContext(sc,Seconds(10)) //每隔十秒拉取一次数据
// 创建链接kafka服务器参数
val kafkaParam = Map(
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "192.168.56.101:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "myKafka2",
ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG -> "true",
ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG -> "20000",
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG -> "earliest"
)
// 创建Direct流
val stream = KafkaUtils.createDirectStream(ssc,LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String,String](Set("mydemo"),kafkaParam))
// 简单的数据处理,并打印
stream.map(_.value).flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).print()
// 启动sparkStreaming
ssc.start()
ssc.awaitTermination()
}
}
讲解:CreateDirectStream中本地策略参数和消费者策略讲解
LocationStrategies(本地位置策略):
控制特定的主题分区在哪个执行器上消费的。
在executor针对主题分区如何对消费者进行调度。
位置的选择是相对的,位置策略有三种方案:
1.PreferBrokers
首选kafka服务器,
只有在kafka服务器和executor位于同一主机,可以使用该中策略。
2.PreferConsistent
首选一致性.
多数时候采用该方式,在所有可用的执行器上均匀分配kakfa的主题的所有分区。 综合利用集群的计算资源。
3.PreferFixed
首选固定模式。
如果负载不均衡,可以使用该中策略放置在特定节点使用指定的主题分区。手动控制方案。没有显式指定的分区仍然采用(2)方案。
一般在工作中最经常用的为PreferConsistent,他可以适用于99%的情况。
ConsumerStrategies(消费者策略)
是控制如何创建和配制消费者对象。
或者对kafka上的消息进行如何消费界定,比如t1主题的分区0和1,
或者消费特定分区上的特定消息段。
该类可扩展,自行实现。
1.ConsumerStrategies.Assign
指定固定的分区集合,指定了特别详细的方范围。
def Assign[K, V](
topicPartitions: Iterable[TopicPartition],
kafkaParams: collection.Map[String, Object],
offsets: collection.Map[TopicPartition, Long])
2.ConsumerStrategies.Subscribe
允许消费订阅固定的主题集合。
3.ConsumerStrategies.SubscribePattern
使用正则表达式指定感兴趣的主题集合。
如果有分区则用Assign策略,如果涉及到正则就用SubscribePattern策略,其余情况都用Subscribe
有状态流处理:
object MyReadKafkaHandler {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("mytest").setMaster("local[2]")
val sc = SparkContext.getOrCreate(conf)
// 流处理的上下文类
val ssc = new StreamingContext(sc,Seconds(10))
// 因为有状态DStream,所以必须要有地方记录
ssc.checkpoint("d:/mykafka-logs")
// 创建链接kafka服务器参数
val kafkaParam = Map(
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "192.168.56.101:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "myKafka2",
ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG -> "true",
ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG -> "20000",
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG -> "earliest"
)
// 创建Direct流
val stream = KafkaUtils.createDirectStream(ssc,LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String,String](Set("mydemo"),kafkaParam))
// 带状态的DStream(1)
val value = stream.map(_.value()).flatMap(_.split(" ")).map((_,1))
value.updateStateByKey((values:Seq[Int],state:Option[Int])=>{
var newVal = state.getOrElse(0)
for (elem <- values) {
newVal+=elem
}
Option(newVal)
}).print()
// 带状态的DStream(2)
stream.map(_.value()).flatMap(_.split(" ")).map((_,1)).updateStateByKey((values:Seq[Int],stateValue:Option[Int])=>Option(stateValue.getOrElse(0)+values.sum)).print()
//两种写法意思相同,只是写法不一样而已,仅供参考
// 启动sparkStreaming
ssc.start()
ssc.awaitTermination()
}
}
SparkStreaming写入Kafka
将KafkaProducer利用lazy val的方式进行包装
class KafkaSinks[K,V](fc:()=>KafkaProducer[K,V]) extends Serializable {
lazy val producer = fc()
def send(topic:String,key:K,value:V)={
producer.send(new ProducerRecord[K,V](topic,key,value))
}
def send(topic:String,value: V)={
producer.send(new ProducerRecord[K,V](topic,value))
}
}
object KafkaSinks{
import scala.collection.JavaConversions._
def apply[K,V](conf:Map[String,String]): KafkaSinks[K,V] = {
var func = ()=>{
val prod = new KafkaProducer[K,V](conf)
sys.addShutdownHook{
prod.close()
}
prod
}
new KafkaSinks[K,V](func)
}
def apply[K,V](conf: Properties): KafkaSinks[K,V] = apply(conf.toMap)
}
利用广播变量将KafkaProducer广播到每一个executor上
普通写法:
// 广播KafkaSink
val kafkaProducer: Broadcast[KafkaSinks[String, String]] = {
val kafkaProducerConfig = {
val p = new Properties()
p.setProperty("bootstrap.servers", Conf.brokers)
p.setProperty("key.serializer", classOf[StringSerializer].getName)
p.setProperty("value.serializer", classOf[StringSerializer].getName)
p
}
ssc.sparkContext.broadcast(KafkaSinks[String, String](kafkaProducerConfig))
}
高阶写法:
object MySingleBaseDAO {
@volatile private var instance:Broadcast[KafkaSinks[String,String]] = null
def getInstance() = {
if(instance==null) {
val conf = new SparkConf().setMaster("local[2]").setAppName("writeKafka")
val sc = SparkContext.getOrCreate(conf)
synchronized{
if (instance==null){
val kafkaParam = Map[String,String](
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG -> "192.168.56.101:9092",
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG -> classOf[StringSerializer].getName,
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG ->classOf[StringSerializer].getName
)
instance = sc.broadcast(KafkaSinks[String,String](kafkaParam))
}
instance
}
}
instance
}
}
这样就可以在每个executor中将数据写入到Kafka中
//输出到kafka
segmentedStream.foreachRDD(rdd => {
if (!rdd.isEmpty) {
rdd.foreach(record => {
kafkaProducer.value.send(Conf.outTopics, record._1.toString, record._2)
// do something else
})
}
})