近期对两个应用进行改造,在上线过程中出现一系列问题(其中一部分是由于ObjectId误区导致的)
先来了解下ObjectId:
TimeStamp
前 4位是一个unix的时间戳,是一个int类别,我们将上面的例子中的objectid的前4位进行提取“4df2dcec”,然后再将他们安装十六进制 专为十进制:“1307761900”,这个数字就是一个时间戳,为了让效果更佳明显,我们将这个时间戳转换成我们习惯的时间格式(精确到秒)
2011年 06月 11日 星期六 03:11:40 UTC
Machine
接下来的三个字节,就是 2cdcd2 ,这三个字节是所在主机的唯一标识符,一般是机器主机名的散列值,这样就确保了不同主机生成不同的机器hash值,确保在分布式中不造成冲突,这也就是在同一台机器生成的objectid中间的字符串都是一模一样的原因。
pid
上面的Machine是为了确保在不同机器产生的objectid不冲突,而pid就是为了在同一台机器不同的mongodb进程产生了objectid不冲突,接下来的0936两位就是产生objectid的进程标识符。
increment
前面的九个字节是保证了一秒内不同机器不同进程生成objectid不冲突,这后面的三个字节a8b817,是一个自动增加的计数器,用来确保在同一秒内产生的objectid也不会发现冲突,允许256的3次方等于16777216条记录的唯一性。
ObjectId唯一性
误区 一 、文档顺序和插入顺序一致?
单线程情况
多线程、多机器或多进程情况
解决办法:
Query query = new Query();
if (id != null)
{
query.addCriteria(Criteria.where("_id").gt(id));
}
query.with(new Sort(Sort.Direction.ASC, "_id"));
Comparator<DBObject> comparator = new Comparator<DBObject>()
{
@Override
public int compare(DBObject o1, DBObject o2)
{
return ((ObjectId)o1.get("_id")).compareTo((ObjectId)o2.get("_id"));
}
};
PriorityQueue<DBObject> queue = new PriorityQueue<DBObject>(200,comparator);
误区 二 、多客户端高并发时,是否可以保证顺序(sort之后)?
4df2dcec aaaa eeee 36a8b813
4df2dcec bbbb 1111 36a8b814
4df2dcec aaaa ffff 36a8b814
4df2dcec aaaa eeee 36a8b814
解决办法:
误区 三 、不在DBObject设置_id使用mongoDB设置ObjectId?
mongoDB插入操作时,new DBBasicObject()时,大家看到_id是没有被填值的,除非手工的设置_id。那么是否是服务端设置的呢?
public WriteResult insert(List<DBObject> list, com.mongodb.WriteConcern concern, DBEncoder encoder ){ if (concern == null) {
throw new IllegalArgumentException("Write concern can not be null");
} return insert(list, true, concern, encoder);
}
可以看到需要添加,默认都为添加
protected WriteResult insert(List<DBObject> list, boolean shouldApply , com.mongodb.WriteConcern concern, DBEncoder encoder ){ if (encoder == null)
encoder = DefaultDBEncoder.FACTORY.create(); if ( willTrace() ) {
for (DBObject o : list) {
trace( "save: " + _fullNameSpace + " " + JSON.serialize( o ) );
}
} if ( shouldApply ){
for (DBObject o : list) {
apply(o);
_checkObject(o, false, false);
Object id = o.get("_id");
if (id instanceof ObjectId) {
((ObjectId) id).notNew();
}
}
} WriteResult last = null; int cur = 0;
int maxsize = _mongo.getMaxBsonObjectSize();
while ( cur < list.size() ) { OutMessage om = OutMessage.insert( this , encoder, concern ); for ( ; cur < list.size(); cur++ ){
DBObject o = list.get(cur);
om.putObject( o ); // limit for batch insert is 4 x maxbson on server, use 2 x to be safe
if ( om.size() > 2 * maxsize ){
cur++;
break;
}
} last = _connector.say( _db , om , concern );
} return last;
}
自动添加ObjectId的操作
/**
* calls {@link DBCollection#apply(com.mongodb.DBObject, boolean)} with ensureID=true
* @param o <code>DBObject</code> to which to add fields
* @return the modified parameter object
*/
public Object apply( DBObject o ){
return apply( o , true );
} /**
* calls {@link DBCollection#doapply(com.mongodb.DBObject)}, optionally adding an automatic _id field
* @param jo object to add fields to
* @param ensureID whether to add an <code>_id</code> field
* @return the modified object <code>o</code>
*/
public Object apply( DBObject jo , boolean ensureID ){ Object id = jo.get( "_id" );
if ( ensureID && id == null ){
id = ObjectId.get();
jo.put( "_id" , id );
} doapply( jo ); return id;
}
可以看到,mongoDB的驱动包中是会自动添加ObjectId的。
public WriteResult save( DBObject jo, WriteConcern concern ){
if ( checkReadOnly( true ) )
return null; _checkObject( jo , false , false ); Object id = jo.get( "_id" ); if ( id == null || ( id instanceof ObjectId && ((ObjectId)id).isNew() ) ){
if ( id != null && id instanceof ObjectId )
((ObjectId)id).notNew();
if ( concern == null )
return insert( jo );
else
return insert( jo, concern );
} DBObject q = new BasicDBObject();
q.put( "_id" , id );
if ( concern == null )
return update( q , jo , true , false );
else
return update( q , jo , true , false , concern ); }
综上所述,默认情况下ObjectId是由客户端生成的,并不是不设置就由服务端生成的。
误区 四 、findAndModify是否真的可以获取到自增变量?
DBObject update = new BasicDBObject("$inc", new BasicDBObject("counter", 1));
DBObject query = new BasicDBObject("_id", key);
DBObject result = getMongoTemplate().getCollection(collectionName).findAndModify(query, update);
if (result == null)
{
DBObject doc = new BasicDBObject();
doc.put("counter", 1L);
doc.put("_id", key);
// insert(collectionName, doc);
getMongoTemplate().save(doc, collectionName);
return 1L;
}
return (Long) result.get("counter");
获取自增变量会使用这种方法编写,但是,我们执行完成后会发现。