非专业开发,一点笔记
val kafkaParams = Map[String, String]("metadata.broker.list" -> brokers,"fetch.message.max.bytes" -> "10485760" )
SparkStreaming默认只启动一个Job,所以使用核心再多如果任务数量不够的话核心也不能充分利用。
为了提高任务个数需要使用设置spark.streaming.concurrentJobs参数:
spark-submit --conf spark.streaming.concurrentJobs=8 ....
实际上一个Job会分成多个Tasks,每个CPU核心执行一个Task,Task执行完成则Core被释放,也就是说8个partitions的Streaming,使用32个核心并不是只能执行4个Jobs,可以根据Spark WebUI的executor页面核心的使用量,适当的增大concurrentJobs或减少核心使用量。
像我这就是利用率比较低,并且Streaming任务一直跟得上,就可以适当降低Cores的数量。
2. GC优化
使用CMS内存收集器:
spark-submit --conf "spark.executor.extraJavaOptions=-XX:+UseConcMarkSweepGC"
自从使用了这个收集器,GC时间下来了,内存也不容易超限了,一口气上五楼都不费劲了~具体原理不清楚,回头补。
有关cache
数据有复用的位置一定要记得cache,否则会从头开始执行处理流程。
被cache的类型需要能够进行序列化。
有关序列化与反序列化
Driver会将Task的内容打包序列化发给Executor,所以需要Task中所有被引用的类型都可以序列化。
如果类型不可序列化则会报object not serializable的错误,此时需要自己实现序列化与反序列化方法,一般只需要实现反序列化方法(readObject)
scala需要加上serializable注解,java实现serializable接口。
private def readObject(in: ObjectInputStream):Unit = {
//调用默认的ReadObject函数
in.defaultReadObject()
//重新初始化一些无法被序列化的内容
this.init(this.config_map)
}
对于无法序列化的属性(Mysql连接、Redis连接等等等)需要在属性前加上transient修饰符,表示在序列化时忽略,然后在readObject中再进行构造。
scala还有一个lazy修饰符,表明使用时再进行构建,所以也可以使用lazy+transient修饰符,让其在使用时重新进行构建。
@transient lazy val logger:Logger = LogManager.getLogger(this.getClass.getName)
如下例子进行参考,一个包装的Kafka的类:
class KafkaSink[K, V](createProducer: () => KafkaProducer[K, V]) extends Serializable {
/* This is the key idea that allows us to work around running into
NotSerializableExceptions. */
lazy val producer = createProducer()
def send(topic: String, key: K, value: V): Future[RecordMetadata] =
producer.send(new ProducerRecord[K, V](topic, key, value))
def send(topic: String, value: V): Future[RecordMetadata] =
producer.send(new ProducerRecord[K, V](topic, value))
}
object KafkaSink {
import scala.collection.JavaConversions._
def apply[K, V](config: Map[String, Object]): KafkaSink[K, V] = {
val createProducerFunc = () => {
val producer = new KafkaProducer[K, V](config)
sys.addShutdownHook {
// Ensure that, on executor JVM shutdown, the Kafka producer sends
// any buffered messages to Kafka before shutting down.
producer.close()
}
producer
}
new KafkaSink(createProducerFunc)
}
def apply[K, V](config: java.util.Properties): KafkaSink[K, V] = apply(config.toMap)
}
5.广播变量
Task过程中使用的变量每次都会序列化传输一次,如果想验证可以使用上面的方法重写readObject打印一些调试信息进行记录。
而一些长时间不变并且比较大、复杂的内容可以使用广播变量进行保存,保证每个executor只存在一份该变量。
比如 Redis连接、MySQL连接、规则配置什么的就可以使用广播变量。
广播变量包裹的类同样需要能够序列化。
广播变量为只读变量。
详细广播变量的使用可以看如下文章:
https://www.jianshu.com/p/3bd18acd2f7f
详细可以看这篇文章:
https://www.cnblogs.com/liuliliuli2017/p/6782687.html
某个大佬写的包装类:
// This wrapper lets us update brodcast variables within DStreams' foreachRDD
// without running into serialization issues
case class BroadcastWrapper[T: ClassTag](
@transient private val ssc: StreamingContext,
@transient private val _v: T) {
@transient private var v = ssc.sparkContext.broadcast(_v)
def update(newValue: T, blocking: Boolean = false): Unit = {
// 删除RDD是否需要锁定
v.unpersist(blocking)
v = ssc.sparkContext.broadcast(newValue)
}
def value: T = v.value
private def writeObject(out: ObjectOutputStream): Unit = {
out.writeObject(v)
}
private def readObject(in: ObjectInputStream): Unit = {
v = in.readObject().asInstanceOf[Broadcast[T]]
}
}
var esin_setting = Map[String,String]("es.nodes"->"es1"
,"es.port"->"7001"
)
var esout_setting = Map[String,String]("es.nodes"->"es2"
,"es.port"->"7001"
,"es.scroll.size"->"5000")
val rdd = sc.esRDD("indexin", query,esin_setting)
rdd.saveToEs("esout/type1",esout_setting)
3.更多复杂的配置项,可以参考ElasticSearch的官网配置文档:
https://www.elastic.co/guide/en/elasticsearch/hadoop/current/configuration.html