mongo cursor 游标超时

mongo cursor 游标超时

mongo cursor 游标超时问题


场景描述

使用 java 程序通过定时任务处理 mongo 中的数据时(mongo 集合中共有近一亿个文档,每次只处理其中的小二十万),出现了以下错误:

org.springframework.data.mongodb.UncategorizedMongoDbException: Query failed with error code 43 and error message 'Cursor not found, cursor id: 412824332348' on server ip:30000; nested exception is com.mongodb.MongoQueryException: Query failed with error code 43 and error message 'Cursor not found, cursor id: 412824332348' on server ip:30000
or
org.springframework.dao.DataAccessResourceFailureException: Query failed with error code -5 and error message 'Cursor 3998850069977484811 not found on server 172.29.181.137:30000' on server ip:30000; nested exception is com.mongodb.MongoCursorNotFoundException: Query failed with error code -5 and error message 'Cursor 3998850069977484811 not found on server 172.29.181.137:30000' on server ip:30000

关于游标(cursor)

当使用 find() 相关函数从 mongo 获取数据时,它返回的并不是数据本身,而是一个游标,且每一个游标都对应一个 id,mongo 服务器会管理这个游标。正真获取数据是用这个游标去 mongo 获取数据;且为了提高 io 利用率,用游标获取数据是批量返回,每一批的大小是由 batch_size 参数决定的,默认是 101 行。正真获取数据的触发时间是在调用 find() 相关函数拿到游标之后,在第一次用 iterator 迭代游标时,客户端会将根据游标拿到的这一批数据放到内存中,然后再用 iterator.next() 一条一条的读取。当内存中的这一批数据迭代完之后,客户端会用这个游标去 mongo 服务器去取下一批数据。

且,游标是 mongo 服务器生成的,是一种系统资源,类似于线程。所以游标用完了需要及时回收。游标有个超时时间,默认为 10min。在超时时间内,如果客户端使用完游标,则会向服务器发送 close 命令,服务器接口到这个命令之后就会回收游标;另一种情况是,在超时时间内,客户端未使用完游标,则服务器会主动回收游标。

// 切换数据库
MongoDatabase mongoDatabase = MongoUtil.getMongoClient("test");

// 获取集合实例
MongoCollection mongoCollection = mongoDatabase.getCollection("collection_name");

// 组装查询条件
BasicDBObject query = new BasicDBObject();
query.put("username", "momo");

// 查询 此时返回游标
FindIterable<Document> findIterable = mongoCollection.find(query);

// 获取迭代器
MongoCursor<Document> mongoCursor = findIterable.iterator();

Document document;
while (mongoCursor.hasNext()) {
    document = mongoCursor.next();
}

游标为什么会找不到?

游标找不到通常有以下两种情况:

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

解决方案

针对上面游标找不到的两种情况,一般都是以 ip1:port1,ip2:port2,ip3:port3 这种形式配置的,所以这里只讨论第一种情况。

第一种情况的表象是客户端对游标的使用超过了游标超时时间。

  • 1、在服务端增大 mongo 服务器的游标超时时间。参数是 cursorTimeoutMillis,其默认是 10 min。修改后需重启 mongo 服务器。

    // 启动时设置
    mongod -setParameter cursorTimeoutMillis=60000
    // 在线修改
    db.runCommand({setParameter:1,cursorTimeoutMillis:60000})
    
  • 2、在客户端减少游标每次返回的数据量,使每批数据能在 10 min 内消费完。参数是 batchSize,默认值是 101。
    这种方式的缺点是增加了 mongo 连接次数,比较消耗 io。

    // com.mongodb.client
    FindIterable<Document> findIterable = mongoCollection.find(query).batchSize(50);
    // spring-boot-starter-data-mongodb
    mongoTemplate.find(query.cursorBatchSize(50), Object.class, "collection_name");
    
    // 注:当 batchSize 设置为 -1 时只返回一条数据
    
  • 3、在客户端一次性获取到全部符合条件的数据。可以通过两种方式设置,分别是 batchSize 和 exhaust。
    这种方式的缺点是,由于一次性返回了所有数据,对系统内存要求较高。

    // batchSize
    batchSize(Integer.MAX);
    cursorBatchSize(Integer.MAX);
    
    // 如果数据量过大或处理过程过慢依旧会出现游标超时的情况 可以配合 noCursorTimeout 来解决 即
    batchSize(Integer.MAX).noCursorTimeout();
    cursorBatchSize(Integer.MAX).noCursorTimeout();
    
    // exhuast 作用是在一个游标批次中返回全量数据
    mongoTemplate.find(query.exhuast(), Object.class, "collection_name");
    
    // 使用 exhaust 时需注意以下几点
    // 1、mongo cluster 不支持此设置
    // 2、EXHAUST 与 limit() 不兼容
    // 3、使用此模式时需忽略网络超时情况
    
  • 4、客户端设置游标永不超时。参数 noCursorTimeout 为 true。
    这种方式的缺点是,如果程序意外停止或异常,该游标永远不会被释放,除非重启 mongo,否则会一直占用系统资源,属于危险操作。

    // com.mongodb.client
    FindIterable<Document> findIterable = mongoCollection.find(query).noCursorTimeout(true);
    // spring-boot-starter-data-mongodb
    mongoTemplate.find(query.noCursorTimeout(), Object.class, "collection_name");
    

@XGLLHZ-XXX.mp3

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用 MongoDb 进行游标查询时,可以使用 `iterator()` 方法获取游标,然后使用 `next()` 方法逐个获取查询结果。如果想将查询结果转换为实体对象,可以使用 Jackson 库将查询结果转换为 JSON 字符串,再将 JSON 字符串转换为实体对象。具体实现步骤如下: 1. 引入 Jackson 库的依赖: ```xml <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> ``` 其中 `${jackson.version}` 是 Jackson 库的版本号。 2. 定义实体类,例如: ```java public class User { private String id; private String name; private int age; // getter 和 setter 方法 } ``` 3. 在游标查询中获取查询结果,并将结果转换为 JSON 字符串: ```java MongoCursor<Document> cursor = collection.find().iterator(); List<String> jsonList = new ArrayList<>(); while (cursor.hasNext()) { Document document = cursor.next(); String json = document.toJson(); jsonList.add(json); } ``` 4. 将 JSON 字符串转换为实体对象: ```java List<User> userList = new ArrayList<>(); for (String json : jsonList) { User user = objectMapper.readValue(json, User.class); userList.add(user); } ``` 其中 `objectMapper` 是 Jackson 库中的一个对象,可以在初始化时创建: ```java ObjectMapper objectMapper = new ObjectMapper(); ``` 注意,在转换 JSON 字符串时,需要注意实体类中的字段名与 MongoDB 中的字段名是否一致,否则可能会出现转换失败的情况。如果不一致,可以使用 `@JsonProperty` 注解指定字段名,例如: ```java public class User { @JsonProperty("_id") private String id; private String name; private int age; // getter 和 setter 方法 } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值