Spark Streaming+Kafka+ES使用笔记

emm

非专业开发,一点笔记

Kafka

  1. 当Kafka内容特别大时会报错,此时设置下fetch.message.max.bytes为一个比较大的值就好。
val kafkaParams = Map[String, String]("metadata.broker.list" -> brokers,"fetch.message.max.bytes" -> "10485760" )
  1. 关于partitions
    KafkaDirectStream提供的分片数与Kafka的分片数相同
    如果运算量远远超过了重新分片的消耗,可以用repartition,否则还是增加同时运行的Jobs数量。

Spark Streaming

  1. 增加同时运行的任务数量

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时间下来了,内存也不容易超限了,一口气上五楼都不费劲了~具体原理不清楚,回头补。

  1. 有关cache
    数据有复用的位置一定要记得cache,否则会从头开始执行处理流程。
    被cache的类型需要能够进行序列化。

  2. 有关序列化与反序列化
    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

  1. 利用广播变量进行配置更新:

详细可以看这篇文章:
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]]
  }
}

ElasticSearch

  1. index名字只能小写,赤裸裸的教训啊= =
  2. 如果只使用单个ES账号,可以使用全局配置的es.nodes等参数,如果使用多个ES源的话可以写入多个配置MAP:
 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

猜你喜欢

转载自blog.csdn.net/fnmsd/article/details/88818143