基于redis的控制sparkStreaming 对接kafka 精确一次消费数据的解决方案

9 篇文章 0 订阅

demo程序使用"KafkaUtils.createDirectStream"创建Kafka输入流,此API内部使用了Kafka客户端低阶API,不支持offset自动提交(提交到zookeeper)。

"KafkaUtils.createDirectStream"官方文档:

http://spark.apache.org/docs/2.2.0/streaming-kafka-0-8-integration.html

3.对策
方案一)通过zookeeper提供的API,自己编写代码,将offset提交到zookeeper;服务启动时,从zookeeper读取offset,并作为"KafkaUtils.createDirectStream"的输入参数
优点:可与基于zookeeper的监控系统融合,对消费情况进行监控

缺点:频繁的读写offset可能影响zookeeper集群性能,从而影响到Kafka集群的稳定性

方案二)自己编写代码维护offset,并将offset保存到MongoDB或者redis
优点:不影响zookeeper集群性能;可基于MongoDB或者redis自主实现消费情况的监控

缺点:无法与基于zookeeper的监控系统融合

4.代码示例
基于上述方案二,将offset保存到redis,并在服务重启时从redis获取offset,确保不会重复消费。

1)Scala操作redis的工具类

package xxx.demo.scala_test

 

import redis.clients.jedis.{Jedis, JedisPool, JedisPoolConfig, Protocol}

import org.apache.commons.pool2.impl.GenericObjectPoolConfig

import org.slf4j.LoggerFactory

import com.typesafe.scalalogging.slf4j.Logger

 

class RedisUtil extends Serializable {

  @transient private var pool: JedisPool = null

  @transient val logger = Logger(LoggerFactory.getLogger("cn.com.flaginfo.demo.scala_test.RedisUtil"))

 

  def makePool(redisHost: String, redisPort: Int,

               password: String, database: Int): Unit = {

    if (pool == null) {

      val poolConfig = new GenericObjectPoolConfig()

      pool = new JedisPool(poolConfig, redisHost, redisPort, Protocol.DEFAULT_TIMEOUT, password, database)

       

      val hook = new Thread {

        override def run = {

          pool.destroy()

          logger.debug("JedisPool destroyed by ShutdownHook")

        }

      }

      sys.addShutdownHook(hook.run)

    }

  }

 

  def jedisPool: JedisPool = {

    assert(pool != null)

    pool

  }

   

  def generateKafkaOffsetGroupIdTopicKey(groupId : String, topic : String) : String = {

    groupId + "/" + topic

  }

}

2)初始化redis工具

import kafka.serializer.{StringDecoder, DefaultDecoder}

import kafka.common.TopicAndPartition

import kafka.message.MessageAndMetadata

import org.apache.spark._

import org.apache.spark.streaming._

import org.apache.spark.streaming.StreamingContext._

import org.apache.spark.streaming.kafka._

import org.apache.spark.sql.SparkSession

import org.bson.Document

import com.mongodb.spark.config._

import com.mongodb.spark._

import com.mongodb._

import xxx.demo.model._

import redis.clients.jedis.{Jedis, JedisPool, JedisPoolConfig, Protocol}

import scala.collection.JavaConversions.{mapAsScalaMap}

import org.slf4j.LoggerFactory

import com.typesafe.scalalogging.slf4j.Logger

...

  var redisUtil = new RedisUtil()

  redisUtil.makePool(redisHost, redisPort, redisPassword, redisDatabase)

  var jedisPool = redisUtil.jedisPool

注意,不需要密码验证时,redisPassword必须设置为null,空字符串会报错。

3)从redis获取上次的offset

var kafkaOffsetKey = redisUtil.generateKafkaOffsetGroupIdTopicKey(groupId, kafkaTopicName)

var allOffset: java.util.Map[String, String] = jedisPool.getResource().hgetAll(kafkaOffsetKey)

val fromOffsets = scala.collection.mutable.Map[TopicAndPartition,Long]()

if( allOffset != null && !allOffset.isEmpty() ){

  // Jedis获取的Java Map转换为Scala Map

  var allOffsetScala : scala.collection.mutable.Map[String, String] = mapAsScalaMap[String, String](allOffset)

  for(offset <- allOffsetScala){

    // 将offset传入kafka参数。offset._1 : partition, offset._2 : offset

    fromOffsets += (TopicAndPartition(newsAnalysisTopic, offset._1.toInt) -> offset._2.toLong)

  }

  logger.debug( "fromOffsets : " + fromOffsets.toString() )

}

else{

  // 初次消费

  for( i <- 0 to (newsAnalysisTopicPartitionCount - 1) ){

    fromOffsets += (TopicAndPartition(newsAnalysisTopic, i) -> 0)

  }

  logger.debug( "fromOffsets : " + fromOffsets.toString() )

}

// mutable转换为imutable

var imutableFromOffsets = Map[TopicAndPartition,Long](

  fromOffsets.map(kv => (kv._1, kv._2)).toList: _*

)

4)定义消息过滤器:根据metadata取出需要的字段

val messageHandler: (MessageAndMetadata[String, String]) => (String,String, Long, Int) = (mmd: MessageAndMetadata[String, String]) =>

    (mmd.topic, mmd.message, mmd.offset, mmd.partition)

5)创建kafka输入流

val kafkaParam = Map[String, String](

  "bootstrap.servers" -> kafkaServer,

  "group.id" -> groupId,

  "client.id" -> clientId,

  "auto.offset.reset" -> "smallest",

  "enable.auto.commit" -> "false"

  )

var kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder, (String,String, Long, Int)](ssc, kafkaParam, imutableFromOffsets, messageHandler)

其中ssc为StreamingContext对象

6)业务逻辑代码中,将offset更新到redis

var kafkaOffsetKey = redisUtil.generateKafkaOffsetGroupIdTopicKey(groupId, kafkaTopicName)

// _._1 : topic name, _._2 : message body, _._3 : offset, _._4 : partition

kafkaStream.foreachRDD { rdd =>

  if( !rdd.isEmpty() ){  // 此处判断可防止offsetRanges.foreach循环意外执行

    var offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges

    // 处理数据

    rdd.foreach{ row =>

      logger.info("message : " + row + offsetRanges)

    }

    // 开启Redis事务

    var jedis = jedisPool.getResource()

    var jedisPipeline = jedis.pipelined()

    jedisPipeline.multi()

    // 更新offset

    offsetRanges.foreach { offsetRange =>

      logger.debug("partition : " + offsetRange.partition + " fromOffset:  " + offsetRange.fromOffset + " untilOffset: " + offsetRange.untilOffset)

      jedisPipeline.hset(kafkaOffsetKey, offsetRange.partition.toString(), offsetRange.untilOffset.toString())

    }

    jedisPipeline.exec() //提交事务

    jedisPipeline.sync //关闭pipeline

    jedis.close()

  }

}
————

原文链接:https://blog.csdn.net/xianyuxiaoqiang/article/details/86700299

### 回答1: 使用Spark Streaming结合Kafka可以实现精确消费一次,将结果保存到Redis的步骤如下: 1. 创建Spark Streaming上下文,并设置批处理时间间隔。 2. 创建Kafka数据源,并设置消费者组和topic。 3. 使用KafkaUtils.createDirectStream()方法创建DStream,该方法可以实现精确消费一次。 4. 对DStream进行数据处理,例如使用map()方法对每条数据进行处理。 5. 将处理后的数据保存到Redis中,可以使用Redis的Java客户端Jedis实现。 6. 启动Spark Streaming应用程序,开始消费Kafka数据并将结果保存到Redis中。 示例代码如下: ```java import org.apache.spark.SparkConf; import org.apache.spark.streaming.Duration; import org.apache.spark.streaming.api.java.JavaDStream; import org.apache.spark.streaming.api.java.JavaStreamingContext; import org.apache.spark.streaming.kafka010.ConsumerStrategies; import org.apache.spark.streaming.kafka010.KafkaUtils; import org.apache.spark.streaming.kafka010.LocationStrategies; import redis.clients.jedis.Jedis; import java.util.Collections; import java.util.HashMap; import java.util.Map; public class SparkStreamingKafkaRedis { public static void main(String[] args) throws InterruptedException { // 创建Spark Streaming上下文 SparkConf conf = new SparkConf().setAppName("SparkStreamingKafkaRedis"); JavaStreamingContext jssc = new JavaStreamingContext(conf, new Duration(5000)); // 创建Kafka数据源 String brokers = "localhost:9092"; String groupId = "test-group"; String topic = "test-topic"; Map<String, Object> kafkaParams = new HashMap<>(); kafkaParams.put("bootstrap.servers", brokers); kafkaParams.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); kafkaParams.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); kafkaParams.put("group.id", groupId); kafkaParams.put("auto.offset.reset", "latest"); kafkaParams.put("enable.auto.commit", false); // 创建DStream JavaDStream<String> lines = KafkaUtils.createDirectStream( jssc, LocationStrategies.PreferConsistent(), ConsumerStrategies.<String, String>Subscribe(Collections.singleton(topic), kafkaParams) ).map(record -> record.value()); // 处理数据并保存到Redis lines.foreachRDD(rdd -> { rdd.foreachPartition(partition -> { Jedis jedis = new Jedis("localhost", 6379); while (partition.hasNext()) { String data = partition.next(); // 处理数据 String result = data.toUpperCase(); // 保存到Redis jedis.set("result", result); } jedis.close(); }); }); // 启动Spark Streaming应用程序 jssc.start(); jssc.awaitTermination(); } } ``` ### 回答2: Spark Streaming是一个用于实时数据处理的框架,而Kafka则是一个高性能的分布式消息队列。结合这两个技术,可以快速搭建一个实时数据处理的系统,并将结果保存到Redis中。 首先需要在Spark Streaming应用程序中引入Kafka相关的依赖包,具体依赖包可以在Spark官网上找到。接着,需要创建一个Kafka DStream来读取消息队列中的数据。在读取数据之前,应当先通过Kafka的Offset管理功能来确定从何处开始读取数据。 在读取到数据之后,可以通过Spark Streaming提供的RDD转换算子来进行数据处理和分析操作。完成数据分析后,我们可以将结果保存到Redis中。为了确保数据精确性,需要保证每条消息只被消费一次,可以通过Kafka的Offset的提交和管理来实现这一点。 在使用Redis保存数据时,在Spark Streaming应用程序中可以引入Redis的Java客户端(Jedis),连接Redis集群。然后,使用Jedis提供的API来向Redis中写入数据。此外,在保存数据Redis之前,还需要对数据进行序列化处理。 总之,结合Spark StreamingKafkaRedis三个技术,可以实现一个高性能的实时数据处理和存储系统。同时,为了确保数据精确性和完整性,还需要在处理过程中注意一些细节问题,如Offset的管理、数据的序列化与反序列化等。 ### 回答3: Spark Streaming是基于Apache Spark构建的流式处理库,它可以处理高速数据流,并支持丰富的数据处理操作。Kafka则是一个分布式的、可扩展的、高吞吐量的发布-订阅消息系统,可用于构建实时数据流处理系统。而Redis则是一种流行的、内存中的键值数据库,支持高速读写操作和数据分析,尤其适用于缓存、消息队列和分布式锁等场景。将Spark StreamingKafkaRedis结合使用,可以实现精确消费一次并将结果保存到Redis的流处理流程。 具体实现步骤如下: 1. 创建Kafka输入流以接收数据 使用KafkaUtils.createDirectStream()方法创建Kafka输入流来接收数据。该方法需要参数:Kafka参数、Topic集合、kafka分区偏移量。 2. 通过处理接收到的数据进行清洗和转换 在创建Kafka输入流后,可以通过转换操作对接收到的数据进行清洗和转换。这里可以使用Spark Streaming提供的丰富的转换操作进行处理。 3. 将转换后的数据保存到Redis中 在清洗和转换数据完成后,我们将数据保存到Redis中。这里可以使用Redis的Java客户端jedis来操作Redis。创建jedis实例,然后使用jedis.set()方法将数据保存到Redis中。 4. 设置执行计划并启动流处理作业 配置好输入流、清洗和转换流程以及将结果保存到Redis中,最后要设置执行计划并启动流处理作业。执行计划将交给Spark Streaming处理,我们只需要启动作业即可。 实现流处理过程后,我们可以使用Spark Streaming自带的数据监控可视化工具监控流数据处理情况。同时还可以使用Redis的客户端工具检查Redis中的数据是否已经成功保存。 以上就是将Spark Streaming结合Kafka精确消费一次并将结果保存到Redis的的流处理过程。该流程可以应用于实时数据分析和处理场景,特别适用于高速数据流处理和数据保存操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值