Mongo讀寫分離

   轉發自http://blog.chinaunix.net/uid-15795819-id-3075952.html 


Mongo的主從和複製集結構提供良好的讀寫分離環境,Mongo的java-driver也實現了讀寫分離的參數,這給程序開發減少了很多工作。現在我們看一下Mongo-Java-Driver讀寫分離的一些機制。

    MongoJavaDriver的讀是通過設置ReadReference參數,顧名思義,讀參照,或者讀偏好。與之對應的是WriteConcern,字面意思寫涉及,就是規定了寫的一些參數,比如是否是一致寫,對應Mongo中的w,j,fync等參數,我們暫不討論。在MongoDB 2.0/Java Driver 2.7版本之前,是通過MongoOption的slaveOk參數控制從庫的讀,在之後的版本已經廢棄。
ReadPreference使用方法:
  1. m.setReadPreference(new ReadPreference().SECONDARY);
    ReadReference是一個類,定義了三種讀的參數:Primary、Secondery、Taged,Taged是一個內部類,使用DbObject賦值。這個方便程序自定義讀參數,暫不討論。

我們還是使用複製集連接代碼,見此篇博客

讀測試代碼:
  1. public static void main(String args[]){
  2.         DB db = m.getDB("test");
  3.         db.authenticate("test", "123".toCharArray());
  4.         while(true){
  5.             DBCollection dbcol = db.getCollection("things");
  6.             System.out.println(dbcol.findOne());
  7.             try {
  8.                 Thread.sleep(500);
  9.             } catch (InterruptedException e) {
  10.                 // TODO Auto-generated catch block
  11.                 e.printStackTrace();
  12.             }
  13.         }
  14.         
  15.     }
每隔0.5秒查詢一次數據庫,觀察mongostat狀態。當我們不設置讀參數的時候,Mongo只在主庫上讀,這跟Mongo文檔中寫的有出入,我們的觀察結果頁印證了這點,兩個從庫上都是是沒有任何讀的。

當我們設置了
  1. m.setReadPreference(new ReadPreference().SECONDARY);
之後,再觀察兩個從庫

已經有讀了,也可以看出兩個從庫是交替讀的(並不嚴格,後面會說),而主庫沒有任何讀。
這是爲什麼呢,我們來看他的實現機制。
首先從findOne()函數開始,這個函數重載了很多方法,最終都是調用:
  1. public DBObject findOne( DBObject o, DBObject fields, ReadPreference readPref ) {
  2.         Iterator<DBObject> i = __find( o , fields , 0 , -, 0, getOptions(), readPref, getDecoder() );
  3.         DBObject obj = (== null ? null : i.next());
  4.         if ( obj != null && ( fields != null && fields.keySet().size() > 0 ) ){
  5.             obj.markAsPartialObject();
  6.         }
  7.         return obj;
  8.     }
這是最上層返回數據的函數,我們看到_find()方法中已經存在readPref參數了,這個函數是個抽象函數,DBApiLayer類實現了此方法,繼續往下走:
  1. Response res = _connector.call( _db , this , query , null , 2, readPref, decoder );
函數中調用了_connector.call(),這估計就是執行命令的函數了,continue:
  1. if (readPref == null)
  2.             readPref = ReadPreference.PRIMARY;

  3.         if (readPref == ReadPreference.PRIMARY && m.hasOption( Bytes.QUERYOPTION_SLAVEOK ))
  4.            readPref = ReadPreference.SECONDARY;
  5. ...
  6. final DBPort port = mp.get( false , readPref, hostNeeded );
  7. ...
  8. res = port.call( m , coll, readPref, decoder );
  9. ...
我們看到了我們開頭講的參數設置的判斷,如果不設置readPref,那麼默認PRIMARY,由於Bytes.QUERYOPTION_SLAVEOK這個參數已經廢棄,而且默認是false,所以其他的情況下就是SECONDARY了。
程序是通過DBPort這個類去執行Mongo命令的,我們看得到port的mp.get()函數:
  1. if ( !(readPref == ReadPreference.PRIMARY) && _rsStatus != null ){
  2.                 // if not a primary read set, try to use a secondary
  3.                 // Do they want a Secondary, or a specific tag set?
  4.                 if (readPref == ReadPreference.SECONDARY) {
  5.                     ServerAddress slave = _rsStatus.getASecondary();
  6.                     if ( slave != null ){
  7.                         return _portHolder.get( slave ).get();
  8.                     }
  9.                 } else if (readPref instanceof ReadPreference.TaggedReadPreference) {
  10.                     // Tag based read
  11.                     ServerAddress secondary = _rsStatus.getASecondary( ( (TaggedReadPreference) readPref ).getTags() );
  12.                     if (secondary != null)
  13.                         return _portHolder.get( secondary ).get();
  14.                     else
  15.                         throw new MongoException( "Could not find any valid secondaries with the supplied tags ('" +
  16.                                                   ( (TaggedReadPreference) readPref ).getTags() + "'");
  17.                 }
  18.             }
  19. ....
  20.             // use master
                DBPort p = _masterPortPool.get();
                if ( keep && _inRequest ) {
                    // if within request, remember port to stick to same server
                    _requestPort = p;
                }
  21. ....
這就比較明瞭了,通過上一篇文章提到的ReplicaSetStatus類的getASecondary()去得到slave
  1. int start = pRandom.nextInt( pNodes.size() );
  2. Node n = pNodes.get( ( start + i ) % nodeCount );
  3. 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等不在意數據延遲的應用中,真的很奏效。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章