使用的flink版本:1.9.1

异常描述

需求:

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

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

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

具体表现如下:

  1. java.lang.Exception: org.apache.flink.streaming.runtime.tasks.ExceptionInChainedOperatorException: Could not forward element to next operator
  2. at org.apache.flink.streaming.runtime.tasks.SourceStreamTask$LegacySourceFunctionThread.checkThrowSourceExecutionException(SourceStreamTask.java:217)
  3. at org.apache.flink.streaming.runtime.tasks.SourceStreamTask.processInput(SourceStreamTask.java:133)
  4. at org.apache.flink.streaming.runtime.tasks.StreamTask.run(StreamTask.java:301)
  5. at org.apache.flink.streaming.runtime.tasks.StreamTask.invoke(StreamTask.java:406)
  6. at org.apache.flink.runtime.taskmanager.Task.doRun(Task.java:705)
  7. at org.apache.flink.runtime.taskmanager.Task.run(Task.java:530)
  8. at java.lang.Thread.run(Thread.java:748)
  9. Caused by: org.apache.flink.streaming.runtime.tasks.ExceptionInChainedOperatorException: Could not forward element to next operator
  10. at org.apache.flink.streaming.runtime.tasks.OperatorChain$CopyingChainingOutput.pushToOperator(OperatorChain.java:654)
  11. at org.apache.flink.streaming.runtime.tasks.OperatorChain$CopyingChainingOutput.collect(OperatorChain.java:612)
  12. at org.apache.flink.streaming.runtime.tasks.OperatorChain$CopyingChainingOutput.collect(OperatorChain.java:592)
  13. at org.apache.flink.streaming.api.operators.AbstractStreamOperator$CountingOutput.collect(AbstractStreamOperator.java:727)
  14. at org.apache.flink.streaming.api.operators.AbstractStreamOperator$CountingOutput.collect(AbstractStreamOperator.java:705)
  15. at org.apache.flink.streaming.api.operators.StreamSourceContexts$ManualWatermarkContext.processAndCollectWithTimestamp(StreamSourceContexts.java:310)
  16. at org.apache.flink.streaming.api.operators.StreamSourceContexts$WatermarkContext.collectWithTimestamp(StreamSourceContexts.java:409)
  17. at org.apache.flink.streaming.connectors.kafka.internals.AbstractFetcher.emitRecordWithTimestamp(AbstractFetcher.java:398)
  18. at org.apache.flink.streaming.connectors.kafka.internal.KafkaFetcher.emitRecord(KafkaFetcher.java:185)
  19. at org.apache.flink.streaming.connectors.kafka.internal.KafkaFetcher.runFetchLoop(KafkaFetcher.java:150)
  20. at org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase.run(FlinkKafkaConsumerBase.java:715)
  21. at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:100)
  22. at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:63)
  23. at org.apache.flink.streaming.runtime.tasks.SourceStreamTask$LegacySourceFunctionThread.run(SourceStreamTask.java:203)

问题追踪

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

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

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

  1. on Tue Apr 21 18:30:41 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
  2. on Tue Apr 21 18:30:42 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
  3. on Tue Apr 21 18:30:44 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
  4. on Tue Apr 21 18:30:45 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
  5. on Tue Apr 21 18:30:47 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
  6. .
  7. .
  8. .
  9. on Tue Apr 21 18:30:45 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
  10. on Tue Apr 21 18:30:47 CST 2020, put Output(714114118412528160|,001,张三,1587471839) to hbase invoke
  11. //并没有到success这一步

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

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

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


问题解决

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

经过测试,此方法无效。

有效的手段
  1. 将二次过滤放到一次过滤的位置
  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改成默认值,然后通过二次过滤,去除默认值
  1. type match {
  2. case "type1" => {
  3. if (data.getInteger("act") == 2) { //二次过滤
  4. if (data.getJSONArray("ids").toArray().length > 0)
  5. id = recode.getString("id") + "," + data.getJSONArray("ids").toArray().mkString(",")
  6. else
  7. id = recode.getString("id")
  8. Input( id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time"), recode.getLong("time") * 1000)//正常输出----标记点:1
  9. } else Input("id","sid", "sn", 0l)//非目标输出 默认值--标记点:2
  10. }
  11. case "type2" => {
  12. if (data.getInteger("act") == 2) { //二次过滤
  13. id = recode.getString("id")
  14. Input(id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time"), recode.getLong("time") * 1000)//正常输出----标记点:1
  15. } else Input("id","sid", "sn", 0l) //非目标输出 默认值--标记点:2
  16. }
  17. }

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

  1. 采用侧输出进行数据分流,将一次过滤的通过侧输出拆分,对拆分后的出具进行特定条件的二次过滤,然后进行对应的解析。
  1. /**
  2. * 数据流处理
  3. *
  4. * @param source
  5. * @return
  6. */
  7. def deal(source: DataStream[String]) = {
  8. println("数据流处理")
  9. //拆分数据流
  10. val splitData: DataStream[String] = splitSource(source)
  11. //解析type1的
  12. val type1: DataStream[Input] = getMkc(splitData)
  13. //解析type2
  14. val type2: DataStream[Input] = getMss(splitData)
  15. //合并数据流
  16. val stream: DataStream[Input] = type1.union(type2)
  17. //拼接rowkey
  18. val result: DataStream[Output] = stream.map(s => {
  19. var rowkey = ""
  20. s.id.split(",").map(id => rowkey += s"$id${9999999999l - s.ts}|")
  21. if (rowkey.equals("")) {
  22. null
  23. } else {
  24. Output(rowkey, s.prdct_cd, s.sid, s.sn, s.ts + "")
  25. }
  26. })
  27. //将结果写入hbase
  28. result.addSink(new CustomSinkToHbase("habse_table", "cf", proInstance)).name("write to hbase").setParallelism(1)
  29. env.execute("test")
  30. }
  31. /**
  32. * 从侧输出中获取type1的数据,过滤开始演唱数据 .filter(_.contains("\"act\":2")) 进行解析
  33. * @param splitData
  34. * @return
  35. */
  36. def getMkc(splitData: DataStream[String]): DataStream[Input] = {
  37. splitData.getSideOutput(new OutputTag[String]("type1"))
  38. .filter(_.contains("\"act\":2"))
  39. .map(str => {
  40. try {
  41. val recode: JSONObject = JSON.parseObject(str)
  42. val dataStr: String = recode.getString("data")
  43. val data = JSON.parseObject(dataStr)
  44. var id: String = ""
  45. if (data.getJSONArray("ids").toArray().length > 0)
  46. id = recode.getString("id") + "," + data.getJSONArray("ids").toArray().mkString(",")
  47. else
  48. id = recode.getString("id")
  49. Input( id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time") * 1000)
  50. } catch {
  51. case e => {
  52. e.printStackTrace()
  53. println("解析json失败: ", str)
  54. Input("id","sid", "sn", 0l)
  55. }
  56. }
  57. }
  58. )
  59. }
  60. /**
  61. * 从侧输出中获取type2的数据,过滤开始演唱数据 .filter(_.contains("\"act\":2")) 进行解析
  62. * @param splitData
  63. * @return
  64. */
  65. def getMss(splitData: DataStream[String]): DataStream[Input] = {
  66. splitData.getSideOutput(new OutputTag[String]("type2"))
  67. .filter(_.contains("\"act\":2"))
  68. .map(str => {
  69. try {
  70. val recode: JSONObject = JSON.parseObject(str)
  71. val dataStr: String = recode.getString("data")
  72. val data = JSON.parseObject(dataStr)
  73. var id: String = ""
  74. id = recode.getString("id")
  75. Input(id.reverse, data.getString("sid"), data.getString("sn"), recode.getLong("time") * 1000)
  76. } catch {
  77. case e => {
  78. e.printStackTrace()
  79. println("解析json失败: ", str)
  80. Input("id","sid", "sn", 0l)
  81. }
  82. }
  83. }
  84. )
  85. }
  86. /**
  87. * 使用侧输出切分数据流
  88. * @param source
  89. * @return
  90. */
  91. def splitSource(source: DataStream[String]) = {
  92. source.process(new ProcessFunction[String, String] {
  93. override def processElement(value: String, ctx: ProcessFunction[String, String]#Context, out: Collector[String]): Unit = {
  94. value match {
  95. case value if value.contains("\"type\":\"type1\"") => ctx.output(new OutputTag[String]("type1"), value)
  96. case value if value.contains("\"type\":\"type2\"") => ctx.output(new OutputTag[String]("type2"), value)
  97. case _ => out.collect(value)
  98. }
  99. }
  100. })
  101. }

问题解决,对比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. 动态规划-区间dp-Palindrome Removal

    2019-11-09 10:31:09 问题描述: 问题求解: n = 100,典型的O(n ^ 3)的动规问题.一般来说这种O(n ^ 3)的问题可以考虑使用区间dp来解决. 区间dp是典型的三层结 ...

  2. Hive架构原理

    什么是Hive Hive是由Facebook开源用于解决海量结构化日志的数据统计:Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射 成一张表,并提供类SQL查询功能,底层计算引 ...

  3. bzoj4693

    题意 bzoj 做法 结论1:对于\((X_1,X_2,...,X_k)\),其为红的充要条件为:令\(Y_i=X_i-1\),\(\prod\limits_{k=1}^K {\sum\limits_ ...

  4. thinkphp 路径 (纯转)

    TP中有不少路径的便捷使用方法,比如模板中使用的__URL__,__ACTION__等,如果你对这些路径不是很明白,用起来说不定就会有这样或那样的问题,抑或出了错也不知道怎么改,现在我们看一下这些路径 ...

  5. TensorFlow Windows 安装

    欢迎大家关注我们的网站和系列教程:http://www.tensorflownews.com/,学习更多的机器学习.深度学习的知识! 本系列教程将手把手带您从零开始学习Tensorflow,并最终通过 ...

  6. 8 个出没在 Linux 终端的诡异家伙

    这篇文章,我们一起来到 Linux 的诡异的一面-- 你知道吗?在我们日常使用的 Unix(和 Linux )及其各种各样的分支系统中,存在着一些诡异的命令或进程,它们让人毛骨悚然,有些确实是有害,但 ...

  7. 2-SAT(HDU-3062 party)

    2-SAT(HDU-3062 party) 解决问题类型: 书本定义:给一个布尔方程,判断是否存在一组解使整个方程为真,被称为布尔方程可满足性问题(SAT) 因为本题只有0,1(丈夫 妻子只能去一个人 ...

  8. Redis调用lua生成验证码

    场景: ​ 通过微信公众号拿验证码在APP上绑定,为了防止重复,尝试使用reids-lua的方法实现此功能 以下是 php 调用 redis.eval 方法传入的 lua 方法,当然这只是修改后的,保 ...

  9. [HDU1029]Ignatius and the Princess IV<桶 水题>

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1029 题目大意: 多组数据,每组数据先给一个n,然后给n各数字,找出n各数字中出现了至少(n+1)/2 ...

  10. 使用kibana操作elasticsearch7.x 教程

    由于elasticsearch7.x取消了type(类型的概念)对应数据库表的概念 添加一个索引 PUT 索引名 { "settings": { "number_of_s ...