美文网首页
springboot mongo查询游标(cursor)不存在错

springboot mongo查询游标(cursor)不存在错

作者: _Kantin | 来源:发表于2022-02-10 11:39 被阅读0次

    背景介绍

    • 线上系统收到不到接口查询失败的告警,均为mongo查询,返回的错误状态码为-5,报错日志如下所示:
    2022-02-09 18:16:56.631 ERROR 1 --- [ask-scheduler-6] o.s.integration.handler.LoggingHandler : org.springframework.dao.DataAccessResourceFailureException: 
    Query failed with error code -5 and error message 'Cursor 73973161000 not found on server <mongodb-server>' on server <mongodb-server>; 
    nested exception is com.mongodb.MongoCursorNotFoundException: 
    Query failed with error code -5 and error message 'Cursor 73973161000 not found on server <mongodb-server>' on server <mongodb-server> 
    
    • 从上面的报错信息来看,是查询在执行过程中游标找不到导致的。可能是被回收或关闭,也可能确实没有。

    故障排查及灰度测试

    故障排查过程

    • 首先确定是否为完全阻塞式故障,这将决定是否将要紧急修复(封网期发布是有限制的)。经过获取客户端的请求参数进行模拟重试后(仅查询),发现是重试是可以成功的。确定不是必现bug
    • 由于客户端一般是分不同的类型来进行批量查询,发现出错的查询均为某一类的type,它们共有的特点就是查询的数据量比较大。确定可能跟大数据量查询有关
    • 获取线上报错的请求参数,在测试环境mock完数据后进行批量并发测试,并未能重现问题。确定可能跟部署环境有关

    灰度过程

    • 测试环境复现不了问题,因此转到线上环境。本次灰度过程采用就k8s pods的金丝雀发布,配置的线上流量为20%
    • 由于代码中有构建mongo线程池,最小的连接数为5,最大空闲时间为30s。一种猜测是可能请求线程在获取到连接并将要查询时,连接超时被回收导致查询失败,因此将线程池的min collection设置为1,确保不会取到将要被回收的连接线程。经过灰度后,仍然会报错
    • 考虑出错的均为大数据量查询,猜测可能与游标超时有关。将出错的type的query设置为noCursorTimeout,经过灰度后,仍然会报错
    • 考虑到可能查询数量超过了mongo的默认batch_size(101行),当数据量太大导致分批迭代时间太长导致超时。因此可以在程序侧修改batch_size大小,增加每次读取的数量。将query的batch_size设置为1000,经过灰度后,不会报错了
    • 考虑可能连接的mongo地址是load balancer,可能mongo在分批加载数据时请求到不同的后端服务器,也即说游标可能由A服务器生成,但是代完数据继续请求数据时访问到 B 服务器,由于该游标不是 B 生成的,因此也会报错。在结合比较测试和生产环境,发现生产连的确实的lb地址,而测试并不是,这就是测试环境重现不出的原因。经过将lb地址改为直连mongos地址,经过灰度后,不会报错了。同时删除掉设置的batch_size也不会再报错

    核心原因

    关于游标的概念

    • 在springboot项目中,不管是使用java-mongo-driver或者springframework.data.mongodb.core依赖包,当使用 find() 相关函数从 mongo 获取数据时,它返回的并不是数据本身,而是一个游标,且每一个游标都对应一个 id,mongo 服务器会管理这个游标。真正获取数据是用这个游标去 mongo 获取数据;且为了提高 io 利用率,用游标获取数据是批量返回,每一批的大小是由 batch_size 参数决定的,默认是 101 行。真正获取数据的触发时间是在调用 find() 相关函数拿到游标之后,在第一次用 iterator 迭代游标时,客户端会将根据游标拿到的这一批数据放到内存中,然后再用 iterator.next() 一条一条的读取。当内存中的这一批数据迭代完之后,客户端会用这个游标去 mongo 服务器去取下一批数据。
    • 游标是 mongo 服务器生成的,是一种系统资源,类似于线程。所以游标用完了需要及时回收。游标有个超时时间,默认为 10min。在超时时间内,如果客户端使用完游标,则会向服务器发送 close 命令,服务器接口到这个命令之后就会回收游标;另一种情况是,在超时时间内,客户端未使用完游标,则服务器会主动回收游标。在可以设置让服务器永远不回收掉游标。

    游标为什么会找不到?

    • 游标找不到通常有以下两种情况:
      • 客户端游标超时,被服务端回收,再用游标向服务器请求数据时就会出现游标找不到的情况。
      • 在 mongo 集群环境下,可能会出现游标找不到的情况。游标由 mongo 服务器生成,在集群环境下,当使用 find() 相关函数时返回一个游标,假设此时该游标由 A 服务器生成,迭代完数据继续请求数据时,访问到了 B 服务器,但是该游标不是 B 生成的,此时就会出现游标找不到的情况。正常情况下,在 mongo 集群时,会将 mongo 地址以 ip1:port1,ip2:port2,ip3:port3 形式传给 mongo 驱动,然后驱动能够自动完成负载均衡和保持会话转发到同一台服务器,此时不会出现游标找不到的情况。但当我们自己搭建了负载均衡层,且用load balancer地址来连接时,就会出现游标找不到的情况。

    游标相关的设置(仅考虑超时情况)

    • 在服务端增大 mongo 服务器的游标超时时间。参数是 cursorTimeoutMillis,其默认是 10 min。修改后需重启 mongo 服务器。
    • 在客户端一次性获取到全部符合条件的数据。也即将batch_size设置为很大的数,但次数若真的有很多数据的话,则对系统内存要求较高,同时如果数据量过大或处理过程过慢依旧会出现游标超时的情况。所以batch_size的评估是一个技术活。
    • 客户端设置游标永不超时:
      • 这种方式的缺点是如果程序意外停止或异常,该游标永远不会被释放,除非重启 mongo,否则会一直占用系统资源,属于危险操作。经过咨询DBA,一般很少对游标的数量进行监控,一般是由其引起的连锁反应如CPU/内存过高才能引起关注,一般的处理方式也就是重启mongo服务器,这样影响就比较大了。
      • 经过查询,在mongo 3.6版本后,客户端就算把游标设置为永不超时。服务端仍然会在闲置30分钟后将其kill掉,所以大量查询若超过30分钟的话需要手动执行下refreshsession来防止超时。但在3.6以下版本则会一直存在。mongo文档

    相关文章

      网友评论

          本文标题:springboot mongo查询游标(cursor)不存在错

          本文链接:https://www.haomeiwen.com/subject/nrjnkrtx.html