环境
mongodb:3.4.15
java:1.7
场景
对用户日志表补加时间戳的字段;
具体代码如下:
public static void main(String[] args) {
final MongoCollection<Document> useropRecord;
//连接数据库 start
MongoCredential credential = MongoCredential.createCredential("gg_user_db_rw", "gg_user_db", "gg_user_db_rw.gogoal.com".toCharArray());
ServerAddress serverAddress;
serverAddress = new ServerAddress("106.75.51.20", 35724);//35724
List<ServerAddress> addrs = new ArrayList<ServerAddress>();
addrs.add(serverAddress);
List<MongoCredential> credentials = new ArrayList<MongoCredential>();
credentials.add(credential);
@SuppressWarnings("resource")
MongoClient mongoClient = new MongoClient(addrs, credentials);
System.out.println("Connect to database successfully");
//连接数据库 end
MongoDatabase database = mongoClient.getDatabase("gg_user_db");
useropRecord = database.getCollection("userop_record");//埋点表
Document match = new Document();
match.append("_tm", null);
Date stringToDate = DateUtil.stringToDate("2017-01-01", "yyyy-MM-dd");
match.append("date", new Document("$gte", stringToDate));
match.append("code", "S3_06");
useropRecord.find(match).forEach(new Block<Document>() {
int aa=3000;
@Override
public void apply(Document doc) {
Document project = new Document();
project.append("$set", new Document("_tm", new BSONTimestamp((int)(System.currentTimeMillis() / 1000), aa++)));
useropRecord.updateMany(new BasicDBObject("_id", doc.get("_id")), project);
if(aa >= 4000){
aa = 3000;
}
}
}
);
}
上面这段代码 执行着就会报如下错误:
Exception in thread "main" com.mongodb.MongoCursorNotFoundException: Query failed with error code -5 and error message 'Cursor 5741457193246430646 not found on server 106.75.51.20:35724' on server 106.75.51.20:35724
at com.mongodb.operation.QueryHelper.translateCommandException(QueryHelper.java:27)
at com.mongodb.operation.QueryBatchCursor.getMore(QueryBatchCursor.java:213)
at com.mongodb.operation.QueryBatchCursor.hasNext(QueryBatchCursor.java:103)
at com.mongodb.MongoBatchCursorAdapter.hasNext(MongoBatchCursorAdapter.java:46)
at com.mongodb.OperationIterable.forEach(OperationIterable.java:72)
at com.mongodb.FindIterableImpl.forEach(FindIterableImpl.java:166)
at test.MongodbTimestamp.main(MongodbTimestamp.java:54)
这段错误的原因呢,这里引用网上的解释:
你在用 db.collection.find() 的时候,它返回的不是所有的数据,而实际上是一个“cursor”。它的默认行为是:第一次向数据库查询 101 个文档,或 1 MB 的文档,取决于哪个条件先满足;之后每次
cursor 中的文档用尽后,查询 4 MB 的文档。另外,find() 的默认行为是返回一个 10 分钟无操作后超时的 cursor。如果我一个 batch 的文档十分钟内没处理完,过后再处理完了,再用同一个 cursor id 向服务器取下一个 batch,这时候
cursor id 当然已经过期了,这也就能解释为啥我得到 cursor id 无效的错误了。Stack Overflow 上有人提出过解决方法,是在 find() 时传入 timeout=False 来禁用 10 分钟超时的保护措施。但是我觉得这是非常差的办法,因为如果你循环时产生异常,甚至断电或断网,都会导致 MongoDB 服务器资源永远无法被释放。而更好的办法是(我也发在了 Stack Overflow 上),估计一个 batch 大小,让 MongoDB 客户端每次抓取的文档在 10 分钟内能用完,这样客户端就不得不 10 分钟内至少联系服务器一次,保证cursor 不超时。
具体做法:useropRecord.find(match).batchSize(10000)
修改后的代码
public static void main(String[] args) {
final MongoCollection<Document> useropRecord;
//连接数据库 start
MongoCredential credential = MongoCredential.createCredential("gg_user_db_rw", "gg_user_db", "gg_user_db_rw.gogoal.com".toCharArray());
ServerAddress serverAddress;
serverAddress = new ServerAddress("106.75.51.20", 35724);//35724
List<ServerAddress> addrs = new ArrayList<ServerAddress>();
addrs.add(serverAddress);
List<MongoCredential> credentials = new ArrayList<MongoCredential>();
credentials.add(credential);
@SuppressWarnings("resource")
MongoClient mongoClient = new MongoClient(addrs, credentials);
System.out.println("Connect to database successfully");
//连接数据库 end
MongoDatabase database = mongoClient.getDatabase("gg_user_db");
useropRecord = database.getCollection("userop_record");//埋点表
Document match = new Document();
match.append("_tm", null);
Date stringToDate = DateUtil.stringToDate("2017-01-01", "yyyy-MM-dd");
match.append("date", new Document("$gte", stringToDate));
match.append("code", "S3_06");
useropRecord.find(match).batchSize(10000).forEach(new Block<Document>() {
int aa=3000;
@Override
public void apply(Document doc) {
Document project = new Document();
project.append("$set", new Document("_tm", new BSONTimestamp((int)(System.currentTimeMillis() / 1000), aa++)));
useropRecord.updateMany(new BasicDBObject("_id", doc.get("_id")), project);
if(aa >= 4000){
aa = 3000;
}
}
}
);
}
这里我估计的一个大小是10000
,之前是我写5万
,依然会报错,后来改3万
,还是会报错。
最后我测试看了下,大概10
分钟 数据库就更新1万
条数据,所以改成1万
了。
从目前实战的效果来看,昨天跑了一下午,今天又跑了一上午,都没有报这个错误了。
参数地址: