Mongo連線分析

方丈的寺院發表於2018-04-22

摘要

在前面的文章中有分析過關係型資料庫的連線,以及連線池的原理。在mongo資料庫同樣存在,經常看到有網友在問mongo 連線了資料庫要不要關,怎麼關。內建的資料庫連線池是單執行緒還是多執行緒,mongo伺服器為什麼會殺遊標,殺連線諸如此類的問題,其實這類問題基本上就是連線池的問題,而很多和關係型資料庫是類似的,並不是mongo獨有的。 本文旨在梳理這些問題,進行一個全面的分析。

Client 連線分析

客戶端連線通過driver jar去連線,以java為例,通過mongo-java-driver 連線mongo,這一點和關係型資料庫一樣,不同的是關係型資料庫有一套標準的阻塞型的,寫入JDK的資料庫操作實現,即JDBC。而mongo則是完全由driver提供。在mongo-java-driver 3.0版本之前只提供了同步的driver操作,3.x之後開始提供非同步的driver操作,這邊不做擴散,後續會有相關博文介紹非同步的資料庫操作,本文只介紹同步driver操作。

資料庫操作 一個基於mongo-java-driver-2.14.x的mongo操作流程

public static void main(final String[] args) {
        try {
            final String host = "localhost";
            // 連線配置屬性
            final MongoClientOptions clientOptions = new MongoClientOptions.Builder()
                    .writeConcern(WriteConcern.ACKNOWLEDGED)
                    .readPreference(ReadPreference.secondaryPreferred())
                    .connectionsPerHost(10).socketTimeout(5000).build();
            final List<MongoCredential> credentialsList = new ArrayList<MongoCredential>();
            final String str = "test";
            final char[] psd = str.toCharArray();
            final MongoCredential credential = MongoCredential.createCredential("test",
                    "test",
                    psd);
            credentialsList.add(credential);
            final ServerAddress address = new ServerAddress(host, 27017);
            //包含了一個內建的資料庫連線池
            final MongoClient client = new MongoClient(address, credentialsList, clientOptions);
            final DB db = client.getDB("test");
            final DBCollection postCollection = db.getCollection("test");
            postCollection.findOne();
            //連線關閉,釋放資源
            client.close();
        } catch (final UnknownHostException e) {
            e.printStackTrace();
        }
    }
複製程式碼

這只是一個sample 實際應用中,MongoClient在一個jvm中只應該有一個例項,由他管理連線,進行資料庫操作。 client與資料庫的互動,mongo 協議也是基於TCP的

這裡寫圖片描述
幾個重要的類

MongoClientOptions: 資料庫連線配置項

DB: database連線

DBCollection: collection操作

所以mongo連線的配置核心就在於MongolientOptions類了。比較重要的配置就是 connectionsPerHost,對於線上環境,如果連線資料庫的應用比較多,這個連線數不宜過大 socketTimeout: 資料庫操作超時時間,一般5s中,對於慢操作,不應該一直佔用連線,會損害應用效能,阻塞其他操作

private int minConnectionsPerHost; //每個節點的最小連線數 private int connectionsPerHost = 100; // 每個節點的連線數 private int threadsAllowedToBlockForConnectionMultiplier = 5; //最大等待執行緒 private int maxWaitTime = 1000 * 60 * 2; // 獲取連線最大等待時間 private int maxConnectionIdleTime; // 連線池最大空閒時間 private int maxConnectionLifeTime; private int connectTimeout = 1000 * 10; // 連線最大時間 private int socketTimeout = 0; // 操作最大時間 private boolean socketKeepAlive = false; private boolean autoConnectRetry = false; private long maxAutoConnectRetryTime = 0; // 心跳檢測,保持TCP連線 private int heartbeatFrequency = Integer.parseInt(System.getProperty("com.mongodb.updaterIntervalMS", "5000")); private int minHeartbeatFrequency = Integer.parseInt(System.getProperty("com.mongodb.updaterIntervalNoMasterMS", "500")); private int heartbeatConnectTimeout = Integer.parseInt(System.getProperty("com.mongodb.updaterConnectTimeoutMS", "20000")); private int heartbeatSocketTimeout = Integer.parseInt(System.getProperty("com.mongodb.updaterSocketTimeoutMS", "20000"));

可以和關係型資料庫連線池的實現對比一下

initialSize:初始連線數 maxActive: 最大連線數量 minIdle: 最小連線數量 maxWait: 獲取連線最大等待時間ms minEvictableIdleTimeMillis:連線保持空閒而不被驅逐的最小時間 timeBetweenEvictionRunsMillis:銷燬執行緒的時間檢測 testOnBorrow:申請連線時執行,比較影響效能 validationQuery:testOnBorrow為true檢測是否是有效連線sql testWhileIdle:申請連線的時候檢測

mongo 內建的連線池管理比較簡單,沒有進行連線池的連線有效管理,通過heartbeat間隔一段時間傳送資料包給mongo 伺服器,確保連線有效,這一點和之前介紹的有點區別,之前的銷燬掉無用的連線。這樣會增加額外的網路和CPU負擔。

看下mongo 建立MongoClient的時候會去初始化連線池。等到進行資料庫操作的時候,再去PooledConnectionProvider中獲取一個連線,進行操作

這裡寫圖片描述

總結

通過以上分析,對於mongo driver 3.x 以下的mongo資料庫連線池與關係型資料庫連線池並無區別。只是連線池的實現方式不一樣,比如一個用鎖,一個用訊號量。容器的選型也不太一樣,但是這些並不會影響到大部分的應用開發者對於連線的配置和理解。回到開頭提出的幾個問題,看到這裡自然就有答案了。而對於伺服器kill掉遊標這個問題,遊標本身也不是mongo獨有的,客戶端通過遊標控制結果數量的讀取,遊標本身也是佔用不少資源的,所以不能一直佔有,伺服器kill掉遊標,所以遊標佔用時間太長。可以通過db.serverStatus().metrics.cursor去檢視timeout的遊標,找出耗時操作,進行優化。

"cursor": {
"timedOut": "NumberLong(99)"
"open": {
"noTimeout": "NumberLong(0)"
"pinned": "NumberLong(3)"
"total": "NumberLong(3)"
}
}

複製程式碼

相關文章