1.两种方式管理偏移量并将偏移量写入redis

(1)第一种:rdd的形式

  一般是使用这种直连的方式,但其缺点是没法调用一些更加高级的api,如窗口操作。如果想更加精确的控制偏移量,就使用这种方式

代码如下

KafkaStreamingWordCountManageOffsetRddApi

  1. package com._51doit.spark13
  2.  
  3. import com._51doit.utils.JedisConnectionPool
  4. import org.apache.kafka.clients.consumer.ConsumerRecord
  5. import org.apache.spark.SparkConf
  6. import org.apache.spark.rdd.RDD
  7. import org.apache.spark.streaming.dstream.InputDStream
  8. import org.apache.spark.streaming.kafka010.{CanCommitOffsets, ConsumerStrategies, HasOffsetRanges, KafkaUtils, LocationStrategies, OffsetRange}
  9. import org.apache.spark.streaming.{Milliseconds, StreamingContext}
  10.  
  11. object KafkaStreamingWordCountManageOffsetRddApi {
  12.  
  13. def main(args: Array[String]): Unit = {
  14.  
  15. val conf = new SparkConf()
  16. .setAppName(this.getClass.getSimpleName)
  17. .setMaster("local[*]")
  18. //创建StreamingContext,并指定批次生成的时间
  19. val ssc = new StreamingContext(conf, Milliseconds(5000))
  20. //设置日志级别
  21. ssc.sparkContext.setLogLevel("WARN")
  22. //SparkStreaming 跟kafka进行整合
  23. //1.导入跟Kafka整合的依赖
  24. //2.跟kafka整合,创建直连的DStream【使用底层的消费API,效率更高】
  25. val topics = Array("test11")
  26. //SparkSteaming跟kafka整合的参数
  27. //kafka的消费者默认的参数就是每5秒钟自动提交偏移量到Kafka特殊的topic中: __consumer_offsets
  28. val kafkaParams = Map[String, Object](
  29. "bootstrap.servers" -> "feng05:9092,feng06:9092,feng07:9092",
  30. "key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
  31. "value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
  32. "group.id" -> "g013",
  33. "auto.offset.reset" -> "earliest" //如果没有记录偏移量,第一次从最开始读,有偏移量,接着偏移量读
  34. , "enable.auto.commit" -> (false: java.lang.Boolean) //消费者不自动提交偏移量
  35. )
  36. //跟Kafka进行整合,需要引入跟Kafka整合的依赖
  37. //createDirectStream更加高效,使用的是Kafka底层的消费API,消费者直接连接到Kafka的Leader分区进行消费
  38. //直连方式,RDD的分区数量和Kafka的分区数量是一一对应的【数目一样】
  39. val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
  40. ssc,
  41. LocationStrategies.PreferConsistent, //调度task到Kafka所在的节点
  42. ConsumerStrategies.Subscribe[String, String](topics, kafkaParams) //指定订阅Topic的规则
  43. )
  44. kafkaDStream.foreachRDD(rdd => {
  45. //println(rdd + "-> partitions " + rdd.partitions.length)
  46. //判断当前批次的RDD是否有数据
  47. if (!rdd.isEmpty()) {
  48. //将RDD转换成KafkaRDD,获取KafkaRDD每一个分区的偏移量【在Driver端】
  49. val offsetRanges: Array[OffsetRange] = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
  50. // //循环遍历每个分区的偏移量
  51. // for (range <- offsetRanges) {
  52. // println(s"topic: ${range.topic}, partition: ${range.partition}, fromOffset : ${range.fromOffset} -> utilOffset: ${range.untilOffset}")
  53. // }
  54. //将获取到的偏移量写入到相应的存储系统呢【Kafka、Redis、MySQL】
  55. //将偏移量写入到Kafka
  56. //对RDD进行处理
  57. //Transformation 开始
  58. val keys = rdd.map(_.key())
  59. println(keys.collect().toBuffer)
  60. val lines: RDD[String] = rdd.map(_.value())
  61. println(lines.collect().toBuffer)
  62. val words: RDD[String] = lines.flatMap(_.split(" "))
  63. val wordAndOne: RDD[(String, Int)] = words.map((_, 1))
  64. val reduced: RDD[(String, Int)] = wordAndOne.reduceByKey(_ + _)
  65. //Transformation 结束
  66. //触发Action
  67. reduced.foreachPartition(it => {
  68. //在Executor端获取Redis连接
  69. val jedis = JedisConnectionPool.getConnection
  70. jedis.select(3)
  71. //将分区对应的结果写入到Redis
  72. it.foreach(t => {
  73. jedis.hincrBy("wc_adv", t._1, t._2)
  74. })
  75. //将连接还回连接池
  76. jedis.close()
  77. })
  78. //再更新这个批次每个分区的偏移量
  79. //异步提交偏移量,将偏移量写入到Kafka特殊的topic中了
  80. kafkaDStream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
  81. }
  82. })
  83. ssc.start()
  84. ssc.awaitTermination()
  85. }
  86. }

 (2)  第二种:DStream的形式

  功能更加丰富,可以使用DStream的api,但最终还是要调用foreachrdd,将数据写入redis

代码如下

KafkaStreamingWordCountManageOffsetDstreamApi

  1. package com._51doit.spark13
  2.  
  3. import com._51doit.utils.JedisConnectionPool
  4. import org.apache.kafka.clients.consumer.ConsumerRecord
  5. import org.apache.spark.SparkConf
  6. import org.apache.spark.streaming.dstream.{DStream, InputDStream}
  7. import org.apache.spark.streaming.kafka010.{CanCommitOffsets, ConsumerStrategies, HasOffsetRanges, KafkaUtils, LocationStrategies, OffsetRange}
  8. import org.apache.spark.streaming.{Milliseconds, StreamingContext}
  9. import redis.clients.jedis.Jedis
  10.  
  11. object KafkaStreamingWordCountManageOffsetDstreamApi {
  12. def main(args: Array[String]): Unit = {
  13. val conf: SparkConf = new SparkConf()
  14. .setAppName(this.getClass.getSimpleName)
  15. .setMaster("local[*]")
  16. // 创建StreamingContext,并指定批次生成的时间
  17. val ssc: StreamingContext = new StreamingContext(conf, Milliseconds(5000))
  18. // 设置日志的级别
  19. ssc.sparkContext.setLogLevel("WARN")
  20. // kafka整合SparkStreaming
  21. // 1.导入跟kafka整合的依赖 2. 跟kafka整合,创建直连的Dstream[使用底层的消费API,消费更高]
  22. val topics = Array("test11")
  23. // SparkStreaming跟kafka整合的参数
  24. //kafka的消费者默认的参数就是每5秒钟自动提交偏移量到Kafka特殊的topic中: __consumer_offsets
  25. val kafkaParams = Map[String, Object](
  26. "bootstrap.servers" -> "feng05:9092,feng06:9092,feng07:9092",
  27. "key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
  28. "value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
  29. "group.id" -> "g014",
  30. "auto.offset.reset" -> "earliest" //如果没有记录偏移量,第一次从最开始读,有偏移量,接着偏移量读
  31. , "enable.auto.commit" -> (false: java.lang.Boolean) //消费者不自动提交偏移量
  32. )
  33. //直连方式,RDD的分区数量和Kafka的分区数量是一一对应的【数目一样】
  34. val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String,String](
  35. ssc,
  36. LocationStrategies.PreferConsistent, // 调度task到kafka所在的节点
  37. ConsumerStrategies Subscribe[String, String](topics, kafkaParams) //消费者策略,指定订阅topic的规则
  38. )
  39. var offsetRanges: Array[OffsetRange] = null
  40. // 调用transform,取出kafkaRDD并获取每一个分区对应的偏移量
  41. val transformDS: DStream[ConsumerRecord[String, String]] = kafkaDStream.transform(rdd => {
  42. // 在该函数中,获取偏移量
  43. offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
  44. rdd
  45. })
  46. // 调用DStream的API,其有一些RDD没有的API,如upsteateByKey, Window相关的操作
  47. val reducedDStream: DStream[(String, Int)] = transformDS.map(_.value()).flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
  48. // 将数据写入redis,此时还是需要使用foreachRDD
  49. reducedDStream.foreachRDD(rdd => {
  50. if(!rdd.isEmpty()){
  51. rdd.foreachPartition(it =>{
  52. // 在Executor端获取Redis连接 c
  53. val jedis: Jedis = JedisConnectionPool.getConnection
  54. jedis.select(4)
  55. it.foreach(t=>{
  56. jedis.hincrBy("wc_adv2",t._1, t._2)
  57. })
  58. jedis.close()
  59. })
  60. // 将计算完的批次对应的偏移量提交(在driver端移交偏移量)
  61. kafkaDStream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
  62. }
  63. })
  64. ssc.start()
  65. ssc.awaitTermination()
  66. }
  67. }

以上两种方式都无法保证数据只读取处理一次(即exactly once)。因为若是提交偏移量时出现网络问题,导致偏移量没有进行更新,但是数据却成功统计到redis中,这样就会反复读取某段数据进行统计

解决方法:使用事务,即数据的统计与偏移量的写入要同时成功,否则就回滚

2. MySQL事务的测试

MySQLTransactionTest

  1. package cn._51doit.spark.day13
  2.  
  3. import java.sql.{Connection, DriverManager, PreparedStatement}
  4.  
  5. /**
  6. * mysql的哪一种存储引擎支持事物呢?
  7. * InnoDB
  8. */
  9. object MySQLTransactionTest {
  10.  
  11. def main(args: Array[String]): Unit = {
  12.  
  13. var connection: Connection = null
  14. var ps1: PreparedStatement = null
  15. var ps2: PreparedStatement = null
  16.  
  17. try {
  18.  
  19. //默认MySQL自动提交事物
  20. connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bigdata", "root", "123456")
  21. //不要自动提交事物
  22. connection.setAutoCommit(false)
  23.  
  24. ps1 = connection.prepareStatement("INSERT INTO t_user1 (name,age) VALUES (?, ?)")
  25. //设置参数
  26. ps1.setString(1, "AAA")
  27. ps1.setInt(2, 18)
  28.  
  29. //执行
  30. ps1.executeUpdate()
  31.  
  32. val i = 1 / 0
  33.  
  34. //往另外一个表写入数据
  35. ps2 = connection.prepareStatement("INSERT INTO t_user2 (name,age) VALUES (?, ?)")
  36. //设置参数
  37. ps2.setString(1, "BBB")
  38. ps2.setInt(2, 28)
  39. //执行
  40. ps2.executeUpdate()
  41.  
  42. //多个对数据库操作成功了,在提交事物
  43. connection.commit()
  44.  
  45. } catch {
  46. case e: Exception => {
  47. e.printStackTrace()
  48. //回顾事物
  49. connection.rollback()
  50. }
  51. } finally {
  52.  
  53. if(ps2 != null) {
  54. ps2.close()
  55. }
  56. if(ps1 != null) {
  57. ps1.close()
  58. }
  59. if(connection != null) {
  60. connection.close()
  61. }
  62. }
  63. }
  64. }

注意:mysql只有InnoDB引擎支持事务,其它引擎都不支持

3.利用MySQL事务实现数据统计的ExactlyOnce

思路:

从Kafka读取数据,实现ExactlyOnce,偏移量保存到MySQL中

  • 1. 将聚合好的数据,收集到driver端(若不收集到driver端,count和偏移量就无法写入一个事务,count数据实在executor中得到,而事务实在driver端得到)
  • 2  然后将计算好的数据和偏移量在一个事物中同时保存到MySQL中
  • 3 成功了提交事务
  • 4 失败了让这个任务重启

代码

(1)ExactlyWordCountOffsetStoreInMySQL(没有查询mysql中的历史偏移量)

  1. package com._51doit.spark13
  2.  
  3. import java.lang
  4. import java.sql.{Connection, DriverManager, PreparedStatement}
  5.  
  6. import org.apache.kafka.clients.consumer.ConsumerRecord
  7. import org.apache.spark.SparkConf
  8. import org.apache.spark.rdd.RDD
  9. import org.apache.spark.streaming.dstream.InputDStream
  10. import org.apache.spark.streaming.kafka010.{ConsumerStrategies, HasOffsetRanges, KafkaUtils, LocationStrategies, OffsetRange}
  11. import org.apache.spark.streaming.{Milliseconds, StreamingContext}
  12.  
  13. object ExactlyWordCountOffsetStoreInMySQL {
  14. def main(args: Array[String]): Unit = {
  15.  
  16. //true a1 g1 ta,tb
  17. val Array(isLocal, appName, groupId, allTopics) = args
  18.  
  19. val conf: SparkConf = new SparkConf()
  20. .setAppName(appName)
  21. if (isLocal.toBoolean){
  22. conf.setMaster("local[*]")
  23. }
  24. //创建StreamingContext,并指定批次生成的时间
  25. val ssc = new StreamingContext(conf, Milliseconds(5000))
  26. // 设置日志级别
  27. ssc.sparkContext.setLogLevel("WARN")
  28.  
  29. // SparkStreaming跟kafka进行整合
  30. // 1.导入跟kafka整合的依赖 2. 跟kafka整合,创建直连的DStream
  31. // SparkStreaming跟kafka整合的参数
  32. // kafka的消费者默认的参数就是5秒钟自动提交偏移量到kafka特殊的topic(__consumer_offsets)中
  33. val kafkaParams: Map[String, Object] = Map[String, Object](
  34. "bootstrap.servers" -> "feng05:9092,feng06:9092,feng07:9092",
  35. "key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
  36. "value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
  37. "group.id" -> groupId,
  38. "auto.offset.reset" -> "earliest" //如果没有记录偏移量,第一次从最开始读,有偏移量,接着偏移量读
  39. , "enable.auto.commit" -> (false: lang.Boolean) //消费者不自动提交偏移量
  40. )
  41. // 需要订阅的topic
  42. val topics = allTopics.split(",")
  43.  
  44. // 跟kafka进行整合,需要引入跟kafka整合的依赖
  45. //createDirectStream更加高效,使用的是Kafka底层的消费API,消费者直接连接到Kafka的Leader分区进行消费
  46. //直连方式,RDD的分区数量和Kafka的分区数量是一一对应的【数目一样】
  47. val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
  48. ssc,
  49. LocationStrategies.PreferConsistent, //调度task到Kafka所在的节点
  50. ConsumerStrategies.Subscribe[String, String](topics, kafkaParams) //指定订阅Topic的规则
  51. )
  52.  
  53. kafkaDStream.foreachRDD(rdd => {
  54. // 判断当前批次的rdd是否有数据
  55. if(!rdd.isEmpty()){
  56. val offsetRanges: Array[OffsetRange] = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
  57. println("偏移量长度"+offsetRanges.length)
  58. println(offsetRanges.toBuffer)
  59. // 进行wc计算
  60. val words = rdd.flatMap(_.value().split(" "))
  61. val wordAndOne: RDD[(String, Int)] = words.map((_, 1))
  62. val reduced: RDD[(String, Int)] = wordAndOne.reduceByKey(_ + _)
  63. //将计算好的结果收集到Driver端再写入到MySQL中【保证数据和偏移量写入在一个事物中】
  64. //触发Action,将数据收集到Driver段
  65. val res: Array[(String, Int)] = reduced.collect()
  66. println("长度"+res.length)
  67. println(res.toBuffer)
  68. var connection:Connection = null
  69. var ps1: PreparedStatement = null
  70. var ps2: PreparedStatement = null
  71. // 利用事务往MYSQL存相关数据
  72. try {
  73. connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_user", "root", "feng")
  74. // 设置不自动提交事务
  75. connection.setAutoCommit(false)
  76. // 往mysql中写入word以及相应的count
  77. val ps1: PreparedStatement = connection.prepareStatement("INSERT INTO t_wordcount (word, count) VALUES(?, ?) ON DUPLICATE KEY UPDATE count=count+?")
  78. for (tp <- res){
  79. ps1.setString(1,tp._1)
  80. ps1.setInt(2,tp._2)
  81. ps1.setInt(3,tp._2)
  82. ps1.executeUpdate() //没有提交事务,不会将数据真正写入MYSQL
  83. }
  84. // 往mysql中写入偏移量
  85. val ps2: PreparedStatement = connection.prepareStatement("INSERT INTO t_kafka_offset (app_gid, topic_partition, offset) VALUES(?, ?, ?) ON DUPLICATE KEY UPDATE offset=?")
  86. for (offsetRange <- offsetRanges){
  87. //topic名称
  88. val topic: String = offsetRange.topic
  89. // topic分区编号
  90. val partition: Int = offsetRange.partition
  91. // 获取结束的偏移量
  92. val utilOffset: Long = offsetRange.untilOffset
  93. ps2.setString(1, appName+"_"+groupId)
  94. ps2.setString(2,topic+"_"+partition)
  95. ps2.setLong(3,utilOffset)
  96. ps2.setLong(4,utilOffset)
  97. ps2.executeUpdate()
  98. }
  99. // 提交事务
  100. connection.commit()
  101. } catch {
  102. case e:Exception => {
  103. // 回滚事务
  104. connection.rollback()
  105. // 让人物停掉
  106. ssc.stop()
  107. }
  108. } finally{
  109. if(ps2 != null){
  110. ps2.close()
  111. }
  112. if(ps1 != null){
  113. ps1.close()
  114. }
  115. if(connection != null){
  116. connection.close()
  117. }
  118. }
  119. }
  120. })
  121. ssc.start()
  122. ssc.awaitTermination()
  123. }
  124. }

此处自己的代码出现了如下问题(暂时没有解决)

当再次消费生产者产生的数据时,统计出现如上问题(暂时没解决),

  1. 2)若是不查询mysql中的偏移量,可能存在重复读取kafka中的数据,比如mysql挂掉时,代码继续消费生产者产生的数据,但数据没有成功写入mysql,当重启mysql并相应重启代码时,会发现kafka中的所有数据会被重新读取一遍,原因:

解决办法,在消费kafka中的数据时,先读取mysql中的偏移量数据,这样消费者从kafka中消费数据时就会从指定的偏移量开始消费,具体代码如下

  1. ExactlyWordCountOffsetStoreInMySQL(考虑了mysql已经存储的历史记录)

  1. package cn._51doit.spark.day13
  2.  
  3. import java.sql.{Connection, DriverManager, PreparedStatement}
  4.  
  5. import cn._51doit.spark.utils.OffsetUtils
  6. import org.apache.kafka.clients.consumer.ConsumerRecord
  7. import org.apache.kafka.common.TopicPartition
  8. import org.apache.spark.SparkConf
  9. import org.apache.spark.rdd.RDD
  10. import org.apache.spark.streaming.dstream.InputDStream
  11. import org.apache.spark.streaming.kafka010._
  12. import org.apache.spark.streaming.{Milliseconds, StreamingContext}
  13.  
  14. /**
  15. * 从Kafka读取数据,实现ExactlyOnce,偏移量保存到MySQL中
  16. * 1.将聚合好的数据,收集到Driver端,
  17. * 2.然后建计算好的数据和偏移量在一个事物中同时保存到MySQL中
  18. * 3.成功了提交事物
  19. * 4.失败了让这个任务重启
  20. *
  21. * MySQL数据库中有两张表:保存计算好的结果、保存偏移量
  22. */
  23. object ExactlyOnceWordCountOffsetStoreInMySQL {
  24.  
  25. def main(args: Array[String]): Unit = {
  26.  
  27. //true a1 g1 ta,tb
  28. val Array(isLocal, appName, groupId, allTopics) = args
  29.  
  30. val conf = new SparkConf()
  31. .setAppName(appName)
  32.  
  33. if (isLocal.toBoolean) {
  34. conf.setMaster("local[*]")
  35. }
  36.  
  37. //创建StreamingContext,并指定批次生成的时间
  38. val ssc = new StreamingContext(conf, Milliseconds(5000))
  39. //设置日志级别
  40. ssc.sparkContext.setLogLevel("WARN")
  41.  
  42. //SparkStreaming 跟kafka进行整合
  43. //1.导入跟Kafka整合的依赖
  44. //2.跟kafka整合,创建直连的DStream【使用底层的消费API,效率更高】
  45.  
  46. val topics = allTopics.split(",")
  47.  
  48. //SparkSteaming跟kafka整合的参数
  49. //kafka的消费者默认的参数就是每5秒钟自动提交偏移量到Kafka特殊的topic中: __consumer_offsets
  50. val kafkaParams = Map[String, Object](
  51. "bootstrap.servers" -> "node-1.51doit.cn:9092,node-2.51doit.cn:9092,node-3.51doit.cn:9092",
  52. "key.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
  53. "value.deserializer" -> "org.apache.kafka.common.serialization.StringDeserializer",
  54. "group.id" -> groupId,
  55. "auto.offset.reset" -> "earliest" //如果没有记录偏移量,第一次从最开始读,有偏移量,接着偏移量读
  56. , "enable.auto.commit" -> (false: java.lang.Boolean) //消费者不自动提交偏移量
  57. )
  58.  
  59. //在创建KafkaDStream之前要先读取MySQL数据库,查询历史偏移量,没有就从头读,有就接着读
  60. //offsets: collection.Map[TopicPartition, Long]
  61. val offsets: Map[TopicPartition, Long] = OffsetUtils.queryHistoryOffsetFromMySQL(appName, groupId)
  62.  
  63. //跟Kafka进行整合,需要引入跟Kafka整合的依赖
  64. //createDirectStream更加高效,使用的是Kafka底层的消费API,消费者直接连接到Kafka的Leader分区进行消费
  65. //直连方式,RDD的分区数量和Kafka的分区数量是一一对应的【数目一样】
  66. val kafkaDStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
  67. ssc,
  68. LocationStrategies.PreferConsistent, //调度task到Kafka所在的节点
  69. ConsumerStrategies.Subscribe[String, String](topics, kafkaParams, offsets) //指定订阅Topic的规则
  70. )
  71.  
  72. kafkaDStream.foreachRDD(rdd => {
  73.  
  74. //判断当前批次的RDD是否有数据
  75. if (!rdd.isEmpty()) {
  76.  
  77. //获取RDD所有分区的偏移量
  78. val offsetRanges: Array[OffsetRange] = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
  79.  
  80. //实现WordCount业务逻辑
  81. val words: RDD[String] = rdd.flatMap(_.value().split(" "))
  82. val wordsAndOne: RDD[(String, Int)] = words.map((_, 1))
  83. val reduced: RDD[(String, Int)] = wordsAndOne.reduceByKey(_ + _)
  84. //将计算好的结果收集到Driver端再写入到MySQL中【保证数据和偏移量写入在一个事物中】
  85. //触发Action,将数据收集到Driver段
  86. val res: Array[(String, Int)] = reduced.collect()
  87.  
  88. //创建一个MySQL的连接【在Driver端创建】
  89. //默认MySQL自动提交事物
  90.  
  91. var connection: Connection = null
  92. var ps1: PreparedStatement = null
  93. var ps2: PreparedStatement = null
  94. try {
  95. connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bigdata", "root", "123456")
  96. //不要自动提交事物
  97. connection.setAutoCommit(false)
  98.  
  99. ps1 = connection.prepareStatement("INSERT INTO t_wordcount (word, counts) VALUES (?, ?) ON DUPLICATE KEY UPDATE counts = counts + ?")
  100. //将计算好的WordCount结果写入数据库表中,但是没有提交事物
  101. for (tp <- res) {
  102. ps1.setString(1, tp._1)
  103. ps1.setLong(2, tp._2)
  104. ps1.setLong(3, tp._2)
  105. ps1.executeUpdate() //没有提交事物,不会讲数据真正写入到MySQL
  106. }
  107.  
  108. //(app1_g001, wc_0) -> 1000
  109. ps2 = connection.prepareStatement("INSERT INTO t_kafka_offset (app_gid, topic_partition, offset) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE offset = ?")
  110. //将偏移量写入到MySQL的另外一个表中,也没有提交事物
  111. for (offsetRange <- offsetRanges) {
  112. //topic名称
  113. val topic = offsetRange.topic
  114. //topic分区编号
  115. val partition = offsetRange.partition
  116. //获取结束偏移量
  117. val untilOffset = offsetRange.untilOffset
  118. //将结果写入MySQL
  119. ps2.setString(1, appName + "_" + groupId)
  120. ps2.setString(2, topic + "_" + partition)
  121. ps2.setLong(3, untilOffset)
  122. ps2.setLong(4, untilOffset)
  123. ps2.executeUpdate()
  124. }
  125.  
  126. //提交事物
  127. connection.commit()
  128.  
  129. } catch {
  130. case e: Exception => {
  131. //回滚事物
  132. connection.rollback()
  133. //让任务停掉
  134. ssc.stop()
  135. }
  136. } finally {
  137. if(ps2 != null) {
  138. ps2.close()
  139. }
  140. if(ps1 != null) {
  141. ps1.close()
  142. }
  143. if(connection != null) {
  144. connection.close()
  145. }
  146. }
  147. }
  148. })
  149.  
  150. ssc.start()
  151.  
  152. ssc.awaitTermination()
  153.  
  154. }
  155. }

 OffsetUtils类(封装了查询偏移量的方法:queryHistoryOffsetFromMysql)

  1. package com._51doit.utils
  2.  
  3. import java.sql.{Connection, DriverManager, ResultSet}
  4.  
  5. import org.apache.kafka.common.TopicPartition
  6. import org.apache.spark.streaming.kafka010.OffsetRange
  7.  
  8. import scala.collection.mutable
  9.  
  10. object OffsetUtils {
  11.  
  12. def queryHistoryOffsetFromMySQL(appName: String, groupId: String): Map[TopicPartition, Long] = {
  13.  
  14. val offsets = new mutable.HashMap[TopicPartition, Long]()
  15.  
  16. val connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bigdata", "root", "123456")
  17.  
  18. val ps = connection.prepareStatement("SELECT topic_partition, offset FROM t_kafka_offset WHERE" +
  19. " app_gid = ?")
  20.  
  21. ps.setString(1, appName + "_" +groupId)
  22.  
  23. val rs: ResultSet = ps.executeQuery()
  24.  
  25. while (rs.next()) {
  26. val topicAndPartition = rs.getString(1)
  27. val offset = rs.getLong(2)
  28. val fields = topicAndPartition.split("_")
  29. val topic = fields(0)
  30. val partition = fields(1).toInt
  31. val topicPartition = new TopicPartition(topic, partition)
  32. //将构建好的TopicPartition放入map中
  33. offsets(topicPartition) = offset
  34. }
  35. offsets.toMap
  36. }
  37.  
  38. /**
  39. * 将偏移量更新到MySQL中
  40. * @param offsetRanges
  41. * @param connection
  42. */
  43. def updateOffsetToMySQL(appNameAndGroupId: String, offsetRanges: Array[OffsetRange], connection: Connection) = {
  44.  
  45. val ps = connection.prepareStatement("INSERT INTO t_kafka_offset (app_gid, topic_partition, offset) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE offset = ?")
  46.  
  47. for (offsetRange <- offsetRanges) {
  48. //topic名称
  49. val topic = offsetRange.topic
  50. //topic分区编号
  51. val partition = offsetRange.partition
  52. //获取结束偏移量
  53. val untilOffset = offsetRange.untilOffset
  54. //将结果写入MySQL
  55. ps.setString(1, appNameAndGroupId)
  56. ps.setString(2, topic + "_" + partition)
  57. ps.setLong(3, untilOffset)
  58. ps.setLong(4, untilOffset)
  59. ps.executeUpdate()
  60. }
  61. ps.close()
  62. }
  63.  
  64. }

4 将数据写入kafka

  需求:将access.log的数据写入kafka中

  此相当于自己写了一个kafka生产者,然后把数据写入名叫access的topic中,然后就可以使用sparkstreaming消费kafka中的数据,然后进行统计

DataToKafka代码

  1. package cn._51doit.spark.day12
  2.  
  3. import java.util.Properties
  4.  
  5. import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}
  6. import org.apache.kafka.common.serialization.StringSerializer
  7.  
  8. import scala.io.Source
  9.  
  10. // 相当于自己写了一个kafka生产者,然后把数据写入access的topic中,然后就可以使用sparkstreaming消费kafka中的数据,然后进行统计
  11. object DataToKafka {
  12.  
  13. def main(args: Array[String]): Unit = {
  14.  
  15. // 1 配置参数
  16. val props = new Properties()
  17.  
  18. // 连接kafka节点
  19. props.setProperty("bootstrap.servers", "node-1.51doit.cn:9092,node-2.51doit.cn:9092,node-3.51doit.cn:9092")
  20. //指定key序列化方式
  21. props.setProperty("key.serializer", "org.apache.kafka.common.serialization.StringSerializer")
  22. //指定value序列化方式
  23. props.setProperty("value.serializer", classOf[StringSerializer].getName) // 两种写法都行
  24.  
  25. val topic = "access"
  26.  
  27. // 2 kafka的生产者
  28. val producer: KafkaProducer[String, String] = new KafkaProducer[String, String](props)
  29.  
  30. //读取一个文件的数据
  31. val iterator = Source.fromFile(args(0)).getLines()
  32.  
  33. iterator.foreach(line => {
  34.  
  35. //没有指定Key和分区,默认的策略就是轮询,数据写入一部分后,切换leader分区(均匀写入多个分区中)
  36. val record = new ProducerRecord[String, String](topic,line)
  37.  
  38. // 4 发送消息
  39. producer.send(record)
  40.  
  41. })
  42.  
  43. println("message send success")
  44. // 释放资源
  45. producer.close()
  46. }
  47.  
  48. }

tttt

大数据学习day33----spark13-----1.两种方式管理偏移量并将偏移量写入redis 2. MySQL事务的测试 3.利用MySQL事务实现数据统计的ExactlyOnce(sql语句中出现相同key时如何进行累加(此处时出现相同的单词))4 将数据写入kafka的更多相关文章

  1. android studio学习---签名打包的两种方式

    注:给我们自己开发的app签名,就代表着我自己的版权,以后要进行升级,也必须要使用相同的签名才行.签名就代表着自己的身份(即keystore),多个app可以使用同一个签名. 如果不知道签名是啥意思, ...

  2. Mybatis返回插入数据的主键的两种方式

    方式一: 需要在映射文件中添加如下片段: <insert id="insertProduct" parameterType="domain.model.Produc ...

  3. Django学习——ajax发送其他请求、上传文件(ajax和form两种方式)、ajax上传json格式、 Django内置序列化(了解)、分页器的使用

    1 ajax发送其他请求 1 写在form表单 submit和button会触发提交 <form action=""> </form> 注释 2 使用inp ...

  4. Redis持久化的两种方式和区别

    该文转载自:http://www.cnblogs.com/swyi/p/6093763.html Redis持久化的两种方式和区别 Redis是一种高级key-value数据库.它跟memcached ...

  5. session有效期设置的两种方式

    /**session有效期设置的两种方式: * 1.代码设置:session.setMaxInactiveInterval(30);//单位:秒.30秒有效期,默认30分钟. * 2.web.xml中 ...

  6. Eclipse项目中引用第三方jar包时将项目打包成jar文件的两种方式

    转载自:http://www.cnblogs.com/lanxuezaipiao/p/3291641.html 方案一:用Eclipse自带的Export功能 步骤1:准备主清单文件 “MANIFES ...

  7. 数据可视化之DAX篇(十)在PowerBI中累计求和的两种方式

    https://zhuanlan.zhihu.com/p/64418286 假设有一组数据, 已知每一个产品贡献的利润,如果要计算前几名产品的贡献利润总和,或者每一个产品和利润更高产品的累计贡献占总体 ...

  8. angular学习笔记(三)-视图绑定数据的两种方式

    绑定数据有两种方式: <!DOCTYPE html> <html ng-app> <head> <title>2.2显示文本</title> ...

  9. Linux内核设计第四周学习总结 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

    陈巧然原创作品 转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验目的: 使用库函数A ...

随机推荐

  1. Windows7下面手把手教你安装Django - Hongten

    我所使用的操作系统是Windows7,内存是2G 在搜索了一些资料发现,对于Django的安装,详细的真的很少,都说的很简化,然而,这篇blog可以手把手教你成功安装Django 对于Django的详 ...

  2. CentOS7自动备份oracle数据库

    1.环境 操作系统:CentOS 7 数据库:11.2.0.1.0 2.登录服务器 切换oracle用户,备份需要在oracle用户下进行 #su - oracle 在oracle家目录下创建bin目 ...

  3. Jmeter分布式 (三)

    一.什么是分布式测试 分布式测试是指通过局域网和Internet,把分布于不同地点.独立完成特定功能的测试计算机连接起来,以达到测试资源共享.分散操作.集中管理.协同工作.负载均衡.测试过程监控等目的 ...

  4. lua入门之环境搭建、第一个demo

    前言 前段时间因为有些项目功能需要,自己研究了下lua,今天整理下,并以一个demo为示例演示 手机上的运行效果 分为几个步骤来逐步讲解. 1.lua介绍,为什么选择它? 2.环境安装 3.撸一个简单 ...

  5. Netty数据如何在 pipeline 中流动

    前言 在之前文章中,我们已经了解了pipeline在netty中所处的角色,像是一条流水线,控制着字节流的读写,本文,我们在这个基础上继续深挖pipeline在事件传播 Unsafe对象 顾名思义,u ...

  6. 【JavaScript定时器小案例】常见的几种定时器实现的案例

    [JavaScript定时器小案例]常见的几种定时器实现的案例 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 说明 在日常开发 ...

  7. 如何正确的找BUG

    什么是BUG 漏洞是在硬件.软件.协议的具体实现或系统安全策略上存在的缺陷,从而可以使攻击者能够在未授权的情况下访问或破坏系统.具体举例来说,比如在Intel Pentium芯片中存在的逻辑错误,在S ...

  8. Spring Boot 2.6.0正式发布:默认禁止循环依赖、增强Docker镜像构建...

    昨天,Spring官方正式发布了Spring Boot今年最后一个特性版本:2.6.0 同时,也宣布了2.4.x版本的终结. 那么这个新版本又带来了哪些新特性呢?下面就一起跟着DD来看看吧! 重要特性 ...

  9. [hdu6978]New Equipments II

    显然可以费用流来做,具体建图如下-- 点集:源点,汇点,左边$n$​个工人,右边$n$​​​个设备 边集:源点向第$i$​个工人连$(1,a_{i})$​的边,第$i$​个设备向汇点连$(1,b_{i ...

  10. [gym101981F]Frank

    在本题中,每一步是独立的,因此即可以看作从$s$移动到$t$的期望步数(对于每一对$s$和$t$都求出答案) 令$f_{i,j}$表示当$s=i$且$t=j$时的答案,则有$f_{i,j}=\begi ...