mongodb游标超时报错:com.mongodb.MongoCursorNotFoundException: Query failed with error code -5的四种处理方式

背景:

我遇到这个问题是用datax导数据时我们自定义了mongoreader组件在读取亿级数据量表时报错,数据量大,数据结构复杂,其中所读取的数据库数据较多,因为是线上业务库读取数据的速度被限制,加之当时网络波动,之前运行正常的任务报了游标超时的错误。

问题

 Caused by: com.mongodb.MongoCursorNotFoundException: Query failed with error code -5 and error message 'Cursor 400554224227 not found on server 11.11.16.123:17000' on server 11.11.17.251:9000

	 at com.mongodb.operation.QueryHelper.translateCommandException(QueryHelper.java:27)

	 at com.mongodb.operation.QueryBatchCursor.getMore(QueryBatchCursor.java:229)

	 at com.mongodb.operation.QueryBatchCursor.hasNext(QueryBatchCursor.java:115)

	 at com.mongodb.MongoBatchCursorAdapter.hasNext(MongoBatchCursorAdapter.java:46)

	 at com.alibaba.datax.plugin.reader.mongodbreader.MongoDBReader$Task.startRead(MongoDBReader.java:301)

	 at com.alibaba.datax.core.taskgroup.runner.ReaderRunner.run(ReaderRunner.java:57)

问题分析:

你在用 db.collection.find()的时候,它返回的不是所有的数据,而实际上是一个cursor。它的默认行为是:第一次向数据库查询 101 个文档,或 1 MB 的文档,取决于哪个条件先满足;之后每次cursor 中的文档用尽后,查询 4 MB 的文档。另外,find() 的默认行为是返回一个 10 分钟无操作后超时的 cursor如果一个 batch 的文档十分钟内没处理完,过后再处理完了,再用同一个 cursor id 向服务器取下一个 batch,这时候cursor id 已经过期了,这也就能解释为啥得到 cursor id 无效的错误了。

解决方案

方案一:

修改MongoDB的配置,延长游标超时时间,并重启MongoDB。但是由于生产环境的MongoDB不能随便重启,联系运维可能会拒绝你,所以这个方案有用,但是你也被运维拒绝后可以考虑以下方案。

方案二:

一次性把数据全部读取下来,再做处理,以下是解释实例:

all_data = [row for row in handler.find()]

for row in all_data:
    parse(row)

这种方案的弊端很明显,如果数据量非常大,你不一定能全部放到内存里面,在我们亿级数据量的背景下几乎不可能。即使能够全部放到内存中,但是列表推导式遍历了所有数据,紧接着for循环又遍历一次,浪费时间,所以不推荐,如果在数据量级小完全可以放到内存中可以做类似的尝试。

方案三:

让游标每次返回的数据小于100条,这样消费完这一批数据的时间就会小于10分钟:

#每次连接数据库,只返回50行数据
for row in handler.find().batch_size(100): 
    parse_data(row)

这种方案虽然比较稳妥但是会增加数据库的连接次数,从而增加I/O耗时。试想我们有1亿数据,你把每次连接查询的数据量限制在100或者说是1000次读取这份数据应该要创建最少10万次连接,这种代码被发现后自己想后果。但是如果你的连接创建次数在接受范围内,完全是可以的。

方案四:

让游标永不超时。通过设定参数no_cursor_timeout=True,让游标永不超时:

cursor = handler.find(no_cursor_timeout=True)
for row in cursor:
    parse_data(row)
cursor.close()  # 一定要手动关闭游标

然而这个操作非常危险,因为如果你循环时产生异常,甚至断电或断网,都会导致 MongoDB 服务器资源永远无法被释放。这个游标就再也无法关闭了!除非重启MongoDB,否则这些游标会一直留在MongoDB上,占用资源。
那么有没有什么方法可以规避这个风险,在创建线程或者使用spark时常常会有close()又或者时stop()的方法,我们经常将他们放在必须执行的结构中。所以我们想到了用try catch finally去做。

/ * try catch:自己处理异常
  * try {
  *可能出现异常的代码
  *} catch(异常类名A e){
  *如果出现了异常类A类型的异常,那么执行该代码
  *} ...catch可以有多个)
  * finally {
  *最终肯定必须要执行的代码(例如释放资源的代码)
  *}

虽然这种方法会对代码美观性造成一定的破坏但是无疑完美的解决了我们的问题。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

扫地增

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值