SparkStreaming消费Kafka中的数据可以将偏移量保存在zookeeper、MySQL、redis中,前两种方式呢,我前面的博客都有演示,今天主要给大家分享第三种方式—采用直连方式,将偏移量保存在redis中。
不过需要注意的是,前两篇kafka的版本是0.8版本 而今天将偏移量保存在redis中是用的kafka0.10版本。一定要注意区别

创建数据库连接池

import redis.clients.jedis.{Jedis, JedisPool, JedisPoolConfig}
object JedisConnectionPool{
  val config = new JedisPoolConfig()
  //最大连接数,
  config.setMaxTotal(20)
  //最大空闲连接数
  config.setMaxIdle(10)
  //当调用borrow Object方法时,是否进行有效性检查 -->
  config.setTestOnBorrow(true)
  //10000代表超时时间(10秒)
  val pool = new JedisPool(config, "192.168.14.131", 6379, 10000, "123")
  def getConnection(): Jedis = {
    pool.getResource
  }
}

处理数据

import java.util
import test.JedisConnectionPool
import org.apache.kafka.common.TopicPartition

object JedisOffset {
  def apply(groupId: String) = {
    // 创建Map形式的Topic、partition、Offset
    var formdbOffset = Map[TopicPartition, Long]()
    //获取Jedis连接
    val jedis1 = JedisConnectionPool.getConnection()
    // 查询出Redis中的所有topic partition
    val topicPartitionOffset: util.Map[String, String] = jedis1.hgetAll(groupId)
    // 导入隐式转换
    import scala.collection.JavaConversions._
    // 将Redis中的Topic下的partition中的offset转换成List
    val topicPartitionOffsetlist: List[(String, String)] =
      topicPartitionOffset.toList
    // 循环处理所有的数据
    for (topicPL <- topicPartitionOffsetlist) {
      val split: Array[String] = topicPL._1.split("[-]")
      formdbOffset += (
        new TopicPartition(split(0), split(1).toInt) -> topicPL._2.toLong)
    }
    formdbOffset
  }
}

保存偏移量

import java.lang
import test.JedisConnectionPool
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.TopicPartition
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.log4j.{Level, Logger}
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}
import redis.clients.jedis.Jedis

object KafkaRedisStreaming {
  // 过滤日志
   Logger.getLogger("org").setLevel(Level.WARN)
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[2]").setAppName("xx")
      //每秒钟每个分区kafka拉取消息的速率
      .set("spark.streaming.kafka.maxRatePerPartition", "100")
      // 序列化
      .set("spark.serilizer", "org.apache.spark.serializer.KryoSerializer")
    val ssc = new StreamingContext(conf, Seconds(3))
    //启动一参数设置
    val groupId = "ts001"
    // kafka配置参数
    val kafkaParams = Map[String, Object](
      "bootstrap.servers" -> "192.168.14.128:9092,192.168.14.129:9092,192.168.14.130:9092",
      // kafka的key和value的解码方式
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> groupId,
      // 从头开始消费
      "auto.offset.reset" -> "earliest",
      "enable.auto.commit" -> (false: lang.Boolean)
    )
    val topics = Array("ts")
    //启动二参数设置  (获取Redis中的kafka偏移量)
    var formdbOffset: Map[TopicPartition, Long] = JedisOffset(groupId)
    //拉取kafka数据
    val stream: InputDStream[ConsumerRecord[String, String]] =
    // 首先判断一下 我们要消费的kafka数据是否是第一次消费,之前有没有消费过
      if (formdbOffset.size == 0) {
      KafkaUtils.createDirectStream[String, String](
        ssc,
        /**
          * 本地策略
          * 一般使用LocationStrategies的PreferConsistent方法。
        它会将分区数据尽可能均匀地分配给所有可用的Executor。
        题外话:本地化策略看到这里就行了,下面讲的是一些特殊情况。
        情况一
        如果你的Executor和kafka broker在同一台机器上,可以用PreferBrokers,
        这将优先将分区调度到kafka分区leader所在的主机上。
        题外话:废话,Executor是随机分布的,我怎么知道是不是在同一台服务器上?
        除非是单机版的are you明白?
        情况二
        分区之间的负荷有明显的倾斜,可以用PreferFixed。
        这个允许你指定一个明确的分区到主机的映射(没有指定的分区将会使用连续的地址)。
        题外话:就是出现了数据倾斜了呗
          */
        LocationStrategies.PreferConsistent,
        /**
          * 消费者策略
          * ConsumerStrategies.Subscribe,能够订阅一个固定的topics的集合。
          * SubscribePattern 能够
          * 根据你感兴趣的topics进行匹配。需要注意的是,不同于 0.8的集成,
          * 使用subscribe or SubscribePattern 可以支持在运行的streaming中增加分区。
          * 而Assign不可以动态的改变消费的分区模式,那么一般都会在开始读取固定的数据时候才能使用
          */
        ConsumerStrategies.Subscribe[String, String](topics, kafkaParams)
      )
    } else {
        // 第一次消费数据,没有任何的消费信息数据
      KafkaUtils.createDirectStream(
        ssc,
        LocationStrategies.PreferConsistent,
        ConsumerStrategies.Assign[String, String](
          formdbOffset.keys, kafkaParams, formdbOffset)
      )
    }
    //数据偏移量处理。
    stream.foreachRDD({
      rdd =>
        // 获得偏移量对象数组
        val offsetRange: Array[OffsetRange] =
          rdd.asInstanceOf[HasOffsetRanges].offsetRanges
        //逻辑处理
        rdd.map(_.value())
          .map((_, 1))
          .reduceByKey(_ + _).foreach(println)
        // 偏移量存入redis
        val jedis: Jedis = JedisConnectionPool.getConnection()
        for (or <- offsetRange) {
          jedis.hset(groupId, or.topic + "-" + or.partition, or.untilOffset.toString)
        }
        jedis.close()
    })
    // 启动Streaming程序
    ssc.start()
    ssc.awaitTermination()
  }
}