使用的flink版本:1.9.1

异常描述

需求:

  1. 从kafka读取一条数据流
  2. 经过filter初次筛选符合要求的数据
  3. 然后通过map进行一次条件判断再解析。这个这个过程中可能返回null或目标输出outData。
  4. 最后将outData通过自定义sink写入hbase。
转换核心代码:
val stream: DataStream[Input] = source.filter(s => (!s.equals(null)) && (s.contains("\"type\":\"type1\"") || s.contains("\"type\":\"type2\"")))//一次过滤
.map(json => {
try {
val recode: JSONObject = JSON.parseObject(json)
val dataStr: String = recode.getString("data")
val type = recode.getString("type")
val data = JSON.parseObject(dataStr)
var id: String = ""
type match {
case "type1" => {
if (data.getInteger("act") == 2) { //二次过滤
if (data.getJSONArray("ids").toArray().length > 0)
id = recode.getString("id") + "," + data.getJSONArray("ids").toArray().mkString(",")
else
id = recode.getString("id")
Input( id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time"), recode.getLong("time") * 1000)//正常输出----标记点:1
} else null//非目标输出 导致问题的位置 此处给个随便的默认值 只要不是null就不会出问题,但是这样后面操作需要二次过滤-----标记点:2
}
case "type2" => {
if (data.getInteger("act") == 2) { //二次过滤
id = recode.getString("id")
Input(id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time"), recode.getLong("time") * 1000)//正常输出----标记点:1
} else null //非目标输出 导致问题的位置 此处给个随便的默认值 只要不是null就不会出问题,但是这样后面操作需要二次过滤 ----标记点:2
}
}
} catch {
case e => {
e.printStackTrace()
println("解析json失败: ", json)
Input("id","sid", "sn", 0l)
}
}
} ) val result: DataStream[Output] = stream.map(s => {
var rowkey = ""
s.id.split(",").map(id => rowkey += s"$id${9999999999l - s.ts}|")
if (rowkey.equals("")) {
null
} else {
Output(rowkey, s.sid, s.sn, s.ts + "")
}
}) result.addSink(new CustomSinkToHbase("habse_table", "cf", proInstance)).name("write to hbase").setParallelism(1)
自定义sink核心代码
override def invoke(value: Output, context: SinkFunction.Context[_]): Unit = {
println(s"on ${new Date}, put $value to hbase invoke ") //输出标记:1
try {
init()
val puts = new util.ArrayList[Put]()
value.rowkey.split("\\|").map(s => {
val rowkey = s
val put: Put = new Put(Bytes.toBytes(rowkey))
put.addColumn(Bytes.toBytes(cf), Bytes.toBytes("sid"), Bytes.toBytes(value.sid))
put.addColumn(Bytes.toBytes(cf), Bytes.toBytes("sn"), Bytes.toBytes(value.sn))
put.addColumn(Bytes.toBytes(cf), Bytes.toBytes("ts"), Bytes.toBytes(value.ts))
puts.add(put)
})
table.put(puts)
println(s"on ${new Date}, put $value to hbase succeese ")//输出标记:2
} catch {
case e => {
e.printStackTrace()
if (table != null) table.close()
if (conn != null) conn.close()
}
}
}
执行情况

在程序启动后,随着数据流的进入会产生不一样的结果:

  1. 如果数据从未有数据进入标记点2,那么一切正常
  2. 如果如果有数据进入标记点2,说明此时返回的是null,程序会马上报错:ExceptionInChainedOperatorException,后续的数据处理也会失败,程序陷入死循环。

具体表现如下:

java.lang.Exception: org.apache.flink.streaming.runtime.tasks.ExceptionInChainedOperatorException: Could not forward element to next operator
at org.apache.flink.streaming.runtime.tasks.SourceStreamTask$LegacySourceFunctionThread.checkThrowSourceExecutionException(SourceStreamTask.java:217)
at org.apache.flink.streaming.runtime.tasks.SourceStreamTask.processInput(SourceStreamTask.java:133)
at org.apache.flink.streaming.runtime.tasks.StreamTask.run(StreamTask.java:301)
at org.apache.flink.streaming.runtime.tasks.StreamTask.invoke(StreamTask.java:406)
at org.apache.flink.runtime.taskmanager.Task.doRun(Task.java:705)
at org.apache.flink.runtime.taskmanager.Task.run(Task.java:530)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.flink.streaming.runtime.tasks.ExceptionInChainedOperatorException: Could not forward element to next operator
at org.apache.flink.streaming.runtime.tasks.OperatorChain$CopyingChainingOutput.pushToOperator(OperatorChain.java:654)
at org.apache.flink.streaming.runtime.tasks.OperatorChain$CopyingChainingOutput.collect(OperatorChain.java:612)
at org.apache.flink.streaming.runtime.tasks.OperatorChain$CopyingChainingOutput.collect(OperatorChain.java:592)
at org.apache.flink.streaming.api.operators.AbstractStreamOperator$CountingOutput.collect(AbstractStreamOperator.java:727)
at org.apache.flink.streaming.api.operators.AbstractStreamOperator$CountingOutput.collect(AbstractStreamOperator.java:705)
at org.apache.flink.streaming.api.operators.StreamSourceContexts$ManualWatermarkContext.processAndCollectWithTimestamp(StreamSourceContexts.java:310)
at org.apache.flink.streaming.api.operators.StreamSourceContexts$WatermarkContext.collectWithTimestamp(StreamSourceContexts.java:409)
at org.apache.flink.streaming.connectors.kafka.internals.AbstractFetcher.emitRecordWithTimestamp(AbstractFetcher.java:398)
at org.apache.flink.streaming.connectors.kafka.internal.KafkaFetcher.emitRecord(KafkaFetcher.java:185)
at org.apache.flink.streaming.connectors.kafka.internal.KafkaFetcher.runFetchLoop(KafkaFetcher.java:150)
at org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase.run(FlinkKafkaConsumerBase.java:715)
at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:100)
at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:63)
at org.apache.flink.streaming.runtime.tasks.SourceStreamTask$LegacySourceFunctionThread.run(SourceStreamTask.java:203)

问题追踪

在程序报错后在taskmanager日志的表现为错误日志无限循环,web页面的表现为任务的开始时间重置。

辅助输出,确定程序出错位置

通过在hbase中添加辅助输出,结果如下

on Tue Apr 21 18:30:41 CST 2020, put  Output(714114118412528160|,001,张三,1587471839) to hbase  invoke
on Tue Apr 21 18:30:42 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
on Tue Apr 21 18:30:44 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
on Tue Apr 21 18:30:45 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
on Tue Apr 21 18:30:47 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
.
.
.
on Tue Apr 21 18:30:45 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
on Tue Apr 21 18:30:47 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
//并没有到success这一步

如果数据流d1进入了标记点:2(输出null);

那么后续的数据流d2进入标记点:1(正常输出) ,此时在web页面task-manager stdout的中出现d2在输出标记:1 和输出标记:2(没有输出2的部分)无限循环。

输出标记:2 没有执行 说明没有写hbase。加上错误产生的条件为要有数据进入标记点:2,初步分析是这个null的返回值影响到了后面hbase的操作。


问题解决

无效手段
  1. 写hbase前过滤掉null的值
    val result: DataStream[Output] = stream.map(s => {
var rowkey = ""
s.id.split(",").map(id => rowkey += s"$id${9999999999l - s.ts}|")
if (rowkey.equals("")) {
null
} else {
Output(rowkey, s.sid, s.sn, s.ts + "")
}
}).filter(_!=null)//过滤null

经过测试,此方法无效。

有效的手段
  1. 将二次过滤放到一次过滤的位置
 source.filter(s => (!s.equals(null)) && (s.contains("\"type\":\"type1\"") || s.contains("\"type\":\"type2\"")) && (s.contains("\"act\":2"))//提前过滤act=2

问题解决,但是因为业务的问题,act不是通用条件,不具备通用性。当然可以进行了;进行两次filter,但是过于繁琐并且会产生多条数据流。

  1. 将标记点2的null改成默认值,然后通过二次过滤,去除默认值
 type match {
case "type1" => {
if (data.getInteger("act") == 2) { //二次过滤
if (data.getJSONArray("ids").toArray().length > 0)
id = recode.getString("id") + "," + data.getJSONArray("ids").toArray().mkString(",")
else
id = recode.getString("id")
Input( id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time"), recode.getLong("time") * 1000)//正常输出----标记点:1
} else Input("id","sid", "sn", 0l)//非目标输出 默认值--标记点:2
}
case "type2" => {
if (data.getInteger("act") == 2) { //二次过滤
id = recode.getString("id")
Input(id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time"), recode.getLong("time") * 1000)//正常输出----标记点:1
} else Input("id","sid", "sn", 0l) //非目标输出 默认值--标记点:2
}
}

问题解决,但是从整体数据量来看,标记点1的数量仅为标记点2数量的六分之一到五分之一之间,此处会做很多无用的json解析。在大数据量的时候还是会对效率的些许影响

  1. 采用侧输出进行数据分流,将一次过滤的通过侧输出拆分,对拆分后的出具进行特定条件的二次过滤,然后进行对应的解析。
 /**
* 数据流处理
*
* @param source
* @return
*/
def deal(source: DataStream[String]) = {
println("数据流处理")
//拆分数据流
val splitData: DataStream[String] = splitSource(source)
//解析type1的
val type1: DataStream[Input] = getMkc(splitData) //解析type2
val type2: DataStream[Input] = getMss(splitData) //合并数据流
val stream: DataStream[Input] = type1.union(type2) //拼接rowkey
val result: DataStream[Output] = stream.map(s => {
var rowkey = ""
s.id.split(",").map(id => rowkey += s"$id${9999999999l - s.ts}|")
if (rowkey.equals("")) {
null
} else {
Output(rowkey, s.prdct_cd, s.sid, s.sn, s.ts + "")
}
}) //将结果写入hbase
result.addSink(new CustomSinkToHbase("habse_table", "cf", proInstance)).name("write to hbase").setParallelism(1) env.execute("test")
} /**
* 从侧输出中获取type1的数据,过滤开始演唱数据 .filter(_.contains("\"act\":2")) 进行解析
* @param splitData
* @return
*/
def getMkc(splitData: DataStream[String]): DataStream[Input] = {
splitData.getSideOutput(new OutputTag[String]("type1"))
.filter(_.contains("\"act\":2"))
.map(str => {
try {
val recode: JSONObject = JSON.parseObject(str)
val dataStr: String = recode.getString("data")
val data = JSON.parseObject(dataStr)
var id: String = ""
if (data.getJSONArray("ids").toArray().length > 0)
id = recode.getString("id") + "," + data.getJSONArray("ids").toArray().mkString(",")
else
id = recode.getString("id")
Input( id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time") * 1000)
} catch {
case e => {
e.printStackTrace()
println("解析json失败: ", str)
Input("id","sid", "sn", 0l)
}
}
}
)
} /**
* 从侧输出中获取type2的数据,过滤开始演唱数据 .filter(_.contains("\"act\":2")) 进行解析
* @param splitData
* @return
*/
def getMss(splitData: DataStream[String]): DataStream[Input] = {
splitData.getSideOutput(new OutputTag[String]("type2"))
.filter(_.contains("\"act\":2"))
.map(str => {
try {
val recode: JSONObject = JSON.parseObject(str)
val dataStr: String = recode.getString("data")
val data = JSON.parseObject(dataStr)
var id: String = ""
id = recode.getString("id")
Input(id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time") * 1000)
} catch {
case e => {
e.printStackTrace()
println("解析json失败: ", str)
Input("id","sid", "sn", 0l)
}
}
}
)
} /**
* 使用侧输出切分数据流
* @param source
* @return
*/
def splitSource(source: DataStream[String]) = {
source.process(new ProcessFunction[String, String] {
override def processElement(value: String, ctx: ProcessFunction[String, String]#Context, out: Collector[String]): Unit = {
value match {
case value if value.contains("\"type\":\"type1\"") => ctx.output(new OutputTag[String]("type1"), value)
case value if value.contains("\"type\":\"type2\"") => ctx.output(new OutputTag[String]("type2"), value)
case _ => out.collect(value)
}
}
})
}

问题解决,对比1的好处是,侧输出的时候,数据流还是只有一个,只是给数据打了一个标签,并且对可后期业务的扩展很友好。


总结

其实虽然问题解决了,但是具体问题出现的原理并没有整理明白。

目前猜测是null的输出类型对后续的输入类型有影响,但是具体的影响怎么发生,估计得抽空研究源码才能知道了。后续有结果再更

本文为原创文章,转载请注明出处!!!

ExceptionInChainedOperatorException:flink写hbase对于null数据导致数据导致出现异常的更多相关文章

  1. Redis面试题记录--缓存双写情况下导致数据不一致问题

    转载自:https://blog.csdn.net/lzhcoder/article/details/79469123 https://blog.csdn.net/u013374645/article ...

  2. 手把手教你写带登录的NodeJS爬虫+数据展示

    其实在早之前,就做过立马理财的销售额统计,只不过是用前端js写的,需要在首页的console调试面板里粘贴一段代码执行,点击这里.主要是通过定时爬取https://www.lmlc.com/s/web ...

  3. Spark-读写HBase,SparkStreaming操作,Spark的HBase相关操作

    Spark-读写HBase,SparkStreaming操作,Spark的HBase相关操作 1.sparkstreaming实时写入Hbase(saveAsNewAPIHadoopDataset方法 ...

  4. 使用Apache Flink 和 Apache Hudi 创建低延迟数据湖管道

    近年来出现了从单体架构向微服务架构的转变.微服务架构使应用程序更容易扩展和更快地开发,支持创新并加快新功能上线时间.但是这种方法会导致数据存在于不同的孤岛中,这使得执行分析变得困难.为了获得更深入和更 ...

  5. 【hbase】——bulk load导入数据时value=\x00\x00\x00\x01问题解析

    一.存入数据类型 Hbase里面,rowkey是按照字典序进行排序.存储的value值,当用filter进行数据筛选的时候,所用的比较算法也是字典序的. 1.当存储的value值是float类型的时候 ...

  6. 应用Flume+HBase采集和存储日志数据

    1. 在本方案中,我们要将数据存储到HBase中,所以使用flume中提供的hbase sink,同时,为了清洗转换日志数据,我们实现自己的AsyncHbaseEventSerializer. pac ...

  7. MySQL实例多库某张表数据文件损坏导致xxx库无法访问故障恢复

    一.问题发现 命令行进入数据库实例手动给某张表进行alter操作,发现如下报错. mysql> use xx_xxx; No connection. Trying to reconnect... ...

  8. 《MySQL必知必会》过滤数据,数据过滤(where ,in ,null ,not)

    <MySQL必知必会>过滤数据,数据过滤 1.过滤数据 1.1 使用 where 子句 在SEL ECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤. WHERE子句在表名(FR ...

  9. c# 传递Null的string值导致的调用C++的dll报错 Attempted to read or write protected memory.

    c# 调用C++的dll报错 Attempted to read or write protected memory:   原因是:c# 传递Null的string值导致的,将Null改为string ...

随机推荐

  1. coding++:快速构建 kindeditor 富文本编辑器(一)

    此案例 demo 为 SpringBoot 开发 1.官网下载相关资源包:http://kindeditor.net/down.php 2.编写页面(引入相关JS) <!DOCTYPE html ...

  2. [洛谷1437&Codevs1257]敲砖块<恶心的dp>

    题目链接:https://www.luogu.org/problem/show?pid=1437#sub http://codevs.cn/problem/1257/ 不得不说,这个题非常的恶心,在初 ...

  3. html第一个程序

    2020-04-05  每日一例第27天 1.打开记事本,输入html格式语言: 2.后台代码注释: <html> <head><!--标题语句--> <ti ...

  4. CSS3实现3D转换

    实现效果: 当鼠标悬停在图片上面时,图片发生翻转,文案出来,鼠标取消之后就又恢复 实现代码: <!DOCTYPE html> <html lang="en"> ...

  5. P3381 【模板】最小费用最大流 题解

    CSDN同步 原题链接 前置知识: 从三种算法剖析网络流本质 简要题意: 给定网络图,求图的最大流,以及流量为最大流时的最小费用. 现在假设你们看了那篇网络流博客之后,所有人都会了 \(\text{E ...

  6. PHP的运行方式(SAPI)

    PHP 常量 PHP_SAPI 具有和 php_sapi_name() 相同的值. define('IS_CGI',(0 === strpos(PHP_SAPI,'cgi') || false !== ...

  7. Nginx-高性能的反向代理服务器

    Nginx Nginx作为一款反向代理服务器,现在大多数网站都有使用,自己在项目中几乎都有用到,自己的网站也使用到了它. 了解Nginx 上面图可以直观的看出Nginx的用处,可以将请求转发至Web服 ...

  8. Vue-cli2.0 第3节 解读Vue-cli模板

    Vue-cli2.0 第3节 解读Vue-cli模板 目录 Vue-cli2.0 第3节 解读Vue-cli模板 第3节 解读Vue-cli模板 1. npm run build命令 2. main. ...

  9. Java第十天,多态

    多态 一.多态的定义: 一个对象拥有多种形态,这就是对象的多态性.也就是说多态针对的是对象.多态的前提是接口和继承(C++中实行多继承,不存在接口). 二.多态在代码中的形式: 父类 对象名 = ne ...

  10. Flask 入门(三)

    官方的文档虽然正规,但是有点太过书面语,有时候,明明很简单的一个程序,如果非要看它的说明,反而会让人疑惑不解,倒不如看一下别人写的简单的一个demo,jinjia2模板看官方的文档看了5回,愣是不明白 ...