Kafka+Spark Streaming管理offset的几种方法
enable.auto.commit设为false。offset的管理方式
在Kafka DirectStream初始化时,取得当前所有partition的存量offset,以让DirectStream能够从正确的位置开始读取数据。
读取消息数据,处理并存储结果。
提交offset,并将其持久化在可靠的外部存储中。
保存offset的方式
enable.auto.commit=true。
一但consumer挂掉,就会导致数据丢失或重复消费。
offset不可控。
(属于At-least-once语义,如果做好了幂等性,可以使用这种方式):
在Kafka 0.10+版本中,offset的默认存储由ZooKeeper移动到了一个自带的topic中,名为__consumer_offsets。
Spark Streaming也专门提供了commitAsync() API用于提交offset。
需要将参数修改为enable.auto.commit=false。
在我实际测试中发现,这种offset的管理方式,不会丢失数据,但会出现重复消费。
停掉streaming应用程序再次启动后,会再次消费停掉前最后的一个批次数据,应该是由于offset是异步提交的方式导致,offset更新不及时引起的。
因此需要做好数据的幂等性。
(修改源码将异步改为同步,应该是可以做到Exactly-once语义的)
(推荐,采用这种方式,可以做到At-least-once语义):
可以将offset存放在第三方储中,包括RDBMS、Redis、ZK、ES等。
若消费数据存储在带事务的组件上,则强烈推荐将offset存储在一起,借助事务实现 Exactly-once 语义。
示例
在Kafka 0.10+版本中,offset的默认存储由ZooKeeper移动到了一个自带的topic中,名为__consumer_offsets。所以我们读写offset的对象正是这个topic,Spark Streaming也专门提供了commitAsync() API用于提交offset。实际上,一切都已经封装好了,直接调用相关API即可。
stream.foreachRDD { rdd =>
val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
// 确保结果都已经正确且幂等地输出了
stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
}ZooKeeper
class ZkKafkaOffsetManager(zkUrl: String) {
private val logger = LoggerFactory.getLogger(classOf[ZkKafkaOffsetManager])
private val zkClientAndConn = ZkUtils.createZkClientAndConnection(zkUrl, 30000, 30000);
private val zkUtils = new ZkUtils(zkClientAndConn._1, zkClientAndConn._2, false)
def readOffsets(topics: Seq[String], groupId: String): Map[TopicPartition, Long] = {
val offsets = mutable.HashMap.empty[TopicPartition, Long]
val partitionsForTopics = zkUtils.getPartitionsForTopics(topics)
// /consumers/<groupId>/offsets/<topic>/<partition>
partitionsForTopics.foreach(partitions => {
val topic = partitions._1
val groupTopicDirs = new ZKGroupTopicDirs(groupId, topic)
partitions._2.foreach(partition => {
val path = groupTopicDirs.consumerOffsetDir + "/" + partition
try {
val data = zkUtils.readData(path)
if (data != null) {
offsets.put(new TopicPartition(topic, partition), data._1.toLong)
logger.info(
"Read offset - topic={}, partition={}, offset={}, path={}",
Seq[AnyRef](topic, partition.toString, data._1, path)
)
}
} catch {
case ex: Exception =>
offsets.put(new TopicPartition(topic, partition), 0L)
logger.info(
"Read offset - not exist: {}, topic={}, partition={}, path={}",
Seq[AnyRef](ex.getMessage, topic, partition.toString, path)
)
}
})
})
offsets.toMap
}
def saveOffsets(offsetRanges: Seq[OffsetRange], groupId: String): Unit = {
offsetRanges.foreach(range => {
val groupTopicDirs = new ZKGroupTopicDirs(groupId, range.topic)
val path = groupTopicDirs.consumerOffsetDir + "/" + range.partition
zkUtils.updatePersistentPath(path, range.untilOffset.toString)
logger.info(
"Save offset - topic={}, partition={}, offset={}, path={}",
Seq[AnyRef](range.topic, range.partition.toString, range.untilOffset.toString, path)
)
})
}
}HasOffsetRanges是KafkaRDD的一个trait,而CanCommitOffsets是DirectKafkaInputDStream的一个trait。与接口不同的是,它还可以定义属性和方法的实现。
private[spark] class KafkaRDD[K, V](
sc: SparkContext,
val kafkaParams: ju.Map[String, Object],
val offsetRanges: Array[OffsetRange],
val preferredHosts: ju.Map[TopicPartition, String],
useConsumerCache: Boolean
) extends RDD[ConsumerRecord[K, V]](sc, Nil) with Logging with HasOffsetRanges
private[spark] class DirectKafkaInputDStream[K, V](
_ssc: StreamingContext,
locationStrategy: LocationStrategy,
consumerStrategy: ConsumerStrategy[K, V],
ppc: PerPartitionConfig
) extends InputDStream[ConsumerRecord[K, V]](_ssc) with Logging with CanCommitOffsets {
不能对stream对象做transformation操作之后的结果进行强制转换(会直接报ClassCastException),因为RDD与DStream的类型都改变了。只有RDD或DStream的包含类型为ConsumerRecord才行。
文章不错?点个【在看】吧! 👇
智能推荐
请求参数获取的几种方法
1、使用@PathVariable注解 @PathVariable也可以获取含正则表达式的一类值 例如: 2、用@ModelAttribute注解获取POST请求的FORM表单数据 3、直接用HttpServletRequest获取 4、用注解@RequestParam 绑定请求参数a到,变量a 当请求参数a不存在时会有异常发生,可以通过设置属required=f...
Android json数据的几种方法
一、什么是json 百度百科中对json的介绍是这样的:JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于JavaScript(Standard ECMA-262 3rd Edition - December 1999)的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, J...
启动Node服务的几种方法
启动Node服务的几种方法 2017年11月20日 22:20:56 阅读数:1295 下面是以express框架为例,启动node服务的三种方法(已测试),使用express安装的文件如下: 一 、使用node命令启动 1 二、使用webStorm启动 1,打开webStorm,点击运行(run)下的编辑配置(Edit Configuration) 2,添加新的配置 3,命名并选择要执行的文件 ...
Unity人物移动的几种方法
方法一:transform.Translate 再讲之前先知道Vector3.forward就是朝前也就是Z轴的方向,如果不明白的可以看一下图 世界坐标系移动 代码中写的是根据世界坐标轴的z轴方向移动,然后再看一下右上角的世界坐标轴发现是根据世界坐标轴的z轴移动是没有问题的,这里在观察一个问题就是这个物体的自身坐标轴的z轴是朝向左侧的,这个就是因为方法第二个参数的作用,Space.World为根据...
Stata导入数据的几种方法
目录 1. 方法一:Ctrl+C / Ctrl+V(适用于数据量较小时) 2. 方法二:直接导入Excel文件(适用于文件较大时) 3. 方法三:dta文件双击可用Stata打开 4. 方法四:sav文件导入Stata 5. 方法五:输入use命令 本文基于陈强老师著《计量经济学及Stata应用》整理 数据集下载地址:陈强教授的计量经济学及Stata主页 1. 方法一:Ctrl+C / Ctrl+...
猜你喜欢
js创建对象的几种方法
1.工厂模式 缺点:没有解决怎样知道一个对象类型的问题 2.构造函数模式 与工厂模式不同之处: 1.没有显示的创建对象 2.直接将属性和方法赋值给this 3.没有return语句 要创建Person的实例,必须使用new操作符,经历了以下四个步骤 1.创建了一个新对象 2.将构造函数的作用域赋给新对象(this就指向了这个对象) 3.执行构造函数中的代码(为这个对象添加属性和方法) 4.返回这个...
清除浮动的几种方法
清除浮动的几种方法 上面这是浮动导致的condiv不显示 解决方案为以下: 1.自定义高度 只适用于高度不变的情况。 2.最后加一个div或者br标签,css样式为clear:both 原理:会将来自元素周围的的浮动清除 3.伪类:after 4.父元素加overflow:hidden 原理:的高度要被切掉,检查到了float高度 5.父元素加overflow:auto 6.父元素也添加float...
MFC绘图的几种方法
MFC绘图的几种方法 /* 1.可以使用OnDraw()函数直接绘图; 2.使用菜单绘图(添加菜单——添加菜单函数(消息映射函数)) 3.自定义函数绘图(添加成员函数——可以在OnDraw()函数调用,也可以在菜单函数里调用) 4.工具条设计(实现图标和子菜单关联) */ 1.可以使用OnDraw()函数直接...
网页返回顶部的几种方法
网页返回顶部的几种方法 1,在页面顶部固定一个 返回网页顶部的 按钮 .back-to-top { position: fixed; right: 20px; bottom: 10px; width: 100px; height: 30px; text-align: center; line-height: 30px; color: #2C2C2C; text-decoration: none; ...
CSS垂直居中的几种方法
方法有很多,这里简述四种。 1.利用vertical-align进行居中。 2.用定位。 3.用弹性盒。 4.用网格布局。 一、vertical-align 给要居中元素添加标尺;或转为table-cell类型。 用标尺: 效果图: 用table-cell: 可以给父元素设为table,子元素设为table-cell(注释内容);也可以给父元素设为table-cell,子元素用margin水平居中...


