mongodb查詢速度慢是什么原因?


mongodb查詢速度慢是什么原因?

通過mongodb客戶端samus代碼研究解決問題         最近有項目需要用到mongodb,於是在網上下載了mongodb的源碼,根據示例寫了測試代碼,但發現一個非常奇怪的問題:插入記錄的速度比獲取數據的速度還要快,而且最重要的問題是獲取數據的速度無法讓人接受。

   

     測試場景:主文檔存儲人員基本信息,子文檔一存儲學生上課合同數據集合,這個集合多的可達到幾百,子文檔二存儲合同的付款記錄集合,集合大小一般不會超過50。根據人員ID查詢人員文檔,序列化后的大小為180K不到,但消耗的時間在400ms以上。

   

    我的主要問題在於不能接收獲取一個180K的記錄需要400ms以上,這比起傳統的RDBMS都沒有優勢,而且mongodb也是內存映射機制,沒道理性能如此之差,而且網絡上關於它的性能測試數據遠遠好於我的測試結果。

   

    排除方式一:是不是因為有子文檔的原因?

    找一個沒有任何合同記錄的文檔查詢,發現結果依舊,沒有明顯的改善;

   

    排除方式二:沒有創建索引?

    在搜索列ID上創建索引,結果依舊;

   

   排除方式三:是不是文檔數量過大?

   一萬多行只是小數目,沒理由,mongodb管理上千萬的文檔都是沒有問題的,於時還是決定試一試,將記錄全部刪除,插入一條記錄然后查詢,結果依舊;

   

   排除方式四:是不是由於客戶端序列化的問題?

   由於我存儲的是自定義的對象,不是默認的Document,所以決定嘗試直接存儲Document,Document就兩個字段,獲取速度還是需要180ms。

   

   排除方式五:是否由於客戶機器是32位,而mongodb服務是64?

   將程序放在64位機器上測試,問題依舊。

   

   排除方式六:是否由於網絡傳輸問題?

   沒道理啊,測試的客戶端以及服務端均在同一局域網,但還是嘗試將客戶端程序直接在mongodb服務器上執行,問題一樣;

   

   上面的六種方式都已經嘗試過,沒有解決,最后決定求助於老代,畢竟是用過mongodb的高人,給我兩個建議就搞定了:

   

   排除方式七:查看mongodb數據文件,看是否已經很大?

   經查看,總大小才64M,這比32位文件上限的2G來講,可以基本忽略;

   

   排除方式八:連接字符串。

   Servers=IP:27017;ConnectTimeout=30000;ConnectionLifetime=300000;MinimumPoolSize=8;MaximumPoolSize=256;Pooled=true

 

   我一看到這個參考字符串,第一印象是,我的寫法和它不一樣(string connectionString = "mongodb://localhost"; ),然后發現有兩個重要的參數:

   1:ConnectionLifetime=300000,從字面意思來看,是說連接的生命周期,而它的數值設置如此大,顯然說明此連接不會被立即關閉,這和sql server的做法有所區別;

   2ooled=true,從字面意思來看,應該是有連接池的概念。

 

   分析:從上面的連接參數來看,我之前所理解的連接,就是客戶端與服務端之間的連接,它需要在使用完之后馬上關閉,即客戶端與服務端不在有tcp連接。但我沒有很好的理解連接池的作用。連接池實際上從存儲很多個已經和服務端建立tcp連接的connection,在它的生命周期內一直保持和服務端的連接,生命周期過后會變成失效連接等待回收。

   

   重新修改連接字符串再進行測試,問題解決,只有第一次請求時,由於需要創建tcp連接,性能會受影響,后面的請求,因為有連接池的存在,性能得到成倍提高。

   

   最后看了下samus源碼,就可以看出它是如何使用連接池的。

   

   先看下我寫的一個mongodb的幫助類:里面有創建Mongo對象等常規操作。

public

class MongodbFactory2<T>: IDisposable where T : class

    {

        //public  string connectionString = "mongodb://10.1.55.172";

 

public

string connectionString = ConfigurationManager.AppSettings["mongodb"];

        public

string databaseName =

"myDatabase";

        Mongo mongo;

        MongoDatabase mongoDatabase;

        public  MongoCollection<T> mongoCollection;

        public  MongodbFactory2()

        {       

            mongo = GetMongo();

            mongoDatabase = mongo.GetDatabase(databaseName) as MongoDatabase;

            mongoCollection = mongoDatabase.GetCollection<T>() as MongoCollection<T>;

            mongo.Connect();

        }

        public

void Dispose()

        {

            this.mongo.Disconnect();

        }

 

 

        ///

<summary>

/// 配置Mongo,將類T映射到集合  

        ///

</summary>

 

 

private Mongo GetMongo()

        {

            var config =

new MongoConfigurationBuilder();

            config.Mapping(mapping =>

            {

                mapping.DefaultProfile(profile =>

                {

                    profile.SubClassesAre(t => t.IsSubclassOf(typeof(T)));

                });

                mapping.Map<T>();

            });

            config.ConnectionString(connectionString);

            return

new Mongo(config.BuildConfiguration());

 

        }

 

 

     從上面的代碼中可以看到有這么一句:mongo.Connect(),我第一印象就是創建客戶端與服務端的連接,其實有了連接池,這個操作並非每次都創建遠程連接,有的情況只是從連接池中直接返回可用連接對象而已。

   

   從源碼分析是如何利用連接池,連接是如何創建的。

   1:Mongo類的Connect函數:需要跟蹤_connection對象。

    

///

<summary>

///   Connects to server.

        ///

</summary>

///

<returns></returns>

///

<exception cref = "MongoDB.MongoConnectionException">Thrown when connection fails.</exception>

 

public

void Connect()

        {

            _connection.Open();

        }

 

 

    2:再看這句:return new Mongo(config.BuildConfiguration());

    

///

<summary>

///   Initializes a new instance of the <see cref = "Mongo" /> class.

        ///

</summary>

///

<param name = "configuration">The mongo configuration.</param>

 

public Mongo(MongoConfiguration configuration){

            if(configuration ==

null)

                throw

new ArgumentNullException("configuration");

 

            configuration.ValidateAndSeal();

 

            _configuration = configuration;

            _connection = ConnectionFactoryFactory.GetConnection(configuration.ConnectionString);

        }

 

 

        上面代碼的最后一句有_connection的生成過程。

    3:可以跟蹤到最終生成connection的函數,終於看到builder.Pooled這個參數了,這的值就是連接串中的參數。

    

///

<summary>

/// Creates the factory.

        ///

</summary>

///

<param name="connectionString">The connection string.</param>

///

<returns></returns>

 

private

static IConnectionFactory CreateFactory(string connectionString){

            var builder =

new MongoConnectionStringBuilder(connectionString);

            

            if(builder.Pooled)

                return

new PooledConnectionFactory(connectionString);

            

            return

new SimpleConnectionFactory(connectionString);

        }

 

    

    4:再看PooledConnectionFactory是如何創建連接的:這的作用就是將可用連接放入連接池中,而最終真正創建連接的函數是CreateRawConnection()

      

///

<summary>

/// Ensures the size of the minimal pool.

        ///

</summary>

 

private

void EnsureMinimalPoolSize()

        {

            lock(_syncObject)

                while(PoolSize < Builder.MinimumPoolSize)

                    _freeConnections.Enqueue(CreateRawConnection());

        }

 

 

    5:真正遠程連接部分。

    

View Code ///

<summary>

/// Creates the raw connection.

        ///

</summary>

///

<returns></returns>

 

protected RawConnection CreateRawConnection()

        {

            var endPoint = GetNextEndPoint();

            try

            {

                return

new RawConnection(endPoint, Builder.ConnectionTimeout);

            }catch(SocketException exception){

                throw

new MongoConnectionException("Failed to connect to server "

+ endPoint, ConnectionString, endPoint, exception);

            }

        }

        private

readonly TcpClient _client =

new TcpClient();

        private

readonly List<string> _authenticatedDatabases =

new List<string>();

        private

bool _isDisposed;

 

        ///

<summary>

/// Initializes a new instance of the <see cref="RawConnection"/> class.

        ///

</summary>

///

<param name="endPoint">The end point.</param>

///

<param name="connectionTimeout">The connection timeout.</param>

 

public RawConnection(MongoServerEndPoint endPoint,TimeSpan connectionTimeout)

        {

            if(endPoint ==

null)

                throw

new ArgumentNullException("endPoint");

 

            EndPoint = endPoint;

            CreationTime = DateTime.UtcNow;

            

            _client.NoDelay =

true;

            _client.ReceiveTimeout = (int)connectionTimeout.TotalMilliseconds;

            _client.SendTimeout = (int)connectionTimeout.TotalMilliseconds;

 

            //Todo: custom exception?

            _client.Connect(EndPoint.Host, EndPoint.Port);

        }

 

 

        

        

    接着我們來看下,連接的生命周期是如何實現的:主要邏輯在PooledConnectionFactory,如果發現連接已經過期,則將連接放入不可用隊列,將此連接從空閑連接中刪除掉。

     

View Code ///

<summary>

/// Checks the free connections alive.

        ///

</summary>

 

private

void CheckFreeConnectionsAlive()

        {

            lock(_syncObject)

            {

                var freeConnections = _freeConnections.ToArray();

                _freeConnections.Clear();

 

                foreach(var freeConnection in freeConnections)

                    if(IsAlive(freeConnection))

                        _freeConnections.Enqueue(freeConnection);

                    else

                        _invalidConnections.Add(freeConnection);

            }

        }

     ///

<summary>

/// Determines whether the specified connection is alive.

        ///

</summary>

///

<param name="connection">The connection.</param>

///

<returns>

///

<c>true</c> if the specified connection is alive; otherwise, <c>false</c>.

        ///

</returns>

 

private

bool IsAlive(RawConnection connection)

        {

            if(connection ==

null)

                throw

new ArgumentNullException("connection");

 

            if(!connection.IsConnected)

                return

false;

 

            if(connection.IsInvalid)

                return

false;

 

            if(Builder.ConnectionLifetime != TimeSpan.Zero)

                if(connection.CreationTime.Add(Builder.ConnectionLifetime) < DateTime.Now)

                    return

false;

 

            return

true;

        }

 

 

        

        最后我們來看我最上面的mongodb幫忙類的如下方法:即釋放連接,而這里的釋放也不是直接意義上將連接從客戶端與服務端之間解除,只不過是將此連接從忙隊列中刪除,重新回歸到可用隊列:

        

 

public

void Dispose()

        {

            this.mongo.Disconnect();

        }

 

 

        再看看mongo.Disconnect()

        

///

<summary>

///   Disconnects this instance.

        ///

</summary>

///

<returns></returns>

 

public

bool Disconnect()

        {

            _connection.Close();

            return _connection.IsConnected;

        }

 

        

        繼續往下就會定位到如下核心內容:

         

View Code ///

<summary>

///   Returns the connection.

        ///

</summary>

///

<param name = "connection">The connection.</param>

 

public

override

void Close(RawConnection connection)

        {

            if(connection ==

null)

                throw

new ArgumentNullException("connection");

 

            if(!IsAlive(connection))

            {

                lock(_syncObject)

                {

                    _usedConnections.Remove(connection);

                    _invalidConnections.Add(connection);

                }

 

                return;

            }

 

            lock(_syncObject)

            {

                _usedConnections.Remove(connection);

                _freeConnections.Enqueue(connection);

                Monitor.Pulse(_syncObject);

            }

        }

        

 

 

 

        總結:經過各位不同的嘗試,終於解決了mongodb查詢慢的原因,並非mongodb本身問題,也非網絡,非數據問題指點。,而是在於沒有正確使用好客戶端連接,不容易啊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM