kafka -> sparkStreaming -> kafka
序列化还可以这样写(就是说的代码中直接写在KafkaProducer后面)
(序列化、lazy、广播变量 )这是一套放在一起写比较好
场景是让变量这样在foreachRDD外面写producer 变量,不在里面创建多次 写在外面只一次就可以
但是要序列化 还要加lazy,最好再广播一下
1… with Serializable 直接在语句 变量后面 跟序列化声明
2.然后有时候不使用java的Map 要导入scala的集合使用
import scala.collection.JavaConversions.__
好处:
广播变量节省数据传输
lazy延迟创建
package Kafka010
import Kafka010.Utils.MyKafkaUtils
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.clients.producer.{KafkaProducer, ProducerConfig, ProducerRecord, RecordMetadata}
import org.apache.kafka.common.serialization.StringSerializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka010._
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* Created by Shi shuai RollerQing on 2019/12/24 19:47
* kafka -> sparkStreaming -> kafka
*/
object Kafka010Demo07 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName(s"${this.getClass.getCanonicalName}")
val ssc = new StreamingContext(conf, Seconds(5))
//读数据
val groupID = "SparkKafka010"
val topics = List("topicB")
val kafkaParams: Map[String, String] = MyKafkaUtils.getKafkaConsumerParams(groupID, "false")
val ds: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String, String](topics, kafkaParams)
)
//写数据 kafka Producer配置
val kafkaProducerParams: Map[String, String] = Map(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG -> "hadoop01:9092,hadoop02:9092,hadoop03:9092",
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG -> classOf[StringSerializer].getName,
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG -> classOf[StringSerializer].getName
)
//懒加载、序列化、 广播producer
import scala.collection.JavaConversions._
lazy val producer = new KafkaProducer[String, String](kafkaProducerParams) with Serializable
val bc = ssc.sparkContext.broadcast(producer)
ds.foreachRDD(rdd => {
//代表对数据进行处理
if(! rdd.isEmpty()){
rdd.foreachPartition(iter => {
val producer: KafkaProducer[String, String] with Serializable = bc.value
iter.foreach(msg => {
val record: ProducerRecord[String, String] = new ProducerRecord[String, String]("topicB", null, msg.value())
producer.send(record)
//需要发送结果的话 需要.get
//val value: Future[RecordMetadata] = producer.send(record)
val metadata: RecordMetadata = producer.send(record).get
metadata.topic()
println(" metadata.partition() = " + metadata.partition())
println(" metadata.offset() = " + metadata.offset())
println(" metadata.topic() = " + metadata.topic())
})
})
}
})
ssc.start()
ssc.awaitTermination()
}
}
1、数据输出到外部存储
2、应用的监控
3、Offset的管理
4、广播变量的更新
5、状态的管理(借助外部存储)
需要以下几个程序:
1、接口参数
2、offset相关问题
(偏移量自动/手动提交;设置/保存偏移量的位置;__consumer_offsets的观察)
3、管理offset(redis)
4、广播变量更新
5、监控
(05老版本,获取json串并解析;06新版本,加监听;WordCountWithMonitor 适用于批处理程序)
6、kafka 2 kafka
(01 普通程序;02 序列化producer、广播变量、懒加载)
工具类:
KafkaUtils
RedisUtils
KafkaProducer
下面笔记比较乱:
receiver :
旧版高阶API
旧版低阶API
zk 会单独起一个receiver去接收数据,造成很多问题,比如说OOM 只有这一个接收数据 可能存不下 挂掉 数据丢失 为了避免这个 使用WAL(实际上就是开启checkpoint放进hdfs 安全了 但造成性能下降)
Direct :
010 新版高阶API 已经没有receiver方式的代码了 只有Direct方式了 新版API只存到 __consumer_offset
kafka
旧版 API 0.8~0.9 有这两种方式
好多API都不向下兼容
存到zk或是_consumer_offset是系统管理的,而checkpoint是自定义管理的,
可以放到各种地方
1.mysql 2.redis 3.hdfs 3.hbase 4.zk 5.等等等
这只是可不可以
不是好不好
所以合适的是,hbase性能好,但有点大材小用, hbase也有事务,叫行事务
mysql 优点支持事务, ACID,稳定
checkpoint最大的问题是应用程序不能升级,系统不能保证升级
kafkaAPI 0.8版本 缺省的情况下,即默认,有两种方式为receiver和direct
receiver就是将offset存到zk
direct有两种方式,一类就是直接不存offset,一直都是消费最新的数据,不管偏移量
另一类如果0.8版本的direct要存offset就是要手动存,自己写代码维护,
那就任意了,可以存到hdfs rdbms redis等等,最简单的方式就是checkpoint,但它有很多问题,一般不使用
offset一般自定义存到redis
1.设置offset----》怎么读出来
2.写offset -----》 怎么写
dStream
消费数据时,先存计算结果再存offset 会造成重复消费数据的可能
先存offset再存计算结果可能造成丢失数据,一般不使用
然后进一步想,要么使用事务,要么使用幂等,才能比较好处理这个问题(尽可能exactly once)
不过这都不一定 看业务 看情况
DStream的输出算子 foreachRDD(rdd) driver
rdd 写到mysql 用 foreachPartition(item => ... 结果) executor
存在的地方都不一样 所以一般用事务不好使
BC broadcast ----> 在driver上,程序起来的时候只执行一次
DS.foreachRDD(rdd => )
kafka -------> SparkStreaming --------> kafka
rdd.foreachPartition(
item =>
通常在里面建立连接即executor,一般不在外面即driver建立连接(麻烦,需要连接序列化)
)
套路一般都是这样的,变得就是些外部存储建立连接的方式不同
首先看看在哪个分区
假设group-id为KafkaUtils10,则计算 ().hashcode % 50 = 48
那么使用命令(此命令只针对kafka0.11以上版本 低版本好像要去掉.group.)
1.加监听:
SparkStreaming,spark程序也能加监听
只适合spark 2.2.0以上版本 ssc. addSparkStreamingListener
2.老办法:解析Metrics的json串信息 好像是http请求之类的返回json串
而且一般请求的4040端口有一定可能被占用的,可能是4041、4042 所以还需要验证此端口的appID是不是同一个
项目中大都是把相似的功能放在一起