tonwei139 发表于 2016-12-2 07:30:43

Mongo读写分离

 Mongo的主从和复制集结构提供良好的读写分离环境,Mongo的java-driver也实现了读写分离的参数,这给程序开发减少了很多工作。现在我们看一下Mongo-Java-Driver读写分离的一些机制。
    MongoJavaDriver的读是通过设置ReadReference参数,顾名思义,读参照,或者读偏好。与之对应的是WriteConcern,字面意思写涉及,就是规定了写的一些参数,比如是否是一致写,对应Mongo中的w,j,fync等参数,我们暂不讨论。在MongoDB 2.0/Java Driver 2.7版本之前,是通过MongoOption的slaveOk参数控制从库的读,在之后的版本已经废弃。
ReadPreference使用方法:


[*]m.setReadPreference(new ReadPreference().SECONDARY);

    ReadReference是一个类,定义了三种读的参数:Primary、Secondery、Taged,Taged是一个内部类,使用DbObject赋值。这个方便程序自定义读参数,暂不讨论。

我们还是使用复制集连接代码,见此篇博客

读测试代码:

[*]public static void main(String args[]){

[*]        DB db = m.getDB("test");
[*]        db.authenticate("test", "123".toCharArray());
[*]        while(true){
[*]            DBCollection dbcol = db.getCollection("things");
[*]            System.out.println(dbcol.findOne());
[*]            try {
[*]                Thread.sleep(500);
[*]            } catch (InterruptedException e) {
[*]                // TODO Auto-generated catch block
[*]                e.printStackTrace();
[*]            }
[*]        }
[*]        
[*]    }

每隔0.5秒查询一次数据库,观察mongostat状态。当我们不设置读参数的时候,Mongo只在主库上读,这跟Mongo文档中写的有出入,我们的观察结果页印证了这点,两个从库上都是是没有任何读的。

当我们设置了

[*]m.setReadPreference(new ReadPreference().SECONDARY);

之后,再观察两个从库

已经有读了,也可以看出两个从库是交替读的(并不严格,后面会说),而主库没有任何读。
这是为什么呢,我们来看他的实现机制。
首先从findOne()函数开始,这个函数重载了很多方法,最终都是调用:

[*]public DBObject findOne( DBObject o, DBObject fields, ReadPreference readPref ) {

[*]        Iterator<DBObject> i = __find( o , fields , 0 , -1 , 0, getOptions(), readPref, getDecoder() );
[*]        DBObject obj = (i == null ? null : i.next());
[*]        if ( obj != null && ( fields != null && fields.keySet().size() > 0 ) ){
[*]            obj.markAsPartialObject();
[*]        }
[*]        return obj;
[*]    }

这是最上层返回数据的函数,我们看到_find()方法中已经存在readPref参数了,这个函数是个抽象函数,DBApiLayer类实现了此方法,继续往下走:

[*]Response res = _connector.call( _db , this , query , null , 2, readPref, decoder );

函数中调用了_connector.call(),这估计就是执行命令的函数了,continue:

[*]if (readPref == null)

[*]            readPref = ReadPreference.PRIMARY;
[*]
[*]        if (readPref == ReadPreference.PRIMARY && m.hasOption( Bytes.QUERYOPTION_SLAVEOK ))
[*]           readPref = ReadPreference.SECONDARY;
[*]...

[*]final DBPort port = mp.get( false , readPref, hostNeeded );

[*]...
[*]res = port.call( m , coll, readPref, decoder );
[*]...


我们看到了我们开头讲的参数设置的判断,如果不设置readPref,那么默认PRIMARY,由于Bytes.QUERYOPTION_SLAVEOK这个参数已经废弃,而且默认是false,所以其他的情况下就是SECONDARY了。
程序是通过DBPort这个类去执行Mongo命令的,我们看得到port的mp.get()函数:

[*]if ( !(readPref == ReadPreference.PRIMARY) && _rsStatus != null ){

[*]                // if not a primary read set, try to use a secondary
[*]                // Do they want a Secondary, or a specific tag set?
[*]                if (readPref == ReadPreference.SECONDARY) {
[*]                    ServerAddress slave = _rsStatus.getASecondary();
[*]                    if ( slave != null ){
[*]                        return _portHolder.get( slave ).get();
[*]                    }
[*]                } else if (readPref instanceof ReadPreference.TaggedReadPreference) {
[*]                    // Tag based read
[*]                    ServerAddress secondary = _rsStatus.getASecondary( ( (TaggedReadPreference) readPref ).getTags() );
[*]                    if (secondary != null)
[*]                        return _portHolder.get( secondary ).get();
[*]                    else
[*]                        throw new MongoException( "Could not find any valid secondaries with the supplied tags ('" +
[*]                                                  ( (TaggedReadPreference) readPref ).getTags() + "'");
[*]                }
[*]            }
[*]....
[*]            // use master
            DBPort p = _masterPortPool.get();
            if ( keep && _inRequest ) {
                // if within request, remember port to stick to same server
                _requestPort = p;
            }

[*]....


这就比较明了了,通过上一篇文章提到的ReplicaSetStatus类的getASecondary()去得到slave:

[*]int start = pRandom.nextInt( pNodes.size() );
[*]Node n = pNodes.get( ( start + i ) % nodeCount );
[*]if ( ! n.secondary() ){
        mybad++;
        continue;
} else if (pTagKey != null && !n.checkTag( pTagKey, pTagValue )){
        mybad++;
        continue;
}


通过一个random的nextInt选择从库,所以说是随即的,不是Round-Robin,交替读也不是这么严格的,但是基本可以这么认为,不是问题。
至于主库的选择,那个实现的比较复杂,他会去判断是不是读的时候主库已经切换,等等严格的检查。

结语:通过简单的设置ReadPreference就可以实现Mongo的读写分离,这对程序再简单不过了。但是由于Mongo跟Mysql都是通过读日志实现的数据同步,短暂的延迟是必然的,而且Mongo现在的版本是全局锁,主从同步也是个问题,特别是设置了严格同步写入的时候。当然这不是Mongo擅长做的事情,你可以用在商品评论,SNS等不在意数据延迟的应用中,真的很奏效。
页: [1]
查看完整版本: Mongo读写分离